From efe3a98eec7e10a60668ebe58928e6c071a8d5e6 Mon Sep 17 00:00:00 2001 From: Richard Carson Date: Sat, 14 Oct 2023 12:36:05 -0400 Subject: [PATCH] Fix runtime concurrency --- Cargo.lock | 1810 +++++--- Cargo.toml | 6 +- README.md | 16 +- example_extensions/colour_utils.js | 10 +- example_extensions/example.js | 37 + example_extensions/simple.js | 12 + example_extensions/stateful_functions.js | 49 +- example_extensions/zarbans_grotto.js | 4328 +++++++++++++++++-- example_scripts/populate_state.lav | 2 + examples/adding_functionality.rs | 28 +- examples/basic_expressions.rs | 10 +- examples/interactive_console.rs | 22 +- examples/using_extensions.rs | 10 +- src/decorators.rs | 542 --- src/decorators/currency.rs | 119 + src/decorators/mod.rs | 233 + src/decorators/numeric.rs | 136 + src/decorators/primitives.rs | 127 + src/decorators/string.rs | 114 + src/errors.rs | 465 +- src/errors/arrays/array_empty.rs | 12 +- src/errors/arrays/array_index.rs | 12 +- src/errors/arrays/array_length.rs | 12 +- src/errors/external/io.rs | 14 +- src/errors/external/network.rs | 14 +- src/errors/external/pest.rs | 12 +- src/errors/external/script.rs | 26 +- src/errors/functions/ambiguous_function.rs | 18 +- src/errors/functions/decorator_arg_type.rs | 25 +- src/errors/functions/decorator_name.rs | 12 +- src/errors/functions/function_arg_type.rs | 29 +- src/errors/functions/function_n_args.rs | 36 +- src/errors/functions/function_name.rs | 12 +- src/errors/functions/function_overflow.rs | 22 +- src/errors/functions/stack.rs | 12 +- src/errors/internal.rs | 18 +- src/errors/syntax/unexpected_decorator.rs | 18 +- src/errors/syntax/unexpected_postfix.rs | 12 +- src/errors/syntax/unterminated_array.rs | 12 +- src/errors/syntax/unterminated_linebreak.rs | 12 +- src/errors/syntax/unterminated_literal.rs | 12 +- src/errors/syntax/unterminated_object.rs | 12 +- src/errors/syntax/unterminated_paren.rs | 12 +- src/errors/values/constant_value.rs | 18 +- src/errors/values/object_key.rs | 12 +- src/errors/values/overflow.rs | 12 +- src/errors/values/parse_value.rs | 25 +- src/errors/values/parsing.rs | 18 +- src/errors/values/range.rs | 14 +- src/errors/values/underflow.rs | 12 +- src/errors/values/value_type.rs | 19 +- src/errors/values/variable_name.rs | 12 +- src/expected_types.rs | 69 + src/extensions.rs | 448 -- src/extensions/extension.rs | 366 ++ src/extensions/function.rs | 125 + src/extensions/js/extension.js | 48 + src/extensions/js/function.js | 65 + src/extensions/js/lavendeux.js | 58 + src/extensions/js/value.js | 117 + src/extensions/mod.rs | 7 + src/extensions/runtime.rs | 103 + src/extensions/table.rs | 133 + src/functions/builtins/api.rs | 177 +- src/functions/builtins/array.rs | 527 ++- src/functions/builtins/crypto.rs | 129 +- src/functions/builtins/dev.rs | 227 +- src/functions/builtins/math.rs | 479 +- src/functions/builtins/network.rs | 138 +- src/functions/builtins/str.rs | 17 +- src/functions/builtins/system.rs | 238 +- src/functions/builtins/trig.rs | 168 +- src/functions/function_argument.rs | 79 +- src/functions/function_definition.rs | 157 +- src/functions/function_table.rs | 64 +- src/{functions.rs => functions/mod.rs} | 49 +- src/handlers/bitwise.rs | 193 +- src/handlers/boolean.rs | 77 +- src/handlers/errors.rs | 69 +- src/handlers/functions.rs | 77 +- src/handlers/math.rs | 391 +- src/{handlers.rs => handlers/mod.rs} | 234 +- src/handlers/utils.rs | 319 +- src/handlers/values.rs | 403 +- src/help.rs | 47 +- src/lib.rs | 142 +- src/{network.rs => network/mod.rs} | 2 +- src/state.rs | 28 +- src/test.rs | 19 +- src/token.rs | 148 +- src/value.rs | 332 +- 91 files changed, 10842 insertions(+), 4221 deletions(-) create mode 100644 example_extensions/example.js create mode 100644 example_extensions/simple.js delete mode 100644 src/decorators.rs create mode 100644 src/decorators/currency.rs create mode 100644 src/decorators/mod.rs create mode 100644 src/decorators/numeric.rs create mode 100644 src/decorators/primitives.rs create mode 100644 src/decorators/string.rs create mode 100644 src/expected_types.rs delete mode 100644 src/extensions.rs create mode 100644 src/extensions/extension.rs create mode 100644 src/extensions/function.rs create mode 100644 src/extensions/js/extension.js create mode 100644 src/extensions/js/function.js create mode 100644 src/extensions/js/lavendeux.js create mode 100644 src/extensions/js/value.js create mode 100644 src/extensions/mod.rs create mode 100644 src/extensions/runtime.rs create mode 100644 src/extensions/table.rs rename src/{functions.rs => functions/mod.rs} (51%) rename src/{handlers.rs => handlers/mod.rs} (51%) rename src/{network.rs => network/mod.rs} (61%) diff --git a/Cargo.lock b/Cargo.lock index bed9373..c1b1bc1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,16 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" +dependencies = [ + "lazy_static", + "regex", +] + [[package]] name = "addr2line" version = "0.21.0" @@ -19,13 +29,19 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aho-corasick" -version = "1.0.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + [[package]] name = "android_system_properties" version = "0.1.5" @@ -36,29 +52,70 @@ dependencies = [ ] [[package]] -name = "ansi_term" -version = "0.12.1" +name = "anstream" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" dependencies = [ - "winapi", + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] +name = "anstyle-parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +dependencies = [ + "anstyle", + "windows-sys", ] [[package]] name = "anyhow" -version = "1.0.70" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] -name = "atty" -version = "0.2.14" +name = "ast_node" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +checksum = "c09c69dffe06d222d072c878c3afe86eee2179806f20503faec97250268b4c24" dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", + "pmutil", + "proc-macro2", + "quote", + "swc_macros_common", + "syn 2.0.38", ] [[package]] @@ -84,9 +141,24 @@ dependencies = [ [[package]] name = "base64" -version = "0.21.0" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" + +[[package]] +name = "better_scoped_tls" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" +checksum = "794edcc9b3fb07bb4aecaa11f093fd45663b4feadb782d68303a2268bc2701de" +dependencies = [ + "scoped-tls", +] [[package]] name = "bitflags" @@ -94,6 +166,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" + [[package]] name = "block-buffer" version = "0.10.4" @@ -105,36 +183,38 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.12.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "bytes" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "cargo-readme" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66dbfc9307f5b2429656e07533613cd3f26803fd2857fc33be22aa2711181d58" +checksum = "10217a8c6eb2ad03cacb4ef2e2839b69abcc7cefb37a83906b331418eb8f6f0a" dependencies = [ "clap", "lazy_static", "percent-encoding", "regex", "serde", - "serde_derive", "toml", ] [[package]] name = "cc" -version = "1.0.79" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] [[package]] name = "cfg-if" @@ -144,44 +224,64 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.24" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" dependencies = [ + "android-tzdata", "iana-time-zone", "js-sys", - "num-integer", "num-traits", - "time", "wasm-bindgen", - "winapi", + "windows-targets", ] [[package]] name = "clap" -version = "2.34.0" +version = "4.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45" dependencies = [ - "ansi_term", - "atty", - "bitflags", + "anstream", + "anstyle", + "clap_lex", "strsim", - "textwrap", - "unicode-width", - "vec_map", ] [[package]] -name = "codespan-reporting" -version = "0.11.1" +name = "clap_derive" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" dependencies = [ - "termcolor", - "unicode-width", + "heck", + "proc-macro2", + "quote", + "syn 2.0.38", ] +[[package]] +name = "clap_lex" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "convert_case" version = "0.4.0" @@ -206,9 +306,9 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "cpufeatures" -version = "0.2.7" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" dependencies = [ "libc", ] @@ -224,55 +324,40 @@ dependencies = [ ] [[package]] -name = "cxx" -version = "1.0.94" +name = "dashmap" +version = "5.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ - "cc", - "cxxbridge-flags", - "cxxbridge-macro", - "link-cplusplus", + "cfg-if", + "hashbrown 0.14.1", + "lock_api", + "once_cell", + "parking_lot_core", ] [[package]] -name = "cxx-build" -version = "1.0.94" +name = "data-encoding" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b" -dependencies = [ - "cc", - "codespan-reporting", - "once_cell", - "proc-macro2", - "quote", - "scratch", - "syn 2.0.33", -] +checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" [[package]] -name = "cxxbridge-flags" -version = "1.0.94" +name = "data-url" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb" +checksum = "41b319d1b62ffbd002e057f36bebd1f42b9f97927c9577461d855f3513c4289f" [[package]] -name = "cxxbridge-macro" -version = "1.0.94" +name = "debugid" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" +checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.33", + "serde", + "uuid", ] -[[package]] -name = "data-encoding" -version = "2.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb" - [[package]] name = "deno-proc-macro-rules" version = "0.3.2" @@ -281,7 +366,7 @@ checksum = "3c65c2ffdafc1564565200967edc4851c7b55422d3913466688907efd05ea26f" dependencies = [ "deno-proc-macro-rules-macros", "proc-macro2", - "syn 2.0.33", + "syn 2.0.38", ] [[package]] @@ -293,21 +378,66 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.33", + "syn 2.0.38", +] + +[[package]] +name = "deno_ast" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a8adb6aeb787db71d015d8e9f63f6e004eeb09c86babb4ded00878be18619b1" +dependencies = [ + "anyhow", + "base64 0.13.1", + "deno_media_type", + "dprint-swc-ext", + "serde", + "swc_atoms", + "swc_common", + "swc_config", + "swc_config_macro", + "swc_ecma_ast", + "swc_ecma_codegen", + "swc_ecma_codegen_macros", + "swc_ecma_loader", + "swc_ecma_parser", + "swc_ecma_transforms_base", + "swc_ecma_transforms_classes", + "swc_ecma_transforms_macros", + "swc_ecma_transforms_proposal", + "swc_ecma_transforms_react", + "swc_ecma_transforms_typescript", + "swc_ecma_utils", + "swc_ecma_visit", + "swc_eq_ignore_macros", + "swc_macros_common", + "swc_visit", + "swc_visit_macros", + "text_lines", + "url", +] + +[[package]] +name = "deno_console" +version = "0.121.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b72cbce2b28d1ede2669f4ddfbb2e68e8e360c4cfe9494686bec4ffd60aeee4b" +dependencies = [ + "deno_core", ] [[package]] name = "deno_core" -version = "0.209.0" +version = "0.222.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c48ff1f83aeeda4b8ed9c101b85380fd2f25a52268130546c610c8e412911d7b" +checksum = "b13c81b9ea8462680e7b77088a44fc36390bab3dbfa5a205a285e11b64e0919c" dependencies = [ "anyhow", "bytes", "deno_ops", "deno_unsync", "futures", - "indexmap 1.9.3", + "indexmap 2.0.2", "libc", "log", "once_cell", @@ -317,17 +447,28 @@ dependencies = [ "serde_json", "serde_v8", "smallvec", - "sourcemap", + "sourcemap 7.0.0", "tokio", "url", "v8", ] +[[package]] +name = "deno_media_type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a798670c20308e5770cc0775de821424ff9e85665b602928509c8c70430b3ee0" +dependencies = [ + "data-url", + "serde", + "url", +] + [[package]] name = "deno_ops" -version = "0.87.0" +version = "0.98.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "573a5ae66f76ce159525ab9007433e19d1a074e32c27b17a4753780d659d79fa" +checksum = "bf89da1a3e50ff7c89956495b53d9bcad29e1f1b3f3d2bc54cad7155f55419c4" dependencies = [ "deno-proc-macro-rules", "lazy-regex", @@ -339,19 +480,39 @@ dependencies = [ "regex", "strum", "strum_macros", - "syn 2.0.33", + "syn 2.0.38", "thiserror", ] [[package]] name = "deno_unsync" -version = "0.1.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac0984205f25e71ddd1be603d76e70255953c12ff864707359ab195d26dfc7b3" +checksum = "f8a8f3722afd50e566ecfc783cc8a3a046bc4dd5eb45007431dfb2776aeb8993" dependencies = [ "tokio", ] +[[package]] +name = "deno_url" +version = "0.121.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1511f888bd2a7c458c31536f5a13403202b5443ed01eeab8b6eb5b5eaff29e2" +dependencies = [ + "deno_core", + "serde", + "urlpattern", +] + +[[package]] +name = "deno_webidl" +version = "0.121.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1358d0bd1ebe7286e7beb0f81848ff1478a08ead6191d6b2ad16bf9b6d712b3" +dependencies = [ + "deno_core", +] + [[package]] name = "derive_more" version = "0.99.17" @@ -367,25 +528,41 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", ] +[[package]] +name = "dprint-swc-ext" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a0a2492465344a58a37ae119de59e81fe5a2885f2711c7b5048ef0dfa14ce42" +dependencies = [ + "bumpalo", + "num-bigint", + "rustc-hash", + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "swc_ecma_parser", + "text_lines", +] + [[package]] name = "either" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "encoding_rs" -version = "0.8.32" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" dependencies = [ "cfg-if", ] @@ -398,33 +575,19 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" -dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" dependencies = [ - "cc", "libc", + "windows-sys", ] [[package]] name = "fastrand" -version = "1.9.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "fnv" @@ -449,13 +612,25 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" dependencies = [ "percent-encoding", ] +[[package]] +name = "from_variant" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03ec5dc38ee19078d84a692b1c41181ff9f94331c76cee66ff0208c770b5e54f" +dependencies = [ + "pmutil", + "proc-macro2", + "swc_macros_common", + "syn 2.0.38", +] + [[package]] name = "fslock" version = "0.1.8" @@ -522,7 +697,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.33", + "syn 2.0.38", ] [[package]] @@ -567,13 +742,13 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", ] [[package]] @@ -584,9 +759,9 @@ checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" [[package]] name = "h2" -version = "0.3.18" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f8a914c2987b688368b5138aa05321db91f4090cf26118185672ad588bce21" +checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" dependencies = [ "bytes", "fnv", @@ -609,9 +784,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.14.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" [[package]] name = "heck" @@ -621,28 +796,19 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.1.19" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" [[package]] -name = "hermit-abi" -version = "0.2.6" +name = "home" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" dependencies = [ - "libc", + "windows-sys", ] -[[package]] -name = "hermit-abi" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" - [[package]] name = "http" version = "0.2.9" @@ -673,15 +839,15 @@ checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "0.14.26" +version = "0.14.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" dependencies = [ "bytes", "futures-channel", @@ -716,9 +882,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.56" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" +checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -730,19 +896,18 @@ dependencies = [ [[package]] name = "iana-time-zone-haiku" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ - "cxx", - "cxx-build", + "cc", ] [[package]] name = "idna" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -766,87 +931,73 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.0.0" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" dependencies = [ "equivalent", - "hashbrown 0.14.0", + "hashbrown 0.14.1", ] [[package]] -name = "instant" -version = "0.1.12" +name = "ipnet" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] +checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" [[package]] -name = "io-lifetimes" -version = "1.0.10" +name = "is-macro" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" +checksum = "f4467ed1321b310c2625c5aa6c1b1ffc5de4d9e42668cf697a08fb033ee8265e" dependencies = [ - "hermit-abi 0.3.1", - "libc", - "windows-sys 0.48.0", + "Inflector", + "pmutil", + "proc-macro2", + "quote", + "syn 2.0.38", ] -[[package]] -name = "ipnet" -version = "2.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" - [[package]] name = "itoa" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" - -[[package]] -name = "js-sandbox" -version = "0.2.0-rc.2" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d275c828474dc2a7ac450b18ee84421e3c09281beb411ae0ef8b073b307057fb" -dependencies = [ - "deno_core", - "js-sandbox-macros", - "serde", - "serde_json", -] +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] -name = "js-sandbox-macros" -version = "0.2.0-rc.2" +name = "js-sys" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eca024fdf3ab1f7241854340ca265e5810d1eafb122672369c1962e6c3b9fb2" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.33", + "wasm-bindgen", ] [[package]] -name = "js-sys" -version = "0.3.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +name = "js_playground" +version = "0.5.0" +source = "git+https://github.com/rscarson/js-playground.git#c01337441b4bfb3dc3f5ae897e0adbbe0a6d1589" dependencies = [ - "wasm-bindgen", + "deno_ast", + "deno_console", + "deno_core", + "deno_url", + "deno_webidl", + "serde", + "thiserror", + "tokio", ] [[package]] name = "lavendeux-parser" version = "0.9.0" dependencies = [ - "base64", + "base64 0.21.4", "cargo-readme", "chrono", - "js-sandbox", + "js_playground", "md-5", + "once_cell", "pest", "pest_derive", "rand", @@ -855,15 +1006,16 @@ dependencies = [ "serde", "serde_json", "sha2", + "thiserror", "urlencoding", "version-sync", ] [[package]] name = "lazy-regex" -version = "2.5.0" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff63c423c68ea6814b7da9e88ce585f793c87ddd9e78f646970891769c8235d4" +checksum = "e723bd417b2df60a0f6a2b6825f297ea04b245d4ba52b5a22cb679bdf58b05fa" dependencies = [ "lazy-regex-proc_macros", "once_cell", @@ -872,14 +1024,14 @@ dependencies = [ [[package]] name = "lazy-regex-proc_macros" -version = "2.4.1" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8edfc11b8f56ce85e207e62ea21557cfa09bb24a8f6b04ae181b086ff8611c22" +checksum = "0f0a1d9139f0ee2e862e08a9c5d0ba0470f2aa21cd1e1aa1b1562f83116c725f" dependencies = [ "proc-macro2", "quote", "regex", - "syn 1.0.109", + "syn 2.0.38", ] [[package]] @@ -890,30 +1042,21 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.148" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" - -[[package]] -name = "link-cplusplus" -version = "1.0.8" +version = "0.2.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" -dependencies = [ - "cc", -] +checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" [[package]] name = "linux-raw-sys" -version = "0.3.3" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b085a4f2cde5781fc4b1717f2e86c62f5cda49de7ba99a7c2eae02b61c9064c" +checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" [[package]] name = "lock_api" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ "autocfg", "scopeguard", @@ -921,27 +1064,25 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "md-5" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6365506850d44bff6e2fbcb5176cf63650e48bd45ef2fe2665ae1570e0f4b9ca" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ + "cfg-if", "digest", ] [[package]] name = "memchr" -version = "2.6.3" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "mime" @@ -960,14 +1101,13 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", - "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.45.0", + "wasi", + "windows-sys", ] [[package]] @@ -988,16 +1128,23 @@ dependencies = [ "tempfile", ] +[[package]] +name = "new_debug_unreachable" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" + [[package]] name = "num-bigint" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" dependencies = [ "autocfg", "num-integer", "num-traits", "rand", + "serde", ] [[package]] @@ -1012,20 +1159,20 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] [[package]] name = "num_cpus" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.2.6", + "hermit-abi", "libc", ] @@ -1040,17 +1187,17 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.1" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "openssl" -version = "0.10.51" +version = "0.10.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97ea2d98598bf9ada7ea6ee8a30fb74f9156b63bbe495d64ec2b87c269d2dda3" +checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c" dependencies = [ - "bitflags", + "bitflags 2.4.0", "cfg-if", "foreign-types", "libc", @@ -1067,7 +1214,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.33", + "syn 2.0.38", ] [[package]] @@ -1078,9 +1225,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.86" +version = "0.9.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "992bac49bdbab4423199c654a5515bd2a6c6a23bf03f2dd3bdb7e5ae6259bc69" +checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d" dependencies = [ "cc", "libc", @@ -1100,28 +1247,34 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.2.16", + "redox_syscall", "smallvec", - "windows-sys 0.45.0", + "windows-targets", ] +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + [[package]] name = "percent-encoding" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pest" -version = "2.7.3" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a4d085fd991ac8d5b05a147b437791b4260b76326baf0fc60cf7c9c27ecd33" +checksum = "c022f1e7b65d6a24c0dbbd5fb344c66881bc01f3e5ae74a1c8100f2f985d98a4" dependencies = [ "memchr", "thiserror", @@ -1130,9 +1283,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.3" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bee7be22ce7918f641a33f08e3f43388c7656772244e2bbb2477f44cc9021a" +checksum = "35513f630d46400a977c4cb58f78e1bfbe01434316e60c37d27b9ad6139c66d8" dependencies = [ "pest", "pest_generator", @@ -1140,22 +1293,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.3" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1511785c5e98d79a05e8a6bc34b4ac2168a0e3e92161862030ad84daa223141" +checksum = "bc9fc1b9e7057baba189b5c626e2d6f40681ae5b6eb064dc7c7834101ec8123a" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.33", + "syn 2.0.38", ] [[package]] name = "pest_meta" -version = "2.7.3" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42f0394d3123e33353ca5e1e89092e533d2cc490389f2bd6131c43c634ebc5f" +checksum = "1df74e9e7ec4053ceb980e7c0c8bd3594e977fde1af91daba9c928e8e8c6708d" dependencies = [ "once_cell", "pest", @@ -1163,25 +1316,69 @@ dependencies = [ ] [[package]] -name = "pin-project" -version = "1.0.12" +name = "phf" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" dependencies = [ - "pin-project-internal", + "phf_macros", + "phf_shared", + "proc-macro-hack", ] [[package]] -name = "pin-project-internal" -version = "1.0.12" +name = "phf_generator" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" dependencies = [ - "proc-macro2", + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro-hack", + "proc-macro2", "quote", "syn 1.0.109", ] +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + [[package]] name = "pin-project-lite" version = "0.2.13" @@ -1196,9 +1393,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "pmutil" @@ -1208,7 +1405,7 @@ checksum = "52a40bc70c2c58040d2d8b167ba9a5ff59fc9dab7ad44771cfde3dcfde7a09c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.33", + "syn 2.0.38", ] [[package]] @@ -1217,6 +1414,12 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + [[package]] name = "proc-macro-crate" version = "1.3.1" @@ -1227,22 +1430,37 @@ dependencies = [ "toml_edit", ] +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + [[package]] name = "proc-macro2" -version = "1.0.67" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] +[[package]] +name = "psm" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" +dependencies = [ + "cc", +] + [[package]] name = "pulldown-cmark" -version = "0.8.0" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffade02495f22453cd593159ea2f59827aae7f53fa8323f756799b670881dcf8" +checksum = "77a1a2f1f0a7ecff9c31abbe177637be0e97a0aef46cf8738ece09327985d998" dependencies = [ - "bitflags", + "bitflags 1.3.2", "memchr", "unicase", ] @@ -1286,29 +1504,20 @@ dependencies = [ "getrandom", ] -[[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags", -] - [[package]] name = "redox_syscall" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] name = "regex" -version = "1.9.4" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29" +checksum = "d119d7c7ca818f8a53c300863d4f87566aac09943aef5b355bb83969dae75d87" dependencies = [ "aho-corasick", "memchr", @@ -1318,9 +1527,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.8" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" +checksum = "465c6fc0621e4abc4187a2bda0937bfd4f722c2730b29562e19689ea796c9a4b" dependencies = [ "aho-corasick", "memchr", @@ -1329,17 +1538,17 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.5" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" +checksum = "56d84fdd47036b038fc80dd333d10b6aab10d5d31f4a366e20014def75328d33" [[package]] name = "reqwest" -version = "0.11.20" +version = "0.11.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1" +checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" dependencies = [ - "base64", + "base64 0.21.4", "bytes", "encoding_rs", "futures-core", @@ -1360,6 +1569,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", + "system-configuration", "tokio", "tokio-native-tls", "tower-service", @@ -1376,6 +1586,12 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc_version" version = "0.2.3" @@ -1391,21 +1607,20 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.17", + "semver 1.0.20", ] [[package]] name = "rustix" -version = "0.37.13" +version = "0.38.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f79bef90eb6d984c72722595b5b1348ab39275a5e5123faca6863bf07d75a4e0" +checksum = "745ecfa778e66b2b63c88a61cb36e0eea109e803b0b86bf9879fbc77c70e86ed" dependencies = [ - "bitflags", + "bitflags 2.4.0", "errno", - "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -1416,38 +1631,38 @@ checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "ryu" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "schannel" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" dependencies = [ - "windows-sys 0.42.0", + "windows-sys", ] [[package]] -name = "scopeguard" -version = "1.1.0" +name = "scoped-tls" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] -name = "scratch" -version = "1.0.5" +name = "scopeguard" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" -version = "2.8.2" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" dependencies = [ - "bitflags", + "bitflags 1.3.2", "core-foundation", "core-foundation-sys", "libc", @@ -1456,9 +1671,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.8.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" dependencies = [ "core-foundation-sys", "libc", @@ -1475,9 +1690,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.17" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" +checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" [[package]] name = "semver-parser" @@ -1487,31 +1702,31 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.188" +version = "1.0.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537" dependencies = [ "serde_derive", ] [[package]] name = "serde_bytes" -version = "0.11.9" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416bda436f9aab92e02c8e10d49a15ddd339cea90b6e340fe51ed97abb548294" +checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.188" +version = "1.0.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.33", + "syn 2.0.38", ] [[package]] @@ -1520,12 +1735,21 @@ version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" dependencies = [ - "indexmap 2.0.0", + "indexmap 2.0.2", "itoa", "ryu", "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -1540,9 +1764,9 @@ dependencies = [ [[package]] name = "serde_v8" -version = "0.120.0" +version = "0.131.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5424b4b41a92222abf9ddbdd78f59164f7594422ee4a61fc3704fc8ba608dc6" +checksum = "38cafa16d0a4288d75925351bb54d06d2e830118ad3fad393947bb11f91b18f3" dependencies = [ "bytes", "derive_more", @@ -1554,11 +1778,22 @@ dependencies = [ "v8", ] +[[package]] +name = "sha-1" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sha2" -version = "0.10.6" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", @@ -1574,20 +1809,37 @@ dependencies = [ "libc", ] +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "slab" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "smallvec" -version = "1.10.0" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" + +[[package]] +name = "smartstring" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29" +dependencies = [ + "autocfg", + "static_assertions", + "version_check", +] [[package]] name = "socket2" @@ -1606,16 +1858,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] name = "sourcemap" -version = "6.2.3" +version = "6.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed16231c92d0a6f0388f56e0ab2be24ecff1173f8e22f0ea5e074d0525631cb" +checksum = "e4cbf65ca7dc576cf50e21f8d0712d96d4fcfd797389744b7b222a85cdf5bd90" dependencies = [ "data-encoding", + "debugid", "if_chain", "rustc_version 0.2.3", "serde", @@ -1624,11 +1877,91 @@ dependencies = [ "url", ] +[[package]] +name = "sourcemap" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbecc42a2b6131acc3bf9a25c9fe4161dba438eb52131bba83c5d781b5b70be3" +dependencies = [ + "data-encoding", + "debugid", + "if_chain", + "rustc_version 0.2.3", + "serde", + "serde_json", + "unicode-id", + "url", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "stacker" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c886bd4480155fd3ef527d45e9ac8dd7118a898a46530b7b94c3e21866259fce" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "winapi", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "string_cache" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" +dependencies = [ + "new_debug_unreachable", + "once_cell", + "parking_lot", + "phf_shared", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", +] + +[[package]] +name = "string_enum" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fa4d4f81d7c05b9161f8de839975d3326328b8ba2831164b465524cc2f55252" +dependencies = [ + "pmutil", + "proc-macro2", + "quote", + "swc_macros_common", + "syn 2.0.38", +] + [[package]] name = "strsim" -version = "0.8.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "strum" @@ -1649,7 +1982,345 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.33", + "syn 2.0.38", +] + +[[package]] +name = "swc_atoms" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f54563d7dcba626d4acfe14ed12def7ecc28e004debe3ecd2c3ee07cc47e449" +dependencies = [ + "once_cell", + "rustc-hash", + "serde", + "string_cache", + "string_cache_codegen", + "triomphe", +] + +[[package]] +name = "swc_common" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cb7fcd56655c8ae7dcf2344f0be6cbff4d9c7cb401fe3ec8e56e1de8dfe582" +dependencies = [ + "ast_node", + "better_scoped_tls", + "cfg-if", + "either", + "from_variant", + "new_debug_unreachable", + "num-bigint", + "once_cell", + "rustc-hash", + "serde", + "siphasher", + "sourcemap 6.4.1", + "string_cache", + "swc_atoms", + "swc_eq_ignore_macros", + "swc_visit", + "tracing", + "unicode-width", + "url", +] + +[[package]] +name = "swc_config" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ba1c7a40d38f9dd4e9a046975d3faf95af42937b34b2b963be4d8f01239584b" +dependencies = [ + "indexmap 1.9.3", + "serde", + "serde_json", + "swc_config_macro", +] + +[[package]] +name = "swc_config_macro" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5b5aaca9a0082be4515f0fbbecc191bf5829cd25b5b9c0a2810f6a2bb0d6829" +dependencies = [ + "pmutil", + "proc-macro2", + "quote", + "swc_macros_common", + "syn 2.0.38", +] + +[[package]] +name = "swc_ecma_ast" +version = "0.109.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bc2286cedd688a68f214faa1c19bb5cceab7c9c54d0cbe3273e4c1704e38f69" +dependencies = [ + "bitflags 2.4.0", + "is-macro", + "num-bigint", + "scoped-tls", + "serde", + "string_enum", + "swc_atoms", + "swc_common", + "unicode-id", +] + +[[package]] +name = "swc_ecma_codegen" +version = "0.144.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e62ba2c0ed1f119fc1a76542d007f1b2c12854d54dea15f5491363227debe11" +dependencies = [ + "memchr", + "num-bigint", + "once_cell", + "rustc-hash", + "serde", + "sourcemap 6.4.1", + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "swc_ecma_codegen_macros", + "tracing", +] + +[[package]] +name = "swc_ecma_codegen_macros" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcdff076dccca6cc6a0e0b2a2c8acfb066014382bc6df98ec99e755484814384" +dependencies = [ + "pmutil", + "proc-macro2", + "quote", + "swc_macros_common", + "syn 2.0.38", +] + +[[package]] +name = "swc_ecma_loader" +version = "0.44.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7d7c322462657ae27ac090a2c89f7e456c94416284a2f5ecf66c43a6a3c19d1" +dependencies = [ + "anyhow", + "pathdiff", + "serde", + "swc_common", + "tracing", +] + +[[package]] +name = "swc_ecma_parser" +version = "0.139.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eab46cb863bc5cd61535464e07e5b74d5f792fa26a27b9f6fd4c8daca9903b7" +dependencies = [ + "either", + "num-bigint", + "num-traits", + "serde", + "smallvec", + "smartstring", + "stacker", + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "tracing", + "typed-arena", +] + +[[package]] +name = "swc_ecma_transforms_base" +version = "0.132.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01ffd4a8149052bfc1ec1832fcbe04f317846ce635a49ec438df33b06db27d26" +dependencies = [ + "better_scoped_tls", + "bitflags 2.4.0", + "indexmap 1.9.3", + "once_cell", + "phf", + "rustc-hash", + "serde", + "smallvec", + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "swc_ecma_parser", + "swc_ecma_utils", + "swc_ecma_visit", + "tracing", +] + +[[package]] +name = "swc_ecma_transforms_classes" +version = "0.121.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4b7fee0e2c6f12456d2aefb2418f2f26529b995945d493e1dce35a5a22584fc" +dependencies = [ + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "swc_ecma_transforms_base", + "swc_ecma_utils", + "swc_ecma_visit", +] + +[[package]] +name = "swc_ecma_transforms_macros" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8188eab297da773836ef5cf2af03ee5cca7a563e1be4b146f8141452c28cc690" +dependencies = [ + "pmutil", + "proc-macro2", + "quote", + "swc_macros_common", + "syn 2.0.38", +] + +[[package]] +name = "swc_ecma_transforms_proposal" +version = "0.166.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "122fd9a69f464694edefbf9c59106b3c15e5cc8cb8575a97836e4fb79018e98f" +dependencies = [ + "either", + "rustc-hash", + "serde", + "smallvec", + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "swc_ecma_transforms_base", + "swc_ecma_transforms_classes", + "swc_ecma_transforms_macros", + "swc_ecma_utils", + "swc_ecma_visit", +] + +[[package]] +name = "swc_ecma_transforms_react" +version = "0.178.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "675b5c755b0448268830e85e59429095d3423c0ce4a850b209c6f0eeab069f63" +dependencies = [ + "base64 0.13.1", + "dashmap", + "indexmap 1.9.3", + "once_cell", + "serde", + "sha-1", + "string_enum", + "swc_atoms", + "swc_common", + "swc_config", + "swc_ecma_ast", + "swc_ecma_parser", + "swc_ecma_transforms_base", + "swc_ecma_transforms_macros", + "swc_ecma_utils", + "swc_ecma_visit", +] + +[[package]] +name = "swc_ecma_transforms_typescript" +version = "0.182.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eba97b1ea71739fcf278aedad4677a3cacb52288a3f3566191b70d16a889de6" +dependencies = [ + "serde", + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "swc_ecma_transforms_base", + "swc_ecma_transforms_react", + "swc_ecma_utils", + "swc_ecma_visit", +] + +[[package]] +name = "swc_ecma_utils" +version = "0.122.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11006a3398ffd4693c4d3b0a1b1a5030edbdc04228159f5301120a6178144708" +dependencies = [ + "indexmap 1.9.3", + "num_cpus", + "once_cell", + "rustc-hash", + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "swc_ecma_visit", + "tracing", + "unicode-id", +] + +[[package]] +name = "swc_ecma_visit" +version = "0.95.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f628ec196e76e67892441e14eef2e423a738543d32bffdabfeec20c29582117" +dependencies = [ + "num-bigint", + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "swc_visit", + "tracing", +] + +[[package]] +name = "swc_eq_ignore_macros" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05a95d367e228d52484c53336991fdcf47b6b553ef835d9159db4ba40efb0ee8" +dependencies = [ + "pmutil", + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "swc_macros_common" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a273205ccb09b51fabe88c49f3b34c5a4631c4c00a16ae20e03111d6a42e832" +dependencies = [ + "pmutil", + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "swc_visit" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e87c337fbb2d191bf371173dea6a957f01899adb8f189c6c31b122a6cfc98fc3" +dependencies = [ + "either", + "swc_visit_macros", +] + +[[package]] +name = "swc_visit_macros" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f322730fb82f3930a450ac24de8c98523af7d34ab8cb2f46bcb405839891a99" +dependencies = [ + "Inflector", + "pmutil", + "proc-macro2", + "quote", + "swc_macros_common", + "syn 2.0.38", ] [[package]] @@ -1665,9 +2336,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.33" +version = "2.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9caece70c63bfba29ec2fed841a09851b14a235c60010fa4de58089b6c025668" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" dependencies = [ "proc-macro2", "quote", @@ -1675,65 +2346,66 @@ dependencies = [ ] [[package]] -name = "tempfile" -version = "3.5.0" +name = "system-configuration" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" dependencies = [ - "cfg-if", - "fastrand", - "redox_syscall 0.3.5", - "rustix", - "windows-sys 0.45.0", + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", ] [[package]] -name = "termcolor" -version = "1.2.0" +name = "system-configuration-sys" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" dependencies = [ - "winapi-util", + "core-foundation-sys", + "libc", ] [[package]] -name = "textwrap" -version = "0.11.0" +name = "tempfile" +version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" dependencies = [ - "unicode-width", + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys", ] [[package]] -name = "thiserror" -version = "1.0.40" +name = "text_lines" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +checksum = "7fd5828de7deaa782e1dd713006ae96b3bee32d3279b79eb67ecf8072c059bcf" dependencies = [ - "thiserror-impl", + "serde", ] [[package]] -name = "thiserror-impl" -version = "1.0.40" +name = "thiserror" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.33", + "thiserror-impl", ] [[package]] -name = "time" -version = "0.1.45" +name = "thiserror-impl" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", + "proc-macro2", + "quote", + "syn 2.0.38", ] [[package]] @@ -1753,9 +2425,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.32.0" +version = "1.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" +checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653" dependencies = [ "backtrace", "bytes", @@ -1767,7 +2439,7 @@ dependencies = [ "signal-hook-registry", "socket2 0.5.4", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -1778,7 +2450,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.33", + "syn 2.0.38", ] [[package]] @@ -1793,9 +2465,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.7" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" +checksum = "1d68074620f57a0b21594d9735eb2e98ab38b17f80d3fcb189fca266771ca60d" dependencies = [ "bytes", "futures-core", @@ -1807,26 +2479,34 @@ dependencies = [ [[package]] name = "toml" -version = "0.5.11" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" dependencies = [ "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", ] [[package]] name = "toml_datetime" -version = "0.6.1" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] [[package]] name = "toml_edit" -version = "0.19.8" +version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 1.9.3", + "indexmap 2.0.2", + "serde", + "serde_spanned", "toml_datetime", "winnow", ] @@ -1845,41 +2525,110 @@ checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + [[package]] name = "tracing-core" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" dependencies = [ "once_cell", ] +[[package]] +name = "triomphe" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee8098afad3fb0c54a9007aab6804558410503ad676d4633f9c2559a00ac0f" +dependencies = [ + "serde", + "stable_deref_trait", +] + [[package]] name = "try-lock" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +[[package]] +name = "typed-arena" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" + [[package]] name = "typenum" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "ucd-trie" -version = "0.1.5" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-ucd-ident" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] [[package]] name = "unicase" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" dependencies = [ "version_check", ] @@ -1892,15 +2641,15 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-id" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d70b6494226b36008c8366c288d77190b3fad2eb4c10533139c1c1f461127f1a" +checksum = "b1b6def86329695390197b82c1e244a54a131ceb66c996f2088a3876e2ae083f" [[package]] name = "unicode-ident" -version = "1.0.8" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" @@ -1913,15 +2662,15 @@ dependencies = [ [[package]] name = "unicode-width" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] name = "url" -version = "2.3.1" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" dependencies = [ "form_urlencoded", "idna", @@ -1931,17 +2680,42 @@ dependencies = [ [[package]] name = "urlencoding" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8db7427f936968176eaa7cdf81b7f98b980b18495ec28f1b5791ac3bfe3eea9" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "urlpattern" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9bd5ff03aea02fa45b13a7980151fe45009af1980ba69f651ec367121a31609" +dependencies = [ + "derive_more", + "regex", + "serde", + "unic-ucd-ident", + "url", +] + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "uuid" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" [[package]] name = "v8" -version = "0.75.1" +version = "0.79.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0e0cb10989bf856c2fdd1b6bed1bc6f96148230aa0c954634299125c1f64230" +checksum = "b15561535230812a1db89a696f1f16a12ae6c2c370c6b2241c68d4cb33963faf" dependencies = [ - "bitflags", + "bitflags 1.3.2", "fslock", "once_cell", "which", @@ -1953,23 +2727,17 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" -[[package]] -name = "vec_map" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" - [[package]] name = "version-sync" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d0801cec07737d88cb900e6419f6f68733867f90b3faaa837e84692e101bf0" +checksum = "835169da0173ea373ddf5987632aac1f918967fbbe58195e304342282efa6089" dependencies = [ "proc-macro2", "pulldown-cmark", "regex", - "semver 1.0.17", - "syn 1.0.109", + "semver 1.0.20", + "syn 2.0.38", "toml", "url", ] @@ -1982,20 +2750,13 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "want" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ - "log", "try-lock", ] -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -2004,9 +2765,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2014,24 +2775,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.38", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.34" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" dependencies = [ "cfg-if", "js-sys", @@ -2041,9 +2802,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2051,28 +2812,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.38", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "web-sys" -version = "0.3.61" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ "js-sys", "wasm-bindgen", @@ -2080,13 +2841,14 @@ dependencies = [ [[package]] name = "which" -version = "4.4.0" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" dependencies = [ "either", - "libc", + "home", "once_cell", + "rustix", ] [[package]] @@ -2105,15 +2867,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -2126,31 +2879,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows-targets 0.48.0", -] - -[[package]] -name = "windows-sys" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", + "windows-targets", ] [[package]] @@ -2159,128 +2888,71 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.48.0", + "windows-targets", ] [[package]] name = "windows-targets" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-targets" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" -dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_msvc" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_i686_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_x86_64_gnu" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_msvc" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "winnow" -version = "0.4.1" +version = "0.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28" +checksum = "037711d82167854aff2018dfd193aa0fef5370f456732f0d5a0c59b0f1b4b907" dependencies = [ "memchr", ] @@ -2292,5 +2964,5 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ "cfg-if", - "windows-sys 0.48.0", + "windows-sys", ] diff --git a/Cargo.toml b/Cargo.toml index 36dea6d..5fc9516 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,11 +12,13 @@ edition = "2021" [features] default = ["extensions", "crypto-functions", "encoding-functions"] -extensions = ["js-sandbox"] +extensions = ["js_playground"] crypto-functions = ["md-5", "sha2"] encoding-functions = ["base64", "urlencoding"] [dependencies] +once_cell = "1.18.0" +thiserror = "1.0.40" regex = "1.9.4" pest = "2.5.6" pest_derive = "2.7.3" @@ -27,7 +29,7 @@ chrono = "0.4.23" rand = "0.8.5" # Feature deps -js-sandbox = { version = "0.2.0-rc.2", optional = true } +js_playground = { git = "https://github.com/rscarson/js-playground.git", optional = true } md-5 = { version = "0.10.5", optional = true } sha2 = { version = "0.10.6", optional = true } base64 = { version = "0.21.0", optional = true } diff --git a/README.md b/README.md index 7459134..fa5918d 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,9 @@ This project is the engine behind [Lavendeux](https://rscarson.github.io/lavende ### Getting Started To use it, create a `ParserState` object, and use it to tokenize input with `Token::new`: ```rust -use lavendeux_parser::{ParserState, ParserError, Token, Value}; +use lavendeux_parser::{ParserState, Error, Token, Value}; -fn main() -> Result<(), ParserError> { +fn main() -> Result<(), Error> { // Create a new parser, and tokenize 2 lines let mut state : ParserState = ParserState::new(); let lines = Token::new("x=9\nsqrt(x) @bin", &mut state)?; @@ -32,9 +32,9 @@ fn main() -> Result<(), ParserError> { ``` The result will be a `Token` object: ```rust -use lavendeux_parser::{ParserState, ParserError, Token, Value}; +use lavendeux_parser::{ParserState, Error, Token, Value}; -fn main() -> Result<(), ParserError> { +fn main() -> Result<(), Error> { let mut state : ParserState = ParserState::new(); let lines = Token::new("x=9\nsqrt(x) @bin", &mut state)?; @@ -56,8 +56,8 @@ fn main() -> Result<(), ParserError> { A number of functions and @decorators are available for expressions to use - add more using the state: ```rust -use lavendeux_parser::{ParserState, ParserError, DecoratorDefinition, FunctionDefinition, FunctionArgument, Value}; -use lavendeux_parser::errors::*; +use lavendeux_parser::{ParserState, Error, DecoratorDefinition, FunctionDefinition, FunctionArgument, Value}; +use lavendeux_parser::Error; let mut state : ParserState = ParserState::new(); state.decorators.register(DecoratorDefinition { @@ -151,9 +151,9 @@ Extensions are enabled by default, and can be excluded by disabling the crate's Extensions can be loaded as follows: ```rust -use lavendeux_parser::{ParserState, ParserError, Value, Token}; +use lavendeux_parser::{ParserState, Error, Value, Token}; -fn main() -> Result<(), ParserError> { +fn main() -> Result<(), Error> { let mut state : ParserState = ParserState::new(); // Load one extension diff --git a/example_extensions/colour_utils.js b/example_extensions/colour_utils.js index eb5077d..17a4737 100644 --- a/example_extensions/colour_utils.js +++ b/example_extensions/colour_utils.js @@ -3,7 +3,7 @@ * It must return an object similar to the one below. * @returns Object */ -function extension() { +export function extension() { return { name: "HTML Colour Utilities", author: "@rscarson", @@ -31,7 +31,7 @@ function extension() { * @param {Value} args * @returns {Value} result */ -function function_colour(args) { +export function function_colour(args) { if (args.length != 1) { throw "color(s): expected 1 argument"; } else if (!args[0].String) { @@ -60,7 +60,7 @@ function function_colour(args) { * @param {Value} args * @returns {Value} result */ -function function_complement(args) { +export function function_complement(args) { if (args.length != 1) { throw "complement(n): expected 1 argument"; } else if (!args[0].Integer) { @@ -89,7 +89,7 @@ function function_complement(args) { * @param {Value} args * @returns {String} result */ -function decorator_colour(value) { +export function decorator_colour(value) { if (value.Integer) { return '#'+value.Integer.toString(16).padEnd(6, '0'); } @@ -97,7 +97,7 @@ function decorator_colour(value) { throw "@color: expected an integer value"; } -function extract_rgb(int_val) { +export function extract_rgb(int_val) { return { "b": int_val & 0xFF, "g": (int_val >> 8) & 0xFF, diff --git a/example_extensions/example.js b/example_extensions/example.js new file mode 100644 index 0000000..3e87280 --- /dev/null +++ b/example_extensions/example.js @@ -0,0 +1,37 @@ +"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const Types={Float:"Float",Integer:"Integer",Numeric:"Numeric",String:"String",Boolean:"Boolean",Array:"Array",Object:"Object",Any:""};class LavendeuxValue{static typeOf(wrappedValue){let inType=Object.keys(wrappedValue);return inType.length?inType[0]:!1}static cooerce(value,targetType){switch(targetType){case"Integer":return Math.floor(Number(value));case"Numeric":case"Float":return Number(value);case"Boolean":return!!value;case"String":return typeof value=="object"?JSON.stringify(value):`${value}`;case"Array":return Array.isArray(value)?value:typeof value=="object"?Object.values(value):[value];case"Object":return typeof value=="object"?Object.assign({},value):{0:value};default:return value}}static unwrap(wrappedValue,targetType=Types.Any){let type=this.typeOf(wrappedValue),value=Object.values(wrappedValue)[0];switch(type){case"Object":value=value.map(([k,v])=>[this.unwrap(k,Types.String),this.unwrap(v)]),value=Object.fromEntries(value);break;case"Array":value=value.map(e=>this.unwrap(e));break}return LavendeuxValue.cooerce(value,targetType)}static wrap(value,targetType=Types.Any){if(value=this.cooerce(value,targetType),Array.isArray(value))return{Array:value.map(e=>this.wrap(e))};if(typeof value=="object"){let result=[];return Object.keys(value).forEach(k=>{result.push([this.wrap(k),this.wrap(value[k])])}),{Object:result}}else return typeof value=="string"||value instanceof String?{String:value}:Number.isInteger(value)?{Integer:value}:Number(value)===value?{Float:value}:{Boolean:value==!0}}}let WARNING_STATE_UNAVAILABLE,setState,getState;if(typeof getState>"u"){WARNING_STATE_UNAVAILABLE=!0;const _lav_state={};getState=()=>_lav_state,setState=s=>Object.assign(_lav_state,s)}class LavendeuxFunction{constructor(name,returnType,callback){this.name=name.replace("@",""),this.callback=callback,this.argumentTypes=[],this.returnType=returnType,this.registeredName=LavendeuxFunction.getRegisteredName(name)}static isStateAvailable(){return WARNING_STATE_UNAVAILABLE}static getRegisteredName(name){return`lavendeuxfunction_${name}`}addArgument(type=Types.Any){return this.argumentTypes.push(type),this}addIntegerArgument(){return this.addArgument(Types.Integer)}addFloatArgument(){return this.addArgument(Types.Float)}addNumericArgument(){return this.addArgument(Types.Numeric)}addStringArgument(){return this.addArgument(Types.String)}addBooleanArgument(){return this.addArgument(Types.Boolean)}addArrayArgument(){return this.addArgument(Types.Array)}addObjectArgument(){return this.addArgument(Types.Object)}decodeArguments(argv){if(argv.length{let _type=LavendeuxValue.typeOf(argv[i]);if(type==Types.Numeric&&![Types.Integer,Types.Float].includes(_type)||[Types.Integer,Types.Float].includes(type)&&type!=_type)throw new Error(`Invalid type for parameter ${i+1} of ${this.name}: Expected ${type}`)}),argv.map((wrappedValue,i)=>{let type=this.argumentTypes[i]?this.argumentTypes[i]:Types.Any;return LavendeuxValue.unwrap(wrappedValue,type)})}getState(){const state=getState();return Object.keys(state).map(k=>{state[k]=LavendeuxValue.unwrap(state[k])}),state}setState(state){Object.keys(state).map(k=>{state[k]=LavendeuxValue.wrap(state[k])}),setState(state)}call(argv){argv=this.decodeArguments(argv);let state=this.getState(),value=LavendeuxValue.wrap(this.callback(...argv,state),this.returnType);return this.setState(state),value}}class LavendeuxDecorator extends LavendeuxFunction{constructor(name,argumentType,callback){super(name,Types.String,callback),this.argumentTypes=[argumentType],this.registeredName=LavendeuxDecorator.getRegisteredName(name)}static getRegisteredName(name){return`lavendeuxdecorator_${name}`}call(arg){return LavendeuxValue.unwrap(super.call([arg]),Types.String)}}class Lavendeux{constructor(name,author,version){this.name=name,this.author=author,this.version=version,this.functions={},this.decorators={},this.allHandlers={}}static register(instance){globalThis.extension=()=>instance.definition(),globalThis.extensionInstance=instance,Object.values(instance.allHandlers).forEach(f=>{globalThis[f.registeredName]=argv=>f.call(argv)})}static registeredInstance(){return globalThis.extensionInstance}definition(){return{name:this.name,author:this.author,version:this.version,functions:this.functions,decorators:this.decorators}}getFunctionCallback(name){return this.allHandlers[this.functions[name]].callback}getDecoratorCallback(name){return this.allHandlers[this.decorators[name]].callback}addFunction(name,callback,expectedType=Types.Any){let f=new LavendeuxFunction(name,expectedType,callback);return this.allHandlers[f.registeredName]=f,this.functions[name]=f.registeredName,f}addIntegerFunction(name,callback){return this.addFunction(name,callback,Types.Integer)}addFloatFunction(name,callback){return this.addFunction(name,callback,Types.Float)}addNumericFunction(name,callback){return this.addFunction(name,callback,Types.Numeric)}addStringFunction(name,callback){return this.addFunction(name,callback,Types.String)}addBooleanFunction(name,callback){return this.addFunction(name,callback,Types.Boolean)}addArrayFunction(name,callback){return this.addFunction(name,callback,Types.Array)}addObjectFunction(name,callback){return this.addFunction(name,callback,Types.Object)}addDecorator(name,callback,expectedType=Types.Any){let f=new LavendeuxDecorator(name,expectedType,callback);return this.allHandlers[f.registeredName]=f,this.decorators[name]=f.registeredName,f}addIntegerDecorator(name,callback){return this.addDecorator(name,callback,Types.Integer)}addFloatDecorator(name,callback){return this.addDecorator(name,callback,Types.Float)}addNumericDecorator(name,callback){return this.addDecorator(name,callback,Types.Numeric)}addStringDecorator(name,callback){return this.addDecorator(name,callback,Types.String)}addBooleanDecorator(name,callback){return this.addDecorator(name,callback,Types.Boolean)}addArrayDecorator(name,callback){return this.addDecorator(name,callback,Types.Array)}addObjectDecorator(name,callback){return this.addDecorator(name,callback,Types.Object)}}exports.Lavendeux=Lavendeux; +/*! + * + * This file is an extension for the Lavendeux parser + * https://rscarson.github.io/lavendeux/ + * + * The contents below were autogenerated using the lavendeux npm package: + * https://www.npmjs.com/package/lavendeux + * + */ +const lavendeux = {"Lavendeux": Lavendeux}; +const name = "example_extension"; +const version = "1.0.0"; +const author = "@rscarson"; +let instance = new lavendeux.Lavendeux(name, author, version); +instance.addNumericDecorator("usd", (input) => { + let n = (Math.round(input * 100) / 100).toFixed(2); + return `$${n}`; +}); +instance.addFunction("add", (left, right) => { + return left + right; +}).addNumericArgument().addNumericArgument(); +lavendeux.Lavendeux.register(instance); + + + +const extension = lavendeux.extend({ + 'name': 'my_extension', + 'author': '@rscarson', + 'version': '1.0.0' +}); + +extension.addFunction("add", (left, right) => { + return left + right; +}) +.requireNumericArgument() +.requireNumericArgument(); diff --git a/example_extensions/simple.js b/example_extensions/simple.js new file mode 100644 index 0000000..aa2252c --- /dev/null +++ b/example_extensions/simple.js @@ -0,0 +1,12 @@ +let extension = lavendeux.extend({ + 'name': 'simple_extension', + 'author': '@rscarson', + 'version': '1.0.0' +}); + +extension.addFunction('add', (left, right) => left + right, 'Float') +.requireArgument('Integer').requireArgument('Integer'); + +extension.addDecorator('usd', (input) => `${input}`, 'Float') + +lavendeux.register(extension); \ No newline at end of file diff --git a/example_extensions/stateful_functions.js b/example_extensions/stateful_functions.js index 008f098..8a71b61 100644 --- a/example_extensions/stateful_functions.js +++ b/example_extensions/stateful_functions.js @@ -1,36 +1,19 @@ -/** - * This function tells Lavendeux about this extension. - * It must return an object similar to the one below. - * @returns Object - */ -function extension() { - return { - name: "Stateful function demo", - author: "@rscarson", - version: "0.2.0", +let extension = lavendeux.extend({ + 'name': 'stateful_extension', + 'author': '@rscarson', + 'version': '1.0.0' +}); - functions: { - "set": "functionSet" - }, - }; -} - -/** - * Functions can also be stateful, gaining access to the parser's variables - * @param {Value} args - * @returns {Value} result - */ -function functionSet(args) { - if (args.length != 2) { - throw new Error("set(, ): expected 2 arguments"); - } else if (!args[0].String) { - throw "set(, ): expected a string value"; - } - - let name = args[0].String, value = args[1]; - const state = getState(); +extension.addFunction('put', (name, value, state) => { state[name] = value; - setState(state); - return value; -} \ No newline at end of file +}) +.requireArgument('String') +.requireArgument(); + +extension.addFunction('get', (name, state) => { + return state[name]; +}) +.requireArgument('String'); + +lavendeux.register(extension); \ No newline at end of file diff --git a/example_extensions/zarbans_grotto.js b/example_extensions/zarbans_grotto.js index 8ff9402..066ad95 100644 --- a/example_extensions/zarbans_grotto.js +++ b/example_extensions/zarbans_grotto.js @@ -1,639 +1,4032 @@ /*! + * * This file is an extension for the Lavendeux parser * https://rscarson.github.io/lavendeux/ * * The contents below were autogenerated using the lavendeux npm package: * https://www.npmjs.com/package/lavendeux + * */ -class e { - constructor(e2 = {}) { - Object.assign(this, JSON.parse(JSON.stringify(e2))); - for (const e3 in this.records) - this.records[e3] = new t(this.records[e3]); - } +const Y = "zarbans_grotto", O = "1.0.0", E = "@rscarson"; +class N { + constructor(e = {}) { + Object.assign(this, JSON.parse(JSON.stringify( + e + ))); + for (const t in this.records) + this.records[t] = new C(this.records[t]); + } + /** + * List the available entries + * @returns Array + */ list() { return Object.keys(this.records); } + /** + * List the available entries + * @returns Array + */ list_chosen() { - return Object.keys(this.records).filter((e2) => this.chose(e2)); - } - choose(e2, t2) { - this.records[e2].enabled = t2; - } - chose(e2) { - return this.records[e2].enabled; + return Object.keys(this.records).filter((e) => this.chose(e)); + } + /** + * Make a choice + * @param {String} choice + */ + choose(e, t) { + this.records[e].enabled = t; + } + /** + * Returns true if the player chose the given path + * @param {String} choice + * @returns boolean + */ + chose(e) { + return this.records[e].enabled; } } -class t { - constructor(e2 = {}) { - Object.apply(this, e2), null == this.enabled && (this.enabled = false); +class C { + constructor(e = {}) { + Object.apply(this, e), this.enabled == null && (this.enabled = !1); } } -class o { - constructor(e2 = {}) { - Object.assign(this, e2); +class c { + constructor(e = {}) { + Object.assign(this, e); } - static build(e2 = {}) { - switch (e2.type) { + static build(e = {}) { + switch (e.type) { case "status": - return new a(e2); + return new D(e); case "choices": - return new r(e2); + return new G(e); case "inventory": - return new s(e2); + return new q(e); } } - verify(e2) { - return "all" == this.target ? 0 == e2[this.type].list().map((t2) => this.verifyTarget(t2, e2[this.type])).filter((e3) => 0 == e3).length : this.verifyTarget(this.target, e2[this.type]); - } - apply(e2, t2 = false) { - if ("all" == this.target ? e2[this.type].list().map((t3) => this.applyToTarget(t3, e2[this.type])).filter((e3) => 0 == e3).length : this.applyToTarget(this.target, e2[this.type]), !t2) { - for (const t3 of e2.inventory.addEffectQueue) - t3.apply(e2, true); - for (const t3 of e2.inventory.delEffectQueue) - t3.remove(e2); - e2.inventory.addEffectQueue = [], e2.inventory.delEffectQueue = []; + /** + * Verify this condition + * @param {object} playerStatus + * @returns boolean + */ + verify(e) { + return this.target == "all" ? e[this.type].list().map((t) => this.verifyTarget(t, e[this.type])).filter((t) => t == !1).length == 0 : this.verifyTarget(this.target, e[this.type]); + } + /** + * Apply this effect + * @param {object} playerStatus + */ + apply(e, t = !1) { + if (this.target == "all" ? e[this.type].list().map((o) => this.applyToTarget(o, e[this.type])).filter((o) => o == !1).length == 0 : this.applyToTarget(this.target, e[this.type]), !t) { + for (const o of e.inventory.addEffectQueue) + o.apply(e, !0); + for (const o of e.inventory.delEffectQueue) + o.remove(e); + e.inventory.addEffectQueue = [], e.inventory.delEffectQueue = []; } } - remove(e2) { - if ("all" == this.target) - return 0 == e2[this.type].list().map((t2) => this.removeFromTarget(t2, e2[this.type])).filter((e3) => 0 == e3).length; - this.removeFromTarget(this.target, e2[this.type]); + /** + * Remove this effect + * @param {object} playerStatus + */ + remove(e) { + if (this.target == "all") + return e[this.type].list().map((t) => this.removeFromTarget(t, e[this.type])).filter((t) => t == !1).length == 0; + this.removeFromTarget(this.target, e[this.type]); } } -class a extends o { - verifyTarget(e2, t2) { +class D extends c { + /** + * Verify this condition against a specific target + * @param {String} target + * @param {object} data + * @returns boolean + */ + verifyTarget(e, t) { switch (this.operation) { case "lt": - return t2.get(e2).value < this.value; + return t.get(e).value < this.value; case "lte": - return t2.get(e2).value <= this.value; + return t.get(e).value <= this.value; case "gt": - return t2.get(e2).value > this.value; + return t.get(e).value > this.value; case "gte": - return t2.get(e2).value >= this.value; + return t.get(e).value >= this.value; case "eq": - return t2.get(e2).value == this.value; + return t.get(e).value == this.value; case "ne": - return t2.get(e2).value != this.value; + return t.get(e).value != this.value; default: throw new Error("Invalid operation for statusEffect"); } } - applyToTarget(e2, t2) { + /** + * Apply this effect to a specific target + * @param {String} target + * @param {object} data + */ + applyToTarget(e, t) { switch (this.operation) { case "add": - return t2.get(e2).add(this.value); + return t.get(e).add(this.value); case "add_max": - return t2.get(e2).maximum += this.value; + return t.get(e).maximum += this.value; } } - removeFromTarget(e2, t2) { + /** + * Remove this effect from a specific target + * @param {String} target + * @param {object} data + */ + removeFromTarget(e, t) { switch (this.operation) { case "add": - return t2.get(e2).add(-this.value); + return t.get(e).add(-this.value); case "add_max": - return t2.get(e2).maximum -= this.value; + return t.get(e).maximum -= this.value; } } } -class r extends o { - verifyTarget(e2, t2) { - return this.value == t2.chose(e2); - } - applyToTarget(e2, t2) { - t2.choose(e2, this.value); - } - removeFromTarget(e2, t2) { - t2.choose(e2, !this.value); +class G extends c { + /** + * Verify this condition against a specific target + * @param {String} target + * @param {object} data + * @returns boolean + */ + verifyTarget(e, t) { + return this.value == t.chose(e); + } + /** + * Apply this effect to a specific target + * @param {String} target + * @param {object} data + */ + applyToTarget(e, t) { + t.choose(e, this.value); + } + /** + * Remove this effect from a specific target + * @param {String} target + * @param {object} data + */ + removeFromTarget(e, t) { + t.choose(e, !this.value); } } -class s extends o { - verifyTarget(e2, t2) { - return t2.has(e2) == this.value; - } - applyToTarget(e2, t2) { - this.value ? t2.give(e2) : t2.take(e2); - } - removeFromTarget(e2, t2) { - this.value ? t2.take(e2) : t2.give(e2); +class q extends c { + /** + * Verify this condition against a specific target + * @param {String} target + * @param {object} data + * @returns boolean + */ + verifyTarget(e, t) { + return t.has(e) == this.value; + } + /** + * Apply this effect to a specific target + * @param {String} target + * @param {object} data + */ + applyToTarget(e, t) { + this.value ? t.give(e) : t.take(e); + } + /** + * Remove this effect from a specific target + * @param {String} target + * @param {object} data + */ + removeFromTarget(e, t) { + this.value ? t.take(e) : t.give(e); } } -class n { - constructor(e2 = {}) { - Object.assign(this, JSON.parse(JSON.stringify(e2))); - for (const e3 in this.records) - this.records[e3] = new i(this.records[e3]); +class $ { + constructor(e = {}) { + Object.assign(this, JSON.parse(JSON.stringify( + e + ))); + for (const t in this.records) + this.records[t] = new L(this.records[t]); this.addEffectQueue = [], this.delEffectQueue = []; } + /** + * List the available entries + * @returns Array + */ list() { return Object.keys(this.records); } + /** + * List the available entries + * @returns Array + */ list_equipped() { - return Object.keys(this.records).filter((e2) => this.has(e2)); + return Object.keys(this.records).filter((e) => this.has(e)); } + /** + * List the equipped entries + * @returns Array + */ all_equipped() { - return Object.values(this.records).filter((e2) => e2.equipped); - } - set(e2, t2) { - this.records[e2].equipped = t2; - } - give(e2) { - this.has(e2) || this.addEffectQueue.push(...this.describe(e2).effects), this.set(e2, true); - } - take(e2) { - this.has(e2) && this.delEffectQueue.push(...this.describe(e2).effects), this.set(e2, false); - } - has(e2) { - return this.records[e2].equipped; - } - describe(e2) { - return this.records[e2]; - } + return Object.values(this.records).filter((e) => e.equipped); + } + /** + * Set an item status + * @param {String} item + * @param {Boolean} equipped + */ + set(e, t) { + this.records[e].equipped = t; + } + /** + * Give an item to the player + * @param {String} item + * @returns array of effects to apply + */ + give(e) { + this.has(e) || this.addEffectQueue.push(...this.describe(e).effects), this.set(e, !0); + } + /** + * Take an item away from the player + * @param {String} item + */ + take(e) { + this.has(e) && this.delEffectQueue.push(...this.describe(e).effects), this.set(e, !1); + } + /** + * Returns true if the player possesses the given item + * @param {String} item + * @returns boolean + */ + has(e) { + return this.records[e].equipped; + } + /** + * Returns item description + * @param {String} item + * @returns boolean + */ + describe(e) { + return this.records[e]; + } + /** + * Return the full set of active effects from equipped items + * @returns PlayEffect[] + */ activeEffects() { - return Object.values(this.records).filter((e2) => e2.equipped).map((e2) => e2.effects).flat(); + return Object.values(this.records).filter((e) => e.equipped).map((e) => e.effects).flat(); } } -class i { - constructor(e2 = {}) { - Object.assign(this, e2); - for (const e3 in this.effects) - this.effects[e3] = o.build(this.effects[e3]); +class L { + constructor(e = {}) { + Object.assign(this, e); + for (const t in this.effects) + this.effects[t] = c.build(this.effects[t]); } } -class l { - constructor(e2 = {}) { - Object.assign(this, JSON.parse(JSON.stringify(e2))); - for (const e3 in this.records) - this.records[e3] = new h(this.records[e3]); - } +class M { + constructor(e = {}) { + Object.assign(this, JSON.parse(JSON.stringify( + e + ))); + for (const t in this.records) + this.records[t] = new F(this.records[t]); + } + /** + * List the available status entries + * @returns Array + */ list() { return Object.keys(this.records); } + /** + * List the available status entries + * @returns Array + */ list_visible() { - return this.list().filter((e2) => !this.records[e2].hidden); - } - get(e2) { - return this.records[e2]; - } - value(e2) { - return this.records[e2].value; - } - set(e2, t2) { - this.get(e2).set(t2); - } - add(e2, t2) { - this.get(e2).add(t2); + return this.list().filter((e) => !this.records[e].hidden); + } + /** + * Retrieve a status + * @param {String} target + * @returns Number + */ + get(e) { + return this.records[e]; + } + /** + * Retrieve a status value + * @param {String} target + * @returns Number + */ + value(e) { + return this.records[e].value; + } + /** + * Change a status value + * @param {String} target + * @param {Number} value + */ + set(e, t) { + this.get(e).set(t); + } + /** + * Add to a status value + * @param {String} target + * @param {Number} value + */ + add(e, t) { + this.get(e).add(t); } } -class h { - constructor(e2 = {}) { - Object.assign(this, e2), null == this.value && (this.value = this.default); - } - set(e2) { - this.value = e2, this.value < 0 ? this.value = 0 : this.value > this.maximum && (this.value = this.maximum); - } - add(e2) { - this.set(this.value + e2); +class F { + constructor(e = {}) { + Object.assign(this, e), this.value == null && (this.value = this.default); + } + /** + * Change a status value + * @param {Number} value + */ + set(e) { + this.value = e, this.value < 0 ? this.value = 0 : this.value > this.maximum && (this.value = this.maximum); + } + /** + * Add to a status value + * @param {Number} value + */ + add(e) { + this.set(this.value + e); } } -class u { - constructor(e2 = {}) { - Object.assign(this, JSON.parse(JSON.stringify(e2))); - for (const e3 in this.records) - this.records[e3] = new c(this.records[e3]); - } - getStory(e2) { - return this.records[e2] || false; +class R { + constructor(e = {}) { + Object.assign(this, JSON.parse(JSON.stringify( + e + ))); + for (const t in this.records) + this.records[t] = new B(this.records[t]); + } + /** + * Retrieve a story in the chapter by ID + * @param {String} story_key + * @returns A Story object, or false + */ + getStory(e) { + return this.records[e] || !1; } } -class c { - constructor(e2 = {}) { - Object.assign(this, e2); - for (const e3 in this.effects) - this.effects[e3] = o.build(this.effects[e3]); - for (const e3 in this.options) - this.options[e3] = new d(this.options[e3]); +class B { + constructor(e = {}) { + Object.assign(this, e); + for (const t in this.effects) + this.effects[t] = c.build(this.effects[t]); + for (const t in this.options) + this.options[t] = new Z(this.options[t]); } } -class d { - constructor(e2 = {}) { - Object.assign(this, e2); - for (const e3 in this.results) - this.results[e3] = new p(this.results[e3]); - for (const e3 in this.conditions) - this.conditions[e3] = o.build(this.conditions[e3]); +class Z { + constructor(e = {}) { + Object.assign(this, e); + for (const t in this.results) + this.results[t] = new z(this.results[t]); + for (const t in this.conditions) + this.conditions[t] = c.build(this.conditions[t]); } toString() { return this.prompt; } } -class p { - constructor(e2 = {}) { - if ("string" == typeof e2 || e2 instanceof String) - this.target = e2, this.conditions = []; +class z { + constructor(e = {}) { + if (typeof e == "string" || e instanceof String) + this.target = e, this.conditions = []; else { - Object.assign(this, e2); - for (const e3 in this.conditions) - this.conditions[e3] = o.build(this.conditions[e3]); + Object.assign(this, e); + for (const t in this.conditions) + this.conditions[t] = c.build(this.conditions[t]); } } } -class m { - static assign(e2, t2) { - Object.assign(e2, JSON.parse(JSON.stringify(t2))); - } - static base64Encode(e2) { - if ("function" == typeof window.btoa) - return window.btoa(e2); - if ("object" == typeof Buffer) - return Buffer.from(e2).toString("base64"); +class y { + /** + * Assign a copy of the source Object to target + * @param {Object} target + * @param {Object} source + */ + static assign(e, t) { + Object.assign(e, JSON.parse(JSON.stringify( + t + ))); + } + static base64Encode(e) { + if (typeof window.btoa == "function") + return window.btoa(e); + if (typeof Buffer == "object") + return Buffer.from(e).toString("base64"); throw new Error("Cannot base64"); } - static base64Decode(e2) { - if ("function" == typeof window.atob) - return window.atob(e2); - if ("object" == typeof Buffer) - return Buffer.from(e2, "base64").toString("binary"); + static base64Decode(e) { + if (typeof window.atob == "function") + return window.atob(e); + if (typeof Buffer == "object") + return Buffer.from(e, "base64").toString("binary"); throw new Error("Cannot base64"); } } -let g = { $schema: "../schema/player.zarban.schema.json", entrypoint: "intro_cave1", chapters: [{ $schema: "../schema/chapter.zarban.schema.json", name: "Chapter 1: The Grotto", records: { intro_cave1: { text: ["Drenched from the rain, and exhausted from having hunted all through the night, you have finally cornered the beast which you have been hired to dispatch.", "You approach the forboding cavern to which you have stalked your prey, the shapeshifting vampire Zarban.", "The foul stench of magic fills your nostrils as you prepare to enter the grotto proper."], effects: [{ type: "choices", target: "all", value: false }, { type: "inventory", target: "all", value: false }, { type: "status", target: "all", operation: "add", value: -99 }, { type: "status", target: "stamina", operation: "add", value: 2 }, { type: "inventory", target: "hunter_sword", value: true }, { type: "inventory", target: "hunter_armor", value: true }], options: [{ prompt: "Enter the grotto", conditions: [], results: ["intro_cave2"] }] }, intro_cave2: { text: ["In the darkness before you, deep within the cave looms a vile shadow, dripping with evil.", "It can only be the mighty vampire Zarban himself."], effects: [], options: [{ prompt: "Draw your magic sword and approach the shadow", conditions: [], results: ["intro_cave3_brave"] }, { prompt: "I don't care about vampires, let's go to the tavern", conditions: [], results: ["intro_cave3_tavern"] }] }, intro_cave3_brave: { text: ["As you prepare yourself and draw your enchanted blade, you are knocked out from behind by a large rock to the head.", "You collapse to the ground, unconcious as the mighty Zarban scurries away into the night, cackling annoyingly."], effects: [], options: [{ prompt: "...", conditions: [], results: ["intro_cave4"] }] }, intro_cave3_tavern: { text: ["As you turn around to give up your promising career as a mediocre vampire-hunter for hire, you are knocked out from behind by a large rock to the head.", "You collapse to the ground, unconcious as the mighty Zarban scurries away into the night, cackling annoyingly."], effects: [], options: [{ prompt: "...", conditions: [], results: ["intro_cave4"] }] }, intro_cave4: { text: ["You awaken some time later, to find your magic blade, and mint-condition vintage vampire hunting armor reduced to worthless scrap before you.", "You gather what little you can salvage, and turn to leave the grotto.", "", "Before you lies a single set of footprints, leading away from the grotto."], effects: [{ type: "inventory", target: "all", value: false }], options: [{ prompt: "Follow the footprints", conditions: [], results: ["intro_cave5_brave"] }, { prompt: "I don't care about footprints, let's go to the tavern", conditions: [], results: ["intro_cave5_tavern"] }] }, intro_cave5_brave: { text: ["You follow the footprints to a nearby village, and arrive just as dawn breaks.", "The footprints lead into the village, but too many footprints coming and going make it impossible to tell what happened next.", "", "One thing you can be sure of, however; you still sense Zarban's evil aura - you are sure he is still hiding out somewhere in this very town", "Likely having replaced one of the sleepy village's unsuspecting peasants.", "You are exausted from searching through the night, and could use a pick-me-up."], effects: [], options: [{ prompt: "Look around", conditions: [], results: ["village"] }] }, intro_cave5_tavern: { text: ["You walk to a tavern in a nearby village, and arrive just as dawn breaks.", "", "As you approach the village, you once again sense Zarban's evil aura - you are sure he is hiding out somewhere in this very town", "Likely having replaced one of the sleepy village's unsuspecting peasants."], effects: [], options: [{ prompt: "Enter the tavern", conditions: [], results: ["tavern_enter"] }] } }, chapter: "0" }, { $schema: "../schema/chapter.zarban.schema.json", name: "Chapter 2: Footprints", records: { tavern_enter: { text: ["You enter the tavern.", "Looking around you see several people of interest milling about the small village bar."], effects: [], options: [{ prompt: "Take a seat at the bar", conditions: [], results: ["tavern"] }, { prompt: "Leave the tavern", conditions: [], results: [{ conditions: [{ type: "status", target: "stamina", operation: "eq", value: 0 }], target: "village_ending_stamina" }, "village"] }] }, tavern_ending_alcoholic: { text: ["You take your familiar seat back at the bar, and order another round.", "Having clearly decided to give up vampire hunting for a promising new career in alcoholism, you decide to let Zarban live.", "Zarban would later go on to burn down 27 orphanages in your name.", "", "GAME OVER! Try again?"], effects: [], options: [{ prompt: "New game", conditions: [], results: ["intro_cave1"] }] }, tavern: { text: ["You take a seat on a ramshackle stool at the tavern bar."], effects: [], options: [{ prompt: "Speak to the bartender", conditions: [], results: ["tavern_bartender"] }, { prompt: "Approach the suspicious hooded figure lurking in the corner", conditions: [{ type: "choices", target: "made_dave_go_home", value: false }], results: [{ target: "tavern_lurker", conditions: [{ type: "choices", target: "made_dave_sad", value: false }] }, { target: "tavern_lurker_sad", conditions: [{ type: "choices", target: "made_dave_sad", value: true }] }] }, { prompt: "Approach the old farmer drinking alone at the bar", conditions: [], results: ["tavern_farmer"] }, { prompt: "Leave the tavern", conditions: [], results: [{ conditions: [{ type: "status", target: "stamina", operation: "eq", value: 0 }], target: "village_ending_stamina" }, "village"] }] }, tavern_bartender: { text: ["You approach the tired looking bartender, intending to vigorously question the overworked customer-service employee.", `"Back for another already? What can I get'cha, stranger?"`], effects: [], options: [{ prompt: "Ask if any strangers have come through town recently", conditions: [], results: ["tavern_bartender_strangers"] }, { prompt: "Ask if anyone in town sells weapons and armor", conditions: [], results: ["tavern_bartender_blacksmith"] }, { prompt: "I could use a refreshing drink", conditions: [], results: [{ conditions: [{ type: "status", target: "alcoholism", value: 1, operation: "eq" }], target: "tavern_ending_alcoholic" }, "tavern_bartender_shots"] }, { prompt: "Go back to your stool", conditions: [], results: ["tavern"] }] }, tavern_bartender_strangers: { text: ["You ask the bartender about other strangers that have passed through town.", "", `"Besides yourself, m'lord? Just one, earlier this very morn'! He'll be long gone by now though, headed out in something of a hurry."`, `"Ye could always ask Gaylen, the priest 'round' these parts - his shack'll be a good halfday's walk from here, north of the village."`], effects: [{ type: "choices", target: "learnt_about_priest", value: true }], options: [{ prompt: "Ask the bartender more questions", conditions: [], results: ["tavern_bartender"] }, { prompt: "Go back to your stool", conditions: [], results: ["tavern"] }] }, tavern_bartender_blacksmith: { text: ["You ask the bartender where you might acquire new weapons and armor.", "", `"Well, you're not likely to find a big fancy smithy in our little village, m'lord"`, `"That said, Dave over yonder has been known to fix a rake in his time, if you catch my meanin'"`, "The bartender laughs at his attempted joke, as he points to a hooded figure drinking alone in the corner of the bar."], effects: [], options: [{ prompt: "Ask the bartender more questions", conditions: [], results: ["tavern_bartender"] }, { prompt: "Go back to your stool", conditions: [], results: ["tavern"] }] }, tavern_bartender_shots: { text: ["You ask the bartender for a glass of his strongest brew, and down it in once mighty gulp.", "Your stamina has been restored"], effects: [{ type: "status", target: "stamina", operation: "add", value: 99 }, { type: "status", target: "alcoholism", operation: "add", value: 1 }], options: [{ prompt: "Ask the bartender more questions", conditions: [], results: ["tavern_bartender"] }, { prompt: "Go back to your stool", conditions: [], results: ["tavern"] }] }, tavern_lurker: { text: ["You approach the hooded stranger, clearly an imposing figure. The hulking goliath of a man looks up at you with a scowl on his face.", `"What do ye' want, stranger. I ain't in no mood fer conversin' with the likes of you today."`], effects: [], options: [{ prompt: "I KNOW YOU'RE THE VAMPIRE, YOU MONSTER! PREPARE TO DIE!", conditions: [], results: ["tavern_lurker_sad"] }, { prompt: "Ask where you might be able to acquire some new weapons and armor", conditions: [], results: ["tavern_lurker_smithy"] }, { prompt: "Ask if he's seen anything unusual", conditions: [], results: ["tavern_lurker_unusual"] }, { prompt: "Go back to your stool", conditions: [], results: ["tavern"] }] }, tavern_lurker_smithy: { text: [`"Well I haven't the skills to make such a thing, if that's what yer asking, but I was a soldier in me youth."`, '"Meet me at my house just outside the village. Could use a hand with something, then I think I can help ye."'], effects: [{ type: "choices", target: "made_dave_go_home", value: true }], options: [{ prompt: "Go back to your stool", conditions: [], results: ["tavern"] }] }, tavern_lurker_unusual: { text: [`"Wouldn't know nothin' about that, stranger. Leave me be."`], effects: [], options: [{ prompt: "Ask him more questions", conditions: [], results: ["tavern_lurker"] }, { prompt: "Go back to your stool", conditions: [], results: ["tavern"] }] }, tavern_lurker_sad: { text: ["The large muscular man before you begins to cry uncontrollably, tears streaming down his bearded visage.", `"Just leave me alone! You're really mean... G... Go away..."`, "", "The mighty Zarban would never debase himself so. This is clearly not he."], effects: [{ type: "choices", target: "made_dave_sad", value: true }], options: [{ prompt: "Go back to your stool", conditions: [], results: ["tavern"] }] }, tavern_farmer: { text: ["An old man sits at the bar next to you, his skin wrinkled from years of hard labour under the sun."], effects: [], options: [{ prompt: "Ask him how a farmer has time to drink in broad daylight", conditions: [], results: ["tavern_farmer_sun"] }, { prompt: "Make small talk", conditions: [], results: ["tavern_farmer_daughter"] }, { prompt: "Ask if anyone in town sells weapons and armor", conditions: [], results: ["tavern_farmer_blacksmith"] }, { prompt: "Ask if he's seen anything unusual", conditions: [], results: ["tavern_farmer_unusual"] }, { prompt: "Go back to your stool", conditions: [], results: ["tavern"] }] }, tavern_farmer_sun: { text: ['"Never much cared for the sun is all. Do most of me work by night these days. What business is it of yours, anyway?"'], effects: [], options: [{ prompt: "Continue talking to the farmer", conditions: [], results: ["tavern_farmer"] }, { prompt: "Go back to your stool", conditions: [], results: ["tavern"] }] }, tavern_farmer_unusual: { text: [`"Unusual? Two strangers in one night is unusual. It's also annoying. Can I get back to me drink now?"`], effects: [], options: [{ prompt: "Continue talking to the farmer", conditions: [], results: ["tavern_farmer"] }, { prompt: "Go back to your stool", conditions: [{ type: "status", target: "alcoholism", value: 2, operation: "lt" }], results: ["tavern"] }] }, tavern_farmer_daughter: { text: ['"Not much of interest happens round these parts, stranger."', `"Me daughter's been aweful moody lately, but tis the norm for the young'uns these days."`], effects: [], options: [{ prompt: "Continue talking to the farmer", conditions: [], results: ["tavern_farmer"] }, { prompt: "Go back to your stool", conditions: [], results: ["tavern"] }] }, tavern_farmer_blacksmith: { text: [`"You could try Dave, round back. He's the hooded feller with crippling emotional issues."`, `"He's been known to fix a rake in his time, if you catch my meanin'"`, "The farmer laughs heartily, clearly pleased with his joke."], effects: [], options: [{ prompt: "Continue talking to the farmer", conditions: [], results: ["tavern_farmer"] }, { prompt: "Go back to your stool", conditions: [], results: ["tavern"] }] }, village_ending_stamina: { text: ["Exhausted and thirsty, you collapse to the ground. The villagers find you, and bring you to the inn to recover.", "Unfortunately, by then Zarban is long gone, and the trail cold. He will later go on to become the CEO of Nestle.", "", "GAME OVER! Try again?"], effects: [], options: [{ prompt: "New game", conditions: [], results: ["intro_cave1"] }] }, village: { text: ["You stand on the main road crossing the village. A nearby sign identifies the town as Rothsten."], effects: [{ type: "status", target: "stamina", operation: "add", value: -1 }], options: [{ prompt: "Search the farm", conditions: [], results: [{ conditions: [{ type: "status", target: "stamina", operation: "eq", value: 0 }], target: "village_ending_stamina" }, "village_farm"] }, { prompt: "Search Dave's house", conditions: [], results: [{ conditions: [{ type: "status", target: "stamina", operation: "eq", value: 0 }], target: "village_ending_stamina" }, { conditions: [{ type: "choices", target: "made_dave_go_home", value: false }], target: "village_dave_not_home" }, { conditions: [{ type: "choices", target: "made_dave_sad", value: true }], target: "village_dave_not_home" }, "village_dave"] }, { prompt: "Search the school", conditions: [], results: [{ conditions: [{ type: "status", target: "stamina", operation: "eq", value: 0 }], target: "village_ending_stamina" }, "village_school_outside"] }, { prompt: "Search the mill", conditions: [], results: [{ conditions: [{ type: "status", target: "stamina", operation: "eq", value: 0 }], target: "village_ending_stamina" }, "village_mill"] }, { prompt: "Search the tavern", conditions: [], results: ["tavern_enter"] }, { prompt: "Leave the village and head to the priest's cottage", conditions: [{ type: "choices", target: "learnt_about_priest", value: true }], results: [{ conditions: [{ type: "status", target: "stamina", operation: "eq", value: 0 }], target: "village_ending_stamina" }, "church_bear"] }] }, village_farm: { text: ["You arrive at a well maintained farmhouse. You walk into the house, and see a young woman crushing herbs with a mortar & pestle.", "Based on her attire - and the visibility of her assets - you wonder if her clothes were the victim of a shrinking spell.", "She does not seem surprised to see you, not so much as looking up from her work.", '"A vampire hunter in our little corner of the world? What could possibly bring you here?" She asks you.'], effects: [], options: [{ prompt: "Speak to the young woman", conditions: [], results: ["village_farm_amelie"] }, { prompt: "Look around the humble farmhouse", conditions: [], results: ["village_farm_search"] }, { prompt: "Leave the farm", conditions: [], results: ["village"] }] }, village_farm_amelie: { text: ['"Need a potion? Or a salve?" The young woman asks'], effects: [], options: [{ prompt: "Ask her for a potion of vampire detection", conditions: [], results: ["village_farm_amelie_potion"] }, { prompt: "Ask her about her family", conditions: [], results: ["village_farm_amelie_family"] }, { prompt: "Ask her about herself", conditions: [], results: ["village_farm_amelie_self"] }, { prompt: "Ask if she's seen anything unusual", conditions: [], results: ["village_farm_amelie_unusual"] }, { prompt: "Stop speaking to the woman", conditions: [], results: ["village_farm"] }] }, village_farm_amelie_potion: { text: [`The woman looks at you confused; "Is that a real potion? I'm afraid I wouldn't know how to make this." The young woman replies`, '"I have a potion to cure rhumatism, if that interests you, or the best salve for warts this side of the continent!"'], effects: [], options: [{ prompt: "Ask her something else", conditions: [], results: ["village_farm_amelie"] }] }, village_farm_amelie_unusual: { text: ['"Unusual?" The young woman laughs', `"Honey, having a stranger on our farm is the most unusual thing that's happened in recent memory."`], effects: [], options: [{ prompt: "Ask her something else", conditions: [], results: ["village_farm_amelie"] }] }, village_farm_amelie_family: { text: [`"Our family? It's an interesting enough tale, I suppose." The young woman begins`, '"My father is Arnoulf, of House Brolette - yes those Brolettes."', `"In the days of the Aremeic Order, our family laid claim to the thrones of half the Order's colonial territories on the East continent!"`, `"My father doesn't like to talk about it, and there's not much left of the family, but it's a proud history to bear."`], effects: [], options: [{ prompt: "Ask her something else", conditions: [], results: ["village_farm_amelie"] }] }, village_farm_amelie_self: { text: [`"Me? I am Amelie of House Brolette; I'm the village herbalist, not that my skills net me much respect in this hole of a village."`, '"I provide these simpletons with medicines and salves, but nobody can see past my choice of clothes. Nobody would bat an eye in the cities."', `"Ever try foraging for mushrooms in 3 layers of frill and fuss? You'd never catch me in some silly long dress."`], effects: [], options: [{ prompt: "Ask her something else", conditions: [], results: ["village_farm_amelie"] }] }, village_farm_search: { text: ["You take a good look around the room.", "In the corner are a pair of small beds, one of which clearly hasn't seem much use of late.", "In the kitchen, various herbs and mushrooms hang from the ceiling.", "Supplies for alchemical brewing are strewn about."], effects: [], options: [{ prompt: "Speak to the young woman", conditions: [], results: ["village_farm_amelie"] }, { prompt: "Leave the farm", conditions: [], results: ["village"] }] }, village_dave_not_home: { text: ["You arrive at Dave's home at the edge of the village, a once beautiful home, but clearly neglected of late.", "Vines and weeds grow rampant across the property, and the windows sit greased and dusty.", "", "No candles burn within, and the door is locked tight."], effects: [], options: [{ prompt: "Leave Dave's house", conditions: [], results: ["village"] }] }, village_dave: { text: ["You arrive at Dave's home at the edge of the village, a once beautiful home, but clearly neglected of late.", "Vines and weeds grow rampant across the property, and the windows sit greased and dusty.", "", "The door sits open, so you walk inside. Dave sits alone on a dusty old wooden chair.", "All around him sits dusty disused brushes, paints and canvases"], effects: [], options: [{ prompt: "Talk to Dave", conditions: [], results: ["village_dave_dave"] }, { prompt: "Leave Dave's house", conditions: [], results: ["village"] }] }, village_dave_dave: { text: [`"So, ye needed a weapon, ey? I do have me gear from the war that I'd be happy enough to never see again."`, `"Not usually in the habit of armin' strangers though, so ye'd best have a good cause."`, `"My wife, god rest 'er soul didn't approve of wanton violence."`], effects: [], options: [{ prompt: "Tell him you are hunting a vampire hiding amongst the villagers", conditions: [], results: ["village_dave_dave_honest"] }, { prompt: "Tell him you are avenging the death of a loved one", conditions: [], results: ["village_dave_dave_lie"] }, { prompt: "Tell him to give you the gear or you'll take it by force", conditions: [], results: ["village_dave_dave_threaten"] }, { prompt: "Leave Dave's house", conditions: [], results: ["village"] }] }, village_dave_dave_honest: { text: ["Dave sits in silence for an awkwardly long time.", `"There's something in the air today; as if the sky itself were thick with dread."`, `"I believe you, stranger. My sword and armor are yours. They ain't much but I hope they help you."`, '"All I ask is that you lay these here flowers at my family grave. The walk to the temple is too much for this old soldier."', `"Ye'll see it marked by the clan name Audemars."`, "", "The rusted old blade would never be able to strike a killing blow against such a powerful vampire;", "But it may grant some protection, at the very least."], effects: [{ type: "choices", target: "made_dave_sad", value: true }, { type: "inventory", target: "rusty_sword", value: true }, { type: "inventory", target: "old_armor", value: true }, { type: "inventory", target: "dave_flowers", value: true }], options: [{ prompt: "Leave Dave's house", conditions: [], results: ["village"] }] }, village_dave_dave_lie: { text: ["Dave slowly shakes his head.", '"Sorry stranger, I cannae in good conscience lend creed to vengence. I hope you find a way to see another path."', `"In the meantime, I'm afraid ye'll need to leave."`], effects: [{ type: "choices", target: "made_dave_sad", value: true }], options: [{ prompt: "Leave Dave's house", conditions: [], results: ["village"] }] }, village_dave_dave_threaten: { text: ["Without any warning, the giant of a man hits you on the side of the head with one mighty punch.", "You awaken outside the house, with the door now securely locked."], effects: [{ type: "choices", target: "made_dave_sad", value: true }], options: [{ prompt: "Leave Dave's house", conditions: [], results: ["village"] }] }, village_mill: { text: ["You arive at the small village mill, and step inside", "The stone brick building is small, but impressive - clearly much older than the wooden structures you've seen thus far.", "Inside are a man, expertly kneading a large ball of dough, and a woman on a wooden rocking chair repairing a tunic.", "", `"Ah, we heard there were strangers about!" says the man; "Something ye' knead? Some bread for the road perhaps?"`], effects: [], options: [{ prompt: "Take the man up on his offer of bread", conditions: [{ type: "choices", target: "accepted_bread", value: false }], results: ["village_mill_bread"] }, { prompt: "Ask if they have garlic bread instead", conditions: [{ type: "choices", target: "accepted_bread", value: false }], results: ["village_mill_garlic"] }, { prompt: "Ask about their family", conditions: [], results: ["village_mill_family"] }, { prompt: "Ask if they've seen anything unusual", conditions: [], results: ["village_mill_unusual"] }, { prompt: "Leave the mill", conditions: [], results: ["village"] }] }, village_mill_family: { text: [`"We're the Fouchers sir - Nils and Silvia. Our family's been in these parts since the colonial days of the Order."`, `"We've 3 little 'uns - Dylan is 8, and the twins are fresh out the oven."`], effects: [], options: [{ prompt: "Ask something else", conditions: [], results: ["village_mill"] }] }, village_mill_unusual: { text: ['"Not a thing of interest has happened in these here parts in many a year, sir."', `"If it's adventure ye' seek, you've picked the wrong village I'm afraid."`], effects: [], options: [{ prompt: "Ask something else", conditions: [], results: ["village_mill"] }] }, village_mill_garlic: { text: ["You narrow your eyes in suspicion as the question leaves your lips", `The man shakes his head; "Nay, stranger. I've a terrible allergy to garlic, so none of that 'ere."`], effects: [], options: [{ prompt: "Ask something else", conditions: [], results: ["village_mill"] }] }, village_mill_bread: { text: ["The baker hands you a fresh loaf of bread, hot out of the oven,", "The family then looks on in horror as you devour the entire loaf in one go like some kind of feral beast"], effects: [{ type: "choices", target: "accepted_bread", value: true }, { type: "status", target: "stamina", operation: "add", value: 99 }], options: [{ prompt: "Ask something else", conditions: [], results: ["village_mill"] }] }, village_school_outside: { text: ["You arrive outside the small schoolhouse. A humble shack sits next to it, likely a home for the teacher and her family.", "A gaggle of annoying looking children mill about uselessly outside the school."], effects: [], options: [{ prompt: "Search the shack", conditions: [], results: ["village_school_shack"] }, { prompt: "Search the schoolhouse", conditions: [], results: ["village_school_inside"] }, { prompt: "Harass the annoying children", conditions: [], results: ["village_school_children"] }, { prompt: "Leave the school", conditions: [], results: ["village"] }] }, village_school_inside: { text: ["You walk into the schoolhouse.", "A woman - the teacher, presumably - is sat at the far side of the room.", "On her head sits the biggest hat you have ever seen. This sunhat has a wingspan that could make a dragon jealous."], effects: [], options: [{ prompt: "Ask her about herself", conditions: [], results: [{ conditions: [{ type: "choices", target: "insulted_teacher", value: true }], target: "village_school_items" }, "village_school_teacher"] }, { prompt: "Ask if she's seen anything unusual", conditions: [], results: [{ conditions: [{ type: "choices", target: "insulted_teacher", value: true }], target: "village_school_items" }, "village_school_unusual"] }, { prompt: "Ask about the strange items in her home", conditions: [{ type: "choices", target: "snooped_on_teacher", value: true }], results: ["village_school_items"] }, { prompt: "Ask about her gigantic hat", conditions: [], results: [{ conditions: [{ type: "choices", target: "insulted_teacher", value: true }], target: "village_school_items" }, "village_school_hat"] }, { prompt: "Look somewhere else", conditions: [], results: ["village_school_outside"] }] }, village_school_items: { text: [`"What kind of creepy imbecile looks in a young woman's windows at her private things?"`, '"Leave my classroom this instant!"'], effects: [{ type: "choices", target: "insulted_teacher", value: true }], options: [{ prompt: "Ask something else", conditions: [], results: ["village_school_inside"] }] }, village_school_unusual: { text: ['"Besides the usual nonsense from that horrible Brolette girl? Not in a long time."'], effects: [], options: [{ prompt: "Ask something else", conditions: [], results: ["village_school_inside"] }] }, village_school_teacher: { text: [`"My family? nothing special, I'm afraid. Lost my eldest and my husband two winters back to red fever."`, '"Milton is outside with his friends - mercifully he was too young to remember it."'], effects: [], options: [{ prompt: "Ask something else", conditions: [], results: ["village_school_inside"] }] }, village_school_hat: { text: ["The young woman laughs as she grips her cap", `"I've a skin condition, always have - the sun; my skin burns so easily"`, '"I use the hat to protect myself, along with a salve I make at home!"', `"I could buy it from that Brolette girl, but I'd rather not have to interact with that one."`], effects: [], options: [{ prompt: "Ask something else", conditions: [], results: ["village_school_inside"] }] }, village_school_shack: { text: ["The shack is locked but you look in through the window.", "You can see strange objects strewn about - a dagger, various herbs, an ornate pendant covered in runes, and a pouch of white powder"], effects: [{ type: "choices", target: "snooped_on_teacher", value: true }], options: [{ prompt: "Look somewhere else", conditions: [], results: ["village_school_outside"] }] }, village_school_children: { text: ["You walk up to the children and kick sand at them like a complete arsehole.", "One child begins to try, while another kicks you in the groin and runs away giggling."], effects: [{ type: "status", target: "stamina", operation: "add", value: -1 }], options: [{ prompt: "Look somewhere else", conditions: [], results: ["village_school_outside"] }] } }, chapter: "1" }, { $schema: "../schema/chapter.zarban.schema.json", name: "Chapter 3: The Hunter, Hunted", records: { church_bear: { text: ["You head down the wooded path towards the temple.", "As you enter the forest, the air around you grows cold and the sun itself begins to dim.", "", "You hear a mighty roar from behind you."], effects: [], options: [{ prompt: "Turn around and face your foe", conditions: [], results: [{ conditions: [{ type: "inventory", target: "rusty_sword", value: true }], target: "church_bear_armed" }, "church_bear_unarmed"] }, { prompt: "Run away from the creature", conditions: [], results: ["church_bear_coward"] }] }, church_bear_coward: { text: ["You bravely run away, as fast as you can into the woods, towards the temple, and Gaylen the town priest.", "You arrive at the temple late in the afternoon, and see the priest standing outside the ornate stone building.", "Nearby you can see a graveyard, with a massive ornate tomb at its center.", "The priest approches you:", '"Greetings, stranger. How may I be of service?"'], effects: [], options: [{ prompt: "Speak to the priest", conditions: [], results: ["church_gaylen"] }] }, church_bear_unarmed: { text: ["You turn to face your foe and come face to crotch with a 12ft tall monster of a bear.", "You instinctively reach for your blade, but of course, you don't have one.", "You start to run away, but the mighty beast - no doubt the shapeshifter himself - rips you open with a mighty swipe of his paw", "", "It will be daybreak before you are found by the villagers, and after numerous surgeries and expensive physiotherapy you regain the ability to walk", "But by then, when no one was looking, Zarban took forty cakes.", "That's as many as four tens.", "And that's terrible.", "", "GAME OVER! Try again?"], effects: [], options: [{ prompt: "New game", conditions: [], results: ["intro_cave1"] }] }, church_bear_armed: { text: ["You turn to face your foe and come face to crotch with a 12ft tall monster of a bear.", "You instinctively reach for your blade, and draw the corroded shortsword you were given.", "The mighty beast looms over you, menace in his eyes. The beast drips with magic and malice;", "You know in your heart that can only be Zarban the undead shapeshifter himself."], effects: [], options: [{ prompt: "Run away very very quickly", conditions: [], results: ["church_bear_armed_coward"] }, { prompt: "Go for the beast's head", conditions: [], results: ["church_bear_armed_stupid"] }, { prompt: "Slash at the beasts legs", conditions: [], results: ["church_bear_armed_legs"] }] }, church_bear_armed_coward: { text: ["You start to run away, but the mighty beast rips your back open with a mighty swipe of his paw", "", "It will be daybreak before you are found by the villagers, and after numerous surgeries and expensive physiotherapy you regain the ability to walk", "But by then, Zarban is long gone. He would later go on to start a mediocre reaction channel on TikTok.", "", "GAME OVER! Try again?"], effects: [], options: [{ prompt: "New game", conditions: [], results: ["intro_cave1"] }] }, church_bear_armed_stupid: { text: ["You stab directly upwards into the beasts head, aiming to finish this now and here.", "The common steel of the blade cannot deal a finishing blow against such a powerful magical foe, however and the metal passes right through the beast.", "While you leave your flank fully exposed, Zarban disembowels you with ease.", "Zarban would later go on to become a successful corporate attorney.", "", "GAME OVER! Try again?"], effects: [], options: [{ prompt: "New game", conditions: [], results: ["intro_cave1"] }] }, church_bear_armed_legs: { text: ["You slash at the beast's legs, causing him to roar in pain. You can't kill him with this sword, but even if he takes on another form,", "It will take days for him to heal from such a blow."], effects: [{ type: "choices", target: "injured_zarban", value: true }], options: [{ prompt: "Escape while the beast reels from the blow", conditions: [], results: ["church_bear_armed_escape"] }, { prompt: "Go for the beast's head", conditions: [], results: ["church_bear_armed_stupid"] }] }, church_bear_armed_escape: { text: ["With the beast's legs injured, you take your chance to escape into the woods, towards the temple, and Gaylen the town priest.", "You arrive at the temple late in the afternoon, and see the priest standing outside the ornate stone building.", "Nearby you can see a graveyard, with a massive ornate tomb at its center.", "The priest approches you:", '"Greetings, stranger. How may I be of service?"'], effects: [], options: [{ prompt: "Speak to the priest", conditions: [], results: ["church_gaylen"] }, { prompt: "There is nothing for me here, let's return to town", conditions: [{ type: "inventory", target: "rusty_sword", value: true }], results: ["jaccuse_arrival"] }] }, church_gaylen: { text: ['"Yes, strenger, how can I help you?"'], effects: [], options: [{ prompt: "Ask for help against the evil vampire", conditions: [{ type: "inventory", target: "ancient_armor", value: false }], results: [{ conditions: [{ type: "choices", target: "impressed_gaylen", value: true }], target: "church_gaylen_success" }, "church_gaylen_fail"] }, { prompt: "Ask about the temple", conditions: [], results: ["church_gaylen_temple"] }, { prompt: "Ask the priest about Dave's family grave", conditions: [{ type: "inventory", target: "dave_flowers", value: true }], results: ["church_gaylen_impressed"] }, { prompt: "Go to the graveyard", conditions: [], results: ["church_graveyard"] }] }, church_gaylen_success: { text: ["The priest stays silent for a time, appearing to pray to himself. Finally he speaks;", '"As you are a friend to the village, and an honourable soul, I grant you the protection of the great Edwin Rothsten."', `"May the ancient vampire hunter's armor protect you. And take this key, you'll find Moonsbane in the hunter's tomb."`], effects: [{ type: "inventory", target: "ancient_armor", value: true }, { type: "inventory", target: "hunter_tomb_key", value: true }, { type: "inventory", target: "old_armor", value: false }, { type: "choices", target: "learnt_about_hunter", value: true }], options: [{ prompt: "Ask the priest something else", conditions: [], results: ["church_gaylen"] }, { prompt: "Go to the graveyard", conditions: [], results: ["church_graveyard"] }] }, church_gaylen_fail: { text: ['"I will say a prayer of blessing for you stranger."', '"Sadly I have only your word, so I cannot help you more than that. Good luck to you."'], effects: [], options: [{ prompt: "Ask the priest something else", conditions: [], results: ["church_gaylen"] }, { prompt: "Go to the graveyard", conditions: [], results: ["church_graveyard"] }] }, church_gaylen_temple: { text: ['"This temple was founded over 700 years ago during the time of the Aremeic Order."', '"It was personally commisioned by the famed vampire hunter, and knight of the order Edwin Rothsten."', '"The legend himself is entombed here, where himself and his enchanted blade Moonsbane can watch over us."', "", "The priest points to the ornate tomb in the center of the graveyard - you think to yourself that it might be prudent to 'borrow' Moonsbane."], effects: [{ type: "choices", target: "learnt_about_hunter", value: true }], options: [{ prompt: "Ask the priest something else", conditions: [], results: ["church_gaylen"] }, { prompt: "Go to the graveyard", conditions: [], results: ["church_graveyard"] }] }, church_gaylen_impressed: { text: ["You show the flowers to the priest, who smiles warmly at you.", '"Ah! A friend of lord Audemars is a friend of mine. I will bless these and place them at his family grave for you."'], effects: [{ type: "inventory", target: "dave_flowers", value: false }, { type: "choices", target: "impressed_gaylen", value: true }], options: [{ prompt: "Ask the priest something else", conditions: [], results: ["church_gaylen"] }, { prompt: "Go to the graveyard", conditions: [], results: ["church_graveyard"] }] }, church_graveyard: { text: ["You arrive at the humble graveyard. In its center lies an ornate tomb marked 'Edwin Rothsten, Hunter of the Aremeic Order'", "The family grave of the Audemars clan sits nearby."], effects: [], options: [{ prompt: "Place the flower's on Dave Audemars' family grave", conditions: [{ type: "inventory", target: "dave_flowers", value: true }], results: ["church_graveyard_flowers"] }, { prompt: "Retrieve the sword from the hunter's tomb", conditions: [{ type: "choices", target: "learnt_about_hunter", value: true }], results: [{ conditions: [{ type: "inventory", target: "hunter_tomb_key", value: true }], target: "church_graveyard_key" }, "church_graveyard_breakin"] }, { prompt: "Return to the village, and confront Zarban", conditions: [{ type: "inventory", target: "rusty_sword", value: true }], results: ["jaccuse_arrival"] }] }, church_graveyard_flowers: { text: ["You place the flowers at the foot of the family grave stone, and say a short prayer.", "You feel content, your promise fulfilled."], effects: [{ type: "inventory", target: "dave_flowers", value: false }], options: [{ prompt: "Retrieve the sword from the hunter's tomb", conditions: [{ type: "choices", target: "learnt_about_hunter", value: true }], results: [{ conditions: [{ type: "inventory", target: "hunter_tomb_key", value: true }], target: "church_graveyard_key" }, "church_graveyard_breakin"] }, { prompt: "Return to the village, and confront Zarban", conditions: [{ type: "inventory", target: "rusty_sword", value: true }], results: ["jaccuse_arrival"] }] }, church_graveyard_key: { text: ["You walk up to the tomb, and use a the key to open the ornate lock.", "You pick up the ancient runic sword and feel its holy power flow through you - Zarban's end is at hand!"], effects: [{ type: "inventory", target: "magic_sword", value: true }, { type: "inventory", target: "rusty_sword", value: false }, { type: "inventory", target: "hunter_tomb_key", value: false }], options: [{ prompt: "Return to the village, and confront Zarban", conditions: [], results: ["jaccuse_arrival"] }] }, church_graveyard_breakin: { text: ["You walk up to the tomb, and use a nearby rock to smash open the ornate lock.", "You pick up the ancient runic sword and equip it - Zarban's end is at hand!", "You'll probably return it when you are finished. Maybe."], effects: [{ type: "inventory", target: "magic_sword", value: true }, { type: "inventory", target: "rusty_sword", value: false }], options: [{ prompt: "Return to the village, and confront Zarban", conditions: [], results: ["jaccuse_arrival"] }] } }, chapter: "2" }, { $schema: "../schema/chapter.zarban.schema.json", name: "Chapter 4: J'Accuse!", records: { jaccuse_arrival: { text: ["You arrive back in the village at midnight. The stench of evil still hangs in the air - it is time to confront Zarban.", "But which villager has been replaced by the vile sorcerer?"], effects: [{ type: "status", target: "stamina", operation: "add", value: -1 }], options: [{ prompt: "Go to the tavern", conditions: [], results: [{ conditions: [{ type: "status", target: "stamina", operation: "eq", value: 0 }], target: "jaccuse_end_stamina" }, "jaccuse_tavern"] }, { prompt: "Go to the farm", conditions: [], results: [{ conditions: [{ type: "status", target: "stamina", operation: "eq", value: 0 }], target: "jaccuse_end_stamina" }, { conditions: [{ type: "choices", target: "injured_zarban", value: true }], target: "jaccuse_farm_scar" }, "jaccuse_farm"] }, { prompt: "Go to Dave's house", conditions: [], results: [{ conditions: [{ type: "status", target: "stamina", operation: "eq", value: 0 }], target: "jaccuse_end_stamina" }, "jaccuse_dave"] }, { prompt: "Go to the schoolhouse", conditions: [], results: [{ conditions: [{ type: "status", target: "stamina", operation: "eq", value: 0 }], target: "jaccuse_end_stamina" }, "jaccuse_school"] }] }, jaccuse_end_stamina: { text: ["Exhausted and thirsty, you collapse to the ground. The villagers find you, and bring you to the inn to recover.", "Unfortunately, by then Zarban is long gone, and the trail cold. He will later go on to form a knitting circle that only makes ugly sweaters for puppies.", "", "GAME OVER! Try again?"], effects: [], options: [{ prompt: "New game", conditions: [], results: ["intro_cave1"] }] }, jaccuse_tavern: { text: ["You enter the tavern. The bartender stands at his usual spot, and Dave drinks alone in the corner."], effects: [], options: [{ prompt: "Confront the bartender", conditions: [], results: ["jaccuse_bartender"] }, { prompt: "Confront Dave", conditions: [], results: ["jaccuse_dave"] }, { prompt: "Go somewhere else", conditions: [], results: ["jaccuse_arrival"] }] }, jaccuse_bartender: { text: ["You approach the bartender, who smiles warmly on your approach. The smile fades as you draw your blade.", "As your sword plunges into the man's chest, and you see the light leave his eyes, you know in your heart you've chosen wrong.", "The innocent man falling to the ground before you is the last thing you ever see as a drunken, angry Dave breaks your neck from behind.", "Zarban would later escape, and go on to form a boy band that only sings Nickelback covers.", "", "GAME OVER! Try again?"], effects: [], options: [{ prompt: "New game", conditions: [], results: ["intro_cave1"] }] }, jaccuse_dave: { text: ["Dave meets your gaze as you approach him, the old soldier recognizing the look in your eyes.", `A single tear falls down his cheek as he mutters to himself "I'm coming home, Amy."`, "As your blade pierces his heart and Dave Audemars dies before you, you know in your heart you've chosen wrong.", "A bottle breaks over your head from behind, and you fall to the ground. You'll later awaken just in time for your hanging", "Zarban would later escape, and go on to become the world's most successful spam email marketer.", "", "GAME OVER! Try again?"], effects: [], options: [{ prompt: "New game", conditions: [], results: ["intro_cave1"] }] }, jaccuse_farm_scar: { text: ["You arrive at the farm, and find Arnoulf and his daughter working the field by moonlight.", "The white frills of Amelie's long dress reflect the dim light of the moon"], effects: [], options: [{ prompt: "Confront the old farmer", conditions: [], results: ["jaccuse_farm_arnoulf"] }, { prompt: "Confront the farmer's daughter", conditions: [], results: [{ conditions: [{ type: "inventory", target: "rusty_sword", value: true }], target: "jaccuse_farm_amelie_nosword" }, { conditions: [{ type: "inventory", target: "ancient_armor", value: true }], target: "jaccuse_farm_amelie_good" }, { conditions: [{ type: "inventory", target: "magic_sword", value: true }], target: "jaccuse_farm_amelie_noarmor" }] }, { prompt: "Go somewhere else", conditions: [], results: ["jaccuse_arrival"] }] }, jaccuse_farm: { text: ["You arrive at the farm, and find Arnoulf and his daughter working the field by moonlight."], effects: [], options: [{ prompt: "Confront the old farmer", conditions: [], results: ["jaccuse_farm_arnoulf"] }, { prompt: "Confront the farmer's daughter", conditions: [], results: [{ conditions: [{ type: "inventory", target: "rusty_sword", value: true }], target: "jaccuse_farm_amelie_nosword" }, { conditions: [{ type: "inventory", target: "ancient_armor", value: true }], target: "jaccuse_farm_amelie_good" }, { conditions: [{ type: "inventory", target: "magic_sword", value: true }], target: "jaccuse_farm_amelie_noarmor" }] }, { prompt: "Go somewhere else", conditions: [], results: ["jaccuse_arrival"] }] }, jaccuse_farm_arnoulf: { text: ["You approach the farmer as he toils under the moonlight, and draw your blade.", "The farmer scowls as he raises his pitchfork to confront you.", "You manage to get one good slash in on the farmer's neck... just as his pitchfork pierces your lungs.", "As you both bleed out on the ground, you know you have chosen incorrectly.", "Zarban would later go on to invent a new type of toothpaste that causes cavities instead of preventing them.", "", "GAME OVER! Try again?"], effects: [], options: [{ prompt: "New game", conditions: [], results: ["intro_cave1"] }] }, jaccuse_farm_amelie_nosword: { text: ["You approach the young woman and draw your sword.", "She dons an evil grin and her features twist and distort, as the evil shapeshifter assumes to form of a massive bear!", "You hack and slash away to no avail, as the common steel of your rusted blade cannot kill the vampire.", "As the old farmer tries in vain to help you, your neck is ripped open by his mighty jaws, and you bleed out onto the moonlit field.", "Zarban, revealed and unleashed, will draw strength from the blood of the village by massacring the entire populate of the tiny hamlet.", "Renewed by his bloodbath, Zarban escapes into the world, more powerful than ever before.", "", "The End! Try again?"], effects: [], options: [{ prompt: "New game", conditions: [], results: ["intro_cave1"] }] }, jaccuse_farm_amelie_noarmor: { text: ["You approach the young woman and draw your sword.", "She dons an evil grin and her features twist and distort, as the evil shapeshifter assumes to form of a massive bear!", "You stab the vile creature through it's dark heart, but in his dying breath, the evil sorcerer utters a final curse.", "As you fall to the ground, the air sucked out of your lungs, and slowly choke to death on your own blood,", "You see the evil creature turn to dust before you. You may die, but at least the world is safe from this creature.", "The village will remember you as a brave hero.", "", "The End! Try again?"], effects: [], options: [{ prompt: "New game", conditions: [], results: ["intro_cave1"] }] }, jaccuse_farm_amelie_good: { text: ["You approach the young woman and draw your sword.", "She dons an evil grin and her features twist and distort, as the evil shapeshifter assumes to form of a massive bear!", "You stab the vile creature through it's dark heart, but in his dying breath, the evil sorcerer utters a final curse.", "Your enchanted armor glows bright with ancient runic magic as the vampire's curse is nullified.", "The creature contorts and screeches as it is reduced to dust and ash - the vile one has been vanquished!", "", "Congratulations! You are victorious!"], effects: [], options: [{ prompt: "New game", conditions: [], results: ["intro_cave1"] }] }, jaccuse_davehouse: { text: ["You arrive at Dave's home at the edge of the village, a once beautiful home, but clearly neglected of late.", "Vines and weeds grow rampant across the property, and the windows sit greased and dusty.", "", "No candles burn within, and the door is locked tight."], effects: [], options: [{ prompt: "Go somewhere else", conditions: [], results: ["jaccuse_arrival"] }] }, jaccuse_school: { text: ["You kick down the door of the teacher's humble shack and burst in, sword drawn.", "The teacher and her 6 year old son scream as you run inside."], effects: [], options: [{ prompt: "Confront the teacher", conditions: [], results: ["jaccuse_school_teacher"] }, { prompt: "Confront the son", conditions: [], results: ["jaccuse_school_teacher_child"] }, { prompt: "Go somewhere else", conditions: [], results: ["jaccuse_arrival"] }] }, jaccuse_school_teacher: { text: ["You expertly swing your blade, and take the young woman's head clean off!", "She dies instantly, since she was a schoolteacher - not a vampire.", "You run off as the young boy cries and vows to avenge his mother.", "You spend the rest of your days in hiding, as Zarban goes on to invent a new type of coffee that tastes like burnt popcorn and expired milk.", "", "The End! Try again?"], effects: [], options: [{ prompt: "New game", conditions: [], results: ["intro_cave1"] }] }, jaccuse_school_teacher_child: { text: ["You expertly swing your blade at the young child, but the mother blocks your blade with her body.", "The young woman bleeds out in seconds, and the small cut on the boy's arm reveals the pure red blood of a human child.", "You run off as the young boy cries and vows to avenge his mother.", "You spend the rest of your days in hiding, as Zarban goes on to open a bakery that only sells cakes made with vegetables instead of sugar.", "", "The End! Try again?"], effects: [], options: [{ prompt: "New game", conditions: [], results: ["intro_cave1"] }] }, jaccuse_mill: { text: ["You burst into the stone building and charge to the far side of the room, where the family sits around a fireplace.", "They look up in confused horror at the unexpected home invasion.", "A bandage is wrapped around the baker's hand, clearly a fresh wound."], effects: [], options: [{ prompt: "Confront the baker", conditions: [], results: ["jaccuse_mill_baker"] }, { prompt: "Confront the seamstress", conditions: [], results: ["jaccuse_mill_seamstress"] }, { prompt: "Confront the 8 year old boy", conditions: [], results: ["jaccuse_mill_boy"] }, { prompt: "Confront the twins", conditions: [], results: ["jaccuse_mill_babies"] }, { prompt: "Go somewhere else", conditions: [], results: ["jaccuse_arrival"] }] }, jaccuse_mill_baker: { text: ["You draw your blade and eviscerate the baker and father.", "As his guts spill all over the floor of the mill, the grieving family holds you down until the authorities arrive.", "You will be sentenced to life in prison without parole, from where you will be blissfully unaware of all the puppies Zarban would later eat.", "", "The End! Try again?"], effects: [], options: [{ prompt: "New game", conditions: [], results: ["intro_cave1"] }] }, jaccuse_mill_seamstress: { text: ["You draw your blade and stab the young mother of 3 through the eye.", "As she falls to the ground and you realize the gravity of your mistake, a cast iron pan to the forehead puts you out of everyone's misery.", "Zarban would later go on to traffic in baby spines for fun and profit.", "", "The End! Try again?"], effects: [], options: [{ prompt: "New game", conditions: [], results: ["intro_cave1"] }] }, jaccuse_mill_boy: { text: ["You draw your sword and slash at the young boy's throat.", "The boy uses his arms to shield himself, and survives - barely - your blow.", "The pure red blood of a human tells you that a mistake has been made as his mother pierces your brain with a fork through the eye.", "Zarban would later go on to rethink his life and become a nun. Just kidding he eats children now.", "", "The End! Try again?"], effects: [], options: [{ prompt: "New game", conditions: [], results: ["intro_cave1"] }] }, jaccuse_mill_babies: { text: ["You draw your sword and approach the sleeping newborn babies.", "You stop yourself from murdering two babies at the last second and decide to go to the tavern to rethink your life.", "The closecall has scarred you, and you no longer wish to hunt vampires.", "Zarban would later to on to invest heavily into dogecoin.", "", "The End! Try again?"], effects: [], options: [{ prompt: "New game", conditions: [], results: ["intro_cave1"] }] } }, chapter: "3" }], choices: { $schema: "../schema/player.choices.zarban.schema.json", records: { made_dave_go_home: { description: "Spoke to Dave in the tavern, making him go back to his home" }, made_dave_sad: { description: "Was rude to Dave, who will no longer help you" }, learnt_about_priest: { description: "Learnt about the church a day's walk down the road from the village" }, accepted_bread: { description: "Accepted bread from the baker" }, snooped_on_teacher: { description: "Snooped on the teacher's shack" }, insulted_teacher: { description: "Teacher found out about your snooping" }, impressed_gaylen: { description: "Impressed the priest with your kindness" }, learnt_about_hunter: { description: "Learnt about the famous hunter (and his sword)" }, injured_zarban: { description: "Injured Zarban's legs" } } }, inventory: { $schema: "../schema/player.inventory.zarban.schema.json", records: { hunter_sword: { description: "Vampire hunter's sword", equipped: false, effects: [] }, hunter_armor: { description: "Vampire hunter's armor", equipped: false, effects: [] }, rusty_sword: { description: "Rusty shortsword", equipped: false, effects: [] }, old_armor: { description: "Soldier's leather cuirass", equipped: false, effects: [{ type: "status", target: "stamina", operation: "add_max", value: 2 }, { type: "status", target: "stamina", operation: "add", value: 2 }] }, magic_sword: { description: "Moonsbane", equipped: false, effects: [] }, ancient_armor: { description: "Edwin Rothsten's armor", equipped: false, effects: [{ type: "status", target: "stamina", operation: "add_max", value: 5 }, { type: "status", target: "stamina", operation: "add", value: 5 }] }, hunter_tomb_key: { description: "Key to the vampire hunter's tomb", equipped: false, effects: [] }, dave_flowers: { description: "Flower's for Dave's family grave", equipped: false, effects: [] } } }, status: { $schema: "../schema/player.status.zarban.schema.json", records: { stamina: { hidden: false, default: 0, maximum: 3 }, alcoholism: { hidden: true, default: 0, maximum: 2 } } } }; -g.data && (g = m.base64Decode(g.data), g = JSON.parse(g)); -class y { - constructor(t2) { - m.assign(this, g), void 0 !== t2 && m.assign(this, t2), this.choices = new e(this.choices), this.inventory = new n(this.inventory), this.status = new l(this.status); - for (const e2 in this.chapters) - this.chapters[e2] = new u(this.chapters[e2]); - void 0 === this.currentStoryKey && this.setStory(this.entrypoint); +const H = "../schema/player.zarban.schema.json", W = "intro_cave1", J = [ + { + $schema: "../schema/chapter.zarban.schema.json", + name: "Chapter 1: The Grotto", + records: { + intro_cave1: { + text: [ + "Drenched from the rain, and exhausted from having hunted all through the night, you have finally cornered the beast which you have been hired to dispatch.", + "You approach the forboding cavern to which you have stalked your prey, the shapeshifting vampire Zarban.", + "The foul stench of magic fills your nostrils as you prepare to enter the grotto proper." + ], + effects: [ + { + type: "choices", + target: "all", + value: !1 + }, + { + type: "inventory", + target: "all", + value: !1 + }, + { + type: "status", + target: "all", + operation: "add", + value: -99 + }, + { + type: "status", + target: "stamina", + operation: "add", + value: 2 + }, + { + type: "inventory", + target: "hunter_sword", + value: !0 + }, + { + type: "inventory", + target: "hunter_armor", + value: !0 + } + ], + options: [ + { + prompt: "Enter the grotto", + conditions: [], + results: [ + "intro_cave2" + ] + } + ] + }, + intro_cave2: { + text: [ + "In the darkness before you, deep within the cave looms a vile shadow, dripping with evil.", + "It can only be the mighty vampire Zarban himself." + ], + effects: [], + options: [ + { + prompt: "Draw your magic sword and approach the shadow", + conditions: [], + results: [ + "intro_cave3_brave" + ] + }, + { + prompt: "I don't care about vampires, let's go to the tavern", + conditions: [], + results: [ + "intro_cave3_tavern" + ] + } + ] + }, + intro_cave3_brave: { + text: [ + "As you prepare yourself and draw your enchanted blade, you are knocked out from behind by a large rock to the head.", + "You collapse to the ground, unconcious as the mighty Zarban scurries away into the night, cackling annoyingly." + ], + effects: [], + options: [ + { + prompt: "...", + conditions: [], + results: [ + "intro_cave4" + ] + } + ] + }, + intro_cave3_tavern: { + text: [ + "As you turn around to give up your promising career as a mediocre vampire-hunter for hire, you are knocked out from behind by a large rock to the head.", + "You collapse to the ground, unconcious as the mighty Zarban scurries away into the night, cackling annoyingly." + ], + effects: [], + options: [ + { + prompt: "...", + conditions: [], + results: [ + "intro_cave4" + ] + } + ] + }, + intro_cave4: { + text: [ + "You awaken some time later, to find your magic blade, and mint-condition vintage vampire hunting armor reduced to worthless scrap before you.", + "You gather what little you can salvage, and turn to leave the grotto.", + "", + "Before you lies a single set of footprints, leading away from the grotto." + ], + effects: [ + { + type: "inventory", + target: "all", + value: !1 + } + ], + options: [ + { + prompt: "Follow the footprints", + conditions: [], + results: [ + "intro_cave5_brave" + ] + }, + { + prompt: "I don't care about footprints, let's go to the tavern", + conditions: [], + results: [ + "intro_cave5_tavern" + ] + } + ] + }, + intro_cave5_brave: { + text: [ + "You follow the footprints to a nearby village, and arrive just as dawn breaks.", + "The footprints lead into the village, but too many footprints coming and going make it impossible to tell what happened next.", + "", + "One thing you can be sure of, however; you still sense Zarban's evil aura - you are sure he is still hiding out somewhere in this very town", + "Likely having replaced one of the sleepy village's unsuspecting peasants.", + "You are exausted from searching through the night, and could use a pick-me-up." + ], + effects: [], + options: [ + { + prompt: "Look around", + conditions: [], + results: [ + "village" + ] + } + ] + }, + intro_cave5_tavern: { + text: [ + "You walk to a tavern in a nearby village, and arrive just as dawn breaks.", + "", + "As you approach the village, you once again sense Zarban's evil aura - you are sure he is hiding out somewhere in this very town", + "Likely having replaced one of the sleepy village's unsuspecting peasants." + ], + effects: [], + options: [ + { + prompt: "Enter the tavern", + conditions: [], + results: [ + "tavern_enter" + ] + } + ] + } + }, + chapter: "0" + }, + { + $schema: "../schema/chapter.zarban.schema.json", + name: "Chapter 2: Footprints", + records: { + tavern_enter: { + text: [ + "You enter the tavern.", + "Looking around you see several people of interest milling about the small village bar." + ], + effects: [], + options: [ + { + prompt: "Take a seat at the bar", + conditions: [], + results: [ + "tavern" + ] + }, + { + prompt: "Leave the tavern", + conditions: [], + results: [ + { + conditions: [ + { + type: "status", + target: "stamina", + operation: "eq", + value: 0 + } + ], + target: "village_ending_stamina" + }, + "village" + ] + } + ] + }, + tavern_ending_alcoholic: { + text: [ + "You take your familiar seat back at the bar, and order another round.", + "Having clearly decided to give up vampire hunting for a promising new career in alcoholism, you decide to let Zarban live.", + "Zarban would later go on to burn down 27 orphanages in your name.", + "", + "GAME OVER! Try again?" + ], + effects: [], + options: [ + { + prompt: "New game", + conditions: [], + results: [ + "intro_cave1" + ] + } + ] + }, + tavern: { + text: [ + "You take a seat on a ramshackle stool at the tavern bar." + ], + effects: [], + options: [ + { + prompt: "Speak to the bartender", + conditions: [], + results: [ + "tavern_bartender" + ] + }, + { + prompt: "Approach the suspicious hooded figure lurking in the corner", + conditions: [ + { + type: "choices", + target: "made_dave_go_home", + value: !1 + } + ], + results: [ + { + target: "tavern_lurker", + conditions: [ + { + type: "choices", + target: "made_dave_sad", + value: !1 + } + ] + }, + { + target: "tavern_lurker_sad", + conditions: [ + { + type: "choices", + target: "made_dave_sad", + value: !0 + } + ] + } + ] + }, + { + prompt: "Approach the old farmer drinking alone at the bar", + conditions: [], + results: [ + "tavern_farmer" + ] + }, + { + prompt: "Leave the tavern", + conditions: [], + results: [ + { + conditions: [ + { + type: "status", + target: "stamina", + operation: "eq", + value: 0 + } + ], + target: "village_ending_stamina" + }, + "village" + ] + } + ] + }, + tavern_bartender: { + text: [ + "You approach the tired looking bartender, intending to vigorously question the overworked customer-service employee.", + `"Back for another already? What can I get'cha, stranger?"` + ], + effects: [], + options: [ + { + prompt: "Ask if any strangers have come through town recently", + conditions: [], + results: [ + "tavern_bartender_strangers" + ] + }, + { + prompt: "Ask if anyone in town sells weapons and armor", + conditions: [], + results: [ + "tavern_bartender_blacksmith" + ] + }, + { + prompt: "I could use a refreshing drink", + conditions: [], + results: [ + { + conditions: [ + { + type: "status", + target: "alcoholism", + value: 1, + operation: "eq" + } + ], + target: "tavern_ending_alcoholic" + }, + "tavern_bartender_shots" + ] + }, + { + prompt: "Go back to your stool", + conditions: [], + results: [ + "tavern" + ] + } + ] + }, + tavern_bartender_strangers: { + text: [ + "You ask the bartender about other strangers that have passed through town.", + "", + `"Besides yourself, m'lord? Just one, earlier this very morn'! He'll be long gone by now though, headed out in something of a hurry."`, + `"Ye could always ask Gaylen, the priest 'round' these parts - his shack'll be a good halfday's walk from here, north of the village."` + ], + effects: [ + { + type: "choices", + target: "learnt_about_priest", + value: !0 + } + ], + options: [ + { + prompt: "Ask the bartender more questions", + conditions: [], + results: [ + "tavern_bartender" + ] + }, + { + prompt: "Go back to your stool", + conditions: [], + results: [ + "tavern" + ] + } + ] + }, + tavern_bartender_blacksmith: { + text: [ + "You ask the bartender where you might acquire new weapons and armor.", + "", + `"Well, you're not likely to find a big fancy smithy in our little village, m'lord"`, + `"That said, Dave over yonder has been known to fix a rake in his time, if you catch my meanin'"`, + "The bartender laughs at his attempted joke, as he points to a hooded figure drinking alone in the corner of the bar." + ], + effects: [], + options: [ + { + prompt: "Ask the bartender more questions", + conditions: [], + results: [ + "tavern_bartender" + ] + }, + { + prompt: "Go back to your stool", + conditions: [], + results: [ + "tavern" + ] + } + ] + }, + tavern_bartender_shots: { + text: [ + "You ask the bartender for a glass of his strongest brew, and down it in once mighty gulp.", + "Your stamina has been restored" + ], + effects: [ + { + type: "status", + target: "stamina", + operation: "add", + value: 99 + }, + { + type: "status", + target: "alcoholism", + operation: "add", + value: 1 + } + ], + options: [ + { + prompt: "Ask the bartender more questions", + conditions: [], + results: [ + "tavern_bartender" + ] + }, + { + prompt: "Go back to your stool", + conditions: [], + results: [ + "tavern" + ] + } + ] + }, + tavern_lurker: { + text: [ + "You approach the hooded stranger, clearly an imposing figure. The hulking goliath of a man looks up at you with a scowl on his face.", + `"What do ye' want, stranger. I ain't in no mood fer conversin' with the likes of you today."` + ], + effects: [], + options: [ + { + prompt: "I KNOW YOU'RE THE VAMPIRE, YOU MONSTER! PREPARE TO DIE!", + conditions: [], + results: [ + "tavern_lurker_sad" + ] + }, + { + prompt: "Ask where you might be able to acquire some new weapons and armor", + conditions: [], + results: [ + "tavern_lurker_smithy" + ] + }, + { + prompt: "Ask if he's seen anything unusual", + conditions: [], + results: [ + "tavern_lurker_unusual" + ] + }, + { + prompt: "Go back to your stool", + conditions: [], + results: [ + "tavern" + ] + } + ] + }, + tavern_lurker_smithy: { + text: [ + `"Well I haven't the skills to make such a thing, if that's what yer asking, but I was a soldier in me youth."`, + '"Meet me at my house just outside the village. Could use a hand with something, then I think I can help ye."' + ], + effects: [ + { + type: "choices", + target: "made_dave_go_home", + value: !0 + } + ], + options: [ + { + prompt: "Go back to your stool", + conditions: [], + results: [ + "tavern" + ] + } + ] + }, + tavern_lurker_unusual: { + text: [ + `"Wouldn't know nothin' about that, stranger. Leave me be."` + ], + effects: [], + options: [ + { + prompt: "Ask him more questions", + conditions: [], + results: [ + "tavern_lurker" + ] + }, + { + prompt: "Go back to your stool", + conditions: [], + results: [ + "tavern" + ] + } + ] + }, + tavern_lurker_sad: { + text: [ + "The large muscular man before you begins to cry uncontrollably, tears streaming down his bearded visage.", + `"Just leave me alone! You're really mean... G... Go away..."`, + "", + "The mighty Zarban would never debase himself so. This is clearly not he." + ], + effects: [ + { + type: "choices", + target: "made_dave_sad", + value: !0 + } + ], + options: [ + { + prompt: "Go back to your stool", + conditions: [], + results: [ + "tavern" + ] + } + ] + }, + tavern_farmer: { + text: [ + "An old man sits at the bar next to you, his skin wrinkled from years of hard labour under the sun." + ], + effects: [], + options: [ + { + prompt: "Ask him how a farmer has time to drink in broad daylight", + conditions: [], + results: [ + "tavern_farmer_sun" + ] + }, + { + prompt: "Make small talk", + conditions: [], + results: [ + "tavern_farmer_daughter" + ] + }, + { + prompt: "Ask if anyone in town sells weapons and armor", + conditions: [], + results: [ + "tavern_farmer_blacksmith" + ] + }, + { + prompt: "Ask if he's seen anything unusual", + conditions: [], + results: [ + "tavern_farmer_unusual" + ] + }, + { + prompt: "Go back to your stool", + conditions: [], + results: [ + "tavern" + ] + } + ] + }, + tavern_farmer_sun: { + text: [ + '"Never much cared for the sun is all. Do most of me work by night these days. What business is it of yours, anyway?"' + ], + effects: [], + options: [ + { + prompt: "Continue talking to the farmer", + conditions: [], + results: [ + "tavern_farmer" + ] + }, + { + prompt: "Go back to your stool", + conditions: [], + results: [ + "tavern" + ] + } + ] + }, + tavern_farmer_unusual: { + text: [ + `"Unusual? Two strangers in one night is unusual. It's also annoying. Can I get back to me drink now?"` + ], + effects: [], + options: [ + { + prompt: "Continue talking to the farmer", + conditions: [], + results: [ + "tavern_farmer" + ] + }, + { + prompt: "Go back to your stool", + conditions: [ + { + type: "status", + target: "alcoholism", + value: 2, + operation: "lt" + } + ], + results: [ + "tavern" + ] + } + ] + }, + tavern_farmer_daughter: { + text: [ + '"Not much of interest happens round these parts, stranger."', + `"Me daughter's been aweful moody lately, but tis the norm for the young'uns these days."` + ], + effects: [], + options: [ + { + prompt: "Continue talking to the farmer", + conditions: [], + results: [ + "tavern_farmer" + ] + }, + { + prompt: "Go back to your stool", + conditions: [], + results: [ + "tavern" + ] + } + ] + }, + tavern_farmer_blacksmith: { + text: [ + `"You could try Dave, round back. He's the hooded feller with crippling emotional issues."`, + `"He's been known to fix a rake in his time, if you catch my meanin'"`, + "The farmer laughs heartily, clearly pleased with his joke." + ], + effects: [], + options: [ + { + prompt: "Continue talking to the farmer", + conditions: [], + results: [ + "tavern_farmer" + ] + }, + { + prompt: "Go back to your stool", + conditions: [], + results: [ + "tavern" + ] + } + ] + }, + village_ending_stamina: { + text: [ + "Exhausted and thirsty, you collapse to the ground. The villagers find you, and bring you to the inn to recover.", + "Unfortunately, by then Zarban is long gone, and the trail cold. He will later go on to become the CEO of Nestle.", + "", + "GAME OVER! Try again?" + ], + effects: [], + options: [ + { + prompt: "New game", + conditions: [], + results: [ + "intro_cave1" + ] + } + ] + }, + village: { + text: [ + "You stand on the main road crossing the village. A nearby sign identifies the town as Rothsten." + ], + effects: [ + { + type: "status", + target: "stamina", + operation: "add", + value: -1 + } + ], + options: [ + { + prompt: "Search the farm", + conditions: [], + results: [ + { + conditions: [ + { + type: "status", + target: "stamina", + operation: "eq", + value: 0 + } + ], + target: "village_ending_stamina" + }, + "village_farm" + ] + }, + { + prompt: "Search Dave's house", + conditions: [], + results: [ + { + conditions: [ + { + type: "status", + target: "stamina", + operation: "eq", + value: 0 + } + ], + target: "village_ending_stamina" + }, + { + conditions: [ + { + type: "choices", + target: "made_dave_go_home", + value: !1 + } + ], + target: "village_dave_not_home" + }, + { + conditions: [ + { + type: "choices", + target: "made_dave_sad", + value: !0 + } + ], + target: "village_dave_not_home" + }, + "village_dave" + ] + }, + { + prompt: "Search the school", + conditions: [], + results: [ + { + conditions: [ + { + type: "status", + target: "stamina", + operation: "eq", + value: 0 + } + ], + target: "village_ending_stamina" + }, + "village_school_outside" + ] + }, + { + prompt: "Search the mill", + conditions: [], + results: [ + { + conditions: [ + { + type: "status", + target: "stamina", + operation: "eq", + value: 0 + } + ], + target: "village_ending_stamina" + }, + "village_mill" + ] + }, + { + prompt: "Search the tavern", + conditions: [], + results: [ + "tavern_enter" + ] + }, + { + prompt: "Leave the village and head to the priest's cottage", + conditions: [ + { + type: "choices", + target: "learnt_about_priest", + value: !0 + } + ], + results: [ + { + conditions: [ + { + type: "status", + target: "stamina", + operation: "eq", + value: 0 + } + ], + target: "village_ending_stamina" + }, + "church_bear" + ] + } + ] + }, + village_farm: { + text: [ + "You arrive at a well maintained farmhouse. You walk into the house, and see a young woman crushing herbs with a mortar & pestle.", + "Based on her attire - and the visibility of her assets - you wonder if her clothes were the victim of a shrinking spell.", + "She does not seem surprised to see you, not so much as looking up from her work.", + '"A vampire hunter in our little corner of the world? What could possibly bring you here?" She asks you.' + ], + effects: [], + options: [ + { + prompt: "Speak to the young woman", + conditions: [], + results: [ + "village_farm_amelie" + ] + }, + { + prompt: "Look around the humble farmhouse", + conditions: [], + results: [ + "village_farm_search" + ] + }, + { + prompt: "Leave the farm", + conditions: [], + results: [ + "village" + ] + } + ] + }, + village_farm_amelie: { + text: [ + '"Need a potion? Or a salve?" The young woman asks' + ], + effects: [], + options: [ + { + prompt: "Ask her for a potion of vampire detection", + conditions: [], + results: [ + "village_farm_amelie_potion" + ] + }, + { + prompt: "Ask her about her family", + conditions: [], + results: [ + "village_farm_amelie_family" + ] + }, + { + prompt: "Ask her about herself", + conditions: [], + results: [ + "village_farm_amelie_self" + ] + }, + { + prompt: "Ask if she's seen anything unusual", + conditions: [], + results: [ + "village_farm_amelie_unusual" + ] + }, + { + prompt: "Stop speaking to the woman", + conditions: [], + results: [ + "village_farm" + ] + } + ] + }, + village_farm_amelie_potion: { + text: [ + `The woman looks at you confused; "Is that a real potion? I'm afraid I wouldn't know how to make this." The young woman replies`, + '"I have a potion to cure rhumatism, if that interests you, or the best salve for warts this side of the continent!"' + ], + effects: [], + options: [ + { + prompt: "Ask her something else", + conditions: [], + results: [ + "village_farm_amelie" + ] + } + ] + }, + village_farm_amelie_unusual: { + text: [ + '"Unusual?" The young woman laughs', + `"Honey, having a stranger on our farm is the most unusual thing that's happened in recent memory."` + ], + effects: [], + options: [ + { + prompt: "Ask her something else", + conditions: [], + results: [ + "village_farm_amelie" + ] + } + ] + }, + village_farm_amelie_family: { + text: [ + `"Our family? It's an interesting enough tale, I suppose." The young woman begins`, + '"My father is Arnoulf, of House Brolette - yes those Brolettes."', + `"In the days of the Aremeic Order, our family laid claim to the thrones of half the Order's colonial territories on the East continent!"`, + `"My father doesn't like to talk about it, and there's not much left of the family, but it's a proud history to bear."` + ], + effects: [], + options: [ + { + prompt: "Ask her something else", + conditions: [], + results: [ + "village_farm_amelie" + ] + } + ] + }, + village_farm_amelie_self: { + text: [ + `"Me? I am Amelie of House Brolette; I'm the village herbalist, not that my skills net me much respect in this hole of a village."`, + '"I provide these simpletons with medicines and salves, but nobody can see past my choice of clothes. Nobody would bat an eye in the cities."', + `"Ever try foraging for mushrooms in 3 layers of frill and fuss? You'd never catch me in some silly long dress."` + ], + effects: [], + options: [ + { + prompt: "Ask her something else", + conditions: [], + results: [ + "village_farm_amelie" + ] + } + ] + }, + village_farm_search: { + text: [ + "You take a good look around the room.", + "In the corner are a pair of small beds, one of which clearly hasn't seem much use of late.", + "In the kitchen, various herbs and mushrooms hang from the ceiling.", + "Supplies for alchemical brewing are strewn about." + ], + effects: [], + options: [ + { + prompt: "Speak to the young woman", + conditions: [], + results: [ + "village_farm_amelie" + ] + }, + { + prompt: "Leave the farm", + conditions: [], + results: [ + "village" + ] + } + ] + }, + village_dave_not_home: { + text: [ + "You arrive at Dave's home at the edge of the village, a once beautiful home, but clearly neglected of late.", + "Vines and weeds grow rampant across the property, and the windows sit greased and dusty.", + "", + "No candles burn within, and the door is locked tight." + ], + effects: [], + options: [ + { + prompt: "Leave Dave's house", + conditions: [], + results: [ + "village" + ] + } + ] + }, + village_dave: { + text: [ + "You arrive at Dave's home at the edge of the village, a once beautiful home, but clearly neglected of late.", + "Vines and weeds grow rampant across the property, and the windows sit greased and dusty.", + "", + "The door sits open, so you walk inside. Dave sits alone on a dusty old wooden chair.", + "All around him sits dusty disused brushes, paints and canvases" + ], + effects: [], + options: [ + { + prompt: "Talk to Dave", + conditions: [], + results: [ + "village_dave_dave" + ] + }, + { + prompt: "Leave Dave's house", + conditions: [], + results: [ + "village" + ] + } + ] + }, + village_dave_dave: { + text: [ + `"So, ye needed a weapon, ey? I do have me gear from the war that I'd be happy enough to never see again."`, + `"Not usually in the habit of armin' strangers though, so ye'd best have a good cause."`, + `"My wife, god rest 'er soul didn't approve of wanton violence."` + ], + effects: [], + options: [ + { + prompt: "Tell him you are hunting a vampire hiding amongst the villagers", + conditions: [], + results: [ + "village_dave_dave_honest" + ] + }, + { + prompt: "Tell him you are avenging the death of a loved one", + conditions: [], + results: [ + "village_dave_dave_lie" + ] + }, + { + prompt: "Tell him to give you the gear or you'll take it by force", + conditions: [], + results: [ + "village_dave_dave_threaten" + ] + }, + { + prompt: "Leave Dave's house", + conditions: [], + results: [ + "village" + ] + } + ] + }, + village_dave_dave_honest: { + text: [ + "Dave sits in silence for an awkwardly long time.", + `"There's something in the air today; as if the sky itself were thick with dread."`, + `"I believe you, stranger. My sword and armor are yours. They ain't much but I hope they help you."`, + '"All I ask is that you lay these here flowers at my family grave. The walk to the temple is too much for this old soldier."', + `"Ye'll see it marked by the clan name Audemars."`, + "", + "The rusted old blade would never be able to strike a killing blow against such a powerful vampire;", + "But it may grant some protection, at the very least." + ], + effects: [ + { + type: "choices", + target: "made_dave_sad", + value: !0 + }, + { + type: "inventory", + target: "rusty_sword", + value: !0 + }, + { + type: "inventory", + target: "old_armor", + value: !0 + }, + { + type: "inventory", + target: "dave_flowers", + value: !0 + } + ], + options: [ + { + prompt: "Leave Dave's house", + conditions: [], + results: [ + "village" + ] + } + ] + }, + village_dave_dave_lie: { + text: [ + "Dave slowly shakes his head.", + '"Sorry stranger, I cannae in good conscience lend creed to vengence. I hope you find a way to see another path."', + `"In the meantime, I'm afraid ye'll need to leave."` + ], + effects: [ + { + type: "choices", + target: "made_dave_sad", + value: !0 + } + ], + options: [ + { + prompt: "Leave Dave's house", + conditions: [], + results: [ + "village" + ] + } + ] + }, + village_dave_dave_threaten: { + text: [ + "Without any warning, the giant of a man hits you on the side of the head with one mighty punch.", + "You awaken outside the house, with the door now securely locked." + ], + effects: [ + { + type: "choices", + target: "made_dave_sad", + value: !0 + } + ], + options: [ + { + prompt: "Leave Dave's house", + conditions: [], + results: [ + "village" + ] + } + ] + }, + village_mill: { + text: [ + "You arive at the small village mill, and step inside", + "The stone brick building is small, but impressive - clearly much older than the wooden structures you've seen thus far.", + "Inside are a man, expertly kneading a large ball of dough, and a woman on a wooden rocking chair repairing a tunic.", + "", + `"Ah, we heard there were strangers about!" says the man; "Something ye' knead? Some bread for the road perhaps?"` + ], + effects: [], + options: [ + { + prompt: "Take the man up on his offer of bread", + conditions: [ + { + type: "choices", + target: "accepted_bread", + value: !1 + } + ], + results: [ + "village_mill_bread" + ] + }, + { + prompt: "Ask if they have garlic bread instead", + conditions: [ + { + type: "choices", + target: "accepted_bread", + value: !1 + } + ], + results: [ + "village_mill_garlic" + ] + }, + { + prompt: "Ask about their family", + conditions: [], + results: [ + "village_mill_family" + ] + }, + { + prompt: "Ask if they've seen anything unusual", + conditions: [], + results: [ + "village_mill_unusual" + ] + }, + { + prompt: "Leave the mill", + conditions: [], + results: [ + "village" + ] + } + ] + }, + village_mill_family: { + text: [ + `"We're the Fouchers sir - Nils and Silvia. Our family's been in these parts since the colonial days of the Order."`, + `"We've 3 little 'uns - Dylan is 8, and the twins are fresh out the oven."` + ], + effects: [], + options: [ + { + prompt: "Ask something else", + conditions: [], + results: [ + "village_mill" + ] + } + ] + }, + village_mill_unusual: { + text: [ + '"Not a thing of interest has happened in these here parts in many a year, sir."', + `"If it's adventure ye' seek, you've picked the wrong village I'm afraid."` + ], + effects: [], + options: [ + { + prompt: "Ask something else", + conditions: [], + results: [ + "village_mill" + ] + } + ] + }, + village_mill_garlic: { + text: [ + "You narrow your eyes in suspicion as the question leaves your lips", + `The man shakes his head; "Nay, stranger. I've a terrible allergy to garlic, so none of that 'ere."` + ], + effects: [], + options: [ + { + prompt: "Ask something else", + conditions: [], + results: [ + "village_mill" + ] + } + ] + }, + village_mill_bread: { + text: [ + "The baker hands you a fresh loaf of bread, hot out of the oven,", + "The family then looks on in horror as you devour the entire loaf in one go like some kind of feral beast" + ], + effects: [ + { + type: "choices", + target: "accepted_bread", + value: !0 + }, + { + type: "status", + target: "stamina", + operation: "add", + value: 99 + } + ], + options: [ + { + prompt: "Ask something else", + conditions: [], + results: [ + "village_mill" + ] + } + ] + }, + village_school_outside: { + text: [ + "You arrive outside the small schoolhouse. A humble shack sits next to it, likely a home for the teacher and her family.", + "A gaggle of annoying looking children mill about uselessly outside the school." + ], + effects: [], + options: [ + { + prompt: "Search the shack", + conditions: [], + results: [ + "village_school_shack" + ] + }, + { + prompt: "Search the schoolhouse", + conditions: [], + results: [ + "village_school_inside" + ] + }, + { + prompt: "Harass the annoying children", + conditions: [], + results: [ + "village_school_children" + ] + }, + { + prompt: "Leave the school", + conditions: [], + results: [ + "village" + ] + } + ] + }, + village_school_inside: { + text: [ + "You walk into the schoolhouse.", + "A woman - the teacher, presumably - is sat at the far side of the room.", + "On her head sits the biggest hat you have ever seen. This sunhat has a wingspan that could make a dragon jealous." + ], + effects: [], + options: [ + { + prompt: "Ask her about herself", + conditions: [], + results: [ + { + conditions: [ + { + type: "choices", + target: "insulted_teacher", + value: !0 + } + ], + target: "village_school_items" + }, + "village_school_teacher" + ] + }, + { + prompt: "Ask if she's seen anything unusual", + conditions: [], + results: [ + { + conditions: [ + { + type: "choices", + target: "insulted_teacher", + value: !0 + } + ], + target: "village_school_items" + }, + "village_school_unusual" + ] + }, + { + prompt: "Ask about the strange items in her home", + conditions: [ + { + type: "choices", + target: "snooped_on_teacher", + value: !0 + } + ], + results: [ + "village_school_items" + ] + }, + { + prompt: "Ask about her gigantic hat", + conditions: [], + results: [ + { + conditions: [ + { + type: "choices", + target: "insulted_teacher", + value: !0 + } + ], + target: "village_school_items" + }, + "village_school_hat" + ] + }, + { + prompt: "Look somewhere else", + conditions: [], + results: [ + "village_school_outside" + ] + } + ] + }, + village_school_items: { + text: [ + `"What kind of creepy imbecile looks in a young woman's windows at her private things?"`, + '"Leave my classroom this instant!"' + ], + effects: [ + { + type: "choices", + target: "insulted_teacher", + value: !0 + } + ], + options: [ + { + prompt: "Ask something else", + conditions: [], + results: [ + "village_school_inside" + ] + } + ] + }, + village_school_unusual: { + text: [ + '"Besides the usual nonsense from that horrible Brolette girl? Not in a long time."' + ], + effects: [], + options: [ + { + prompt: "Ask something else", + conditions: [], + results: [ + "village_school_inside" + ] + } + ] + }, + village_school_teacher: { + text: [ + `"My family? nothing special, I'm afraid. Lost my eldest and my husband two winters back to red fever."`, + '"Milton is outside with his friends - mercifully he was too young to remember it."' + ], + effects: [], + options: [ + { + prompt: "Ask something else", + conditions: [], + results: [ + "village_school_inside" + ] + } + ] + }, + village_school_hat: { + text: [ + "The young woman laughs as she grips her cap", + `"I've a skin condition, always have - the sun; my skin burns so easily"`, + '"I use the hat to protect myself, along with a salve I make at home!"', + `"I could buy it from that Brolette girl, but I'd rather not have to interact with that one."` + ], + effects: [], + options: [ + { + prompt: "Ask something else", + conditions: [], + results: [ + "village_school_inside" + ] + } + ] + }, + village_school_shack: { + text: [ + "The shack is locked but you look in through the window.", + "You can see strange objects strewn about - a dagger, various herbs, an ornate pendant covered in runes, and a pouch of white powder" + ], + effects: [ + { + type: "choices", + target: "snooped_on_teacher", + value: !0 + } + ], + options: [ + { + prompt: "Look somewhere else", + conditions: [], + results: [ + "village_school_outside" + ] + } + ] + }, + village_school_children: { + text: [ + "You walk up to the children and kick sand at them like a complete arsehole.", + "One child begins to try, while another kicks you in the groin and runs away giggling." + ], + effects: [ + { + type: "status", + target: "stamina", + operation: "add", + value: -1 + } + ], + options: [ + { + prompt: "Look somewhere else", + conditions: [], + results: [ + "village_school_outside" + ] + } + ] + } + }, + chapter: "1" + }, + { + $schema: "../schema/chapter.zarban.schema.json", + name: "Chapter 3: The Hunter, Hunted", + records: { + church_bear: { + text: [ + "You head down the wooded path towards the temple.", + "As you enter the forest, the air around you grows cold and the sun itself begins to dim.", + "", + "You hear a mighty roar from behind you." + ], + effects: [], + options: [ + { + prompt: "Turn around and face your foe", + conditions: [], + results: [ + { + conditions: [ + { + type: "inventory", + target: "rusty_sword", + value: !0 + } + ], + target: "church_bear_armed" + }, + "church_bear_unarmed" + ] + }, + { + prompt: "Run away from the creature", + conditions: [], + results: [ + "church_bear_coward" + ] + } + ] + }, + church_bear_coward: { + text: [ + "You bravely run away, as fast as you can into the woods, towards the temple, and Gaylen the town priest.", + "You arrive at the temple late in the afternoon, and see the priest standing outside the ornate stone building.", + "Nearby you can see a graveyard, with a massive ornate tomb at its center.", + "The priest approches you:", + '"Greetings, stranger. How may I be of service?"' + ], + effects: [], + options: [ + { + prompt: "Speak to the priest", + conditions: [], + results: [ + "church_gaylen" + ] + } + ] + }, + church_bear_unarmed: { + text: [ + "You turn to face your foe and come face to crotch with a 12ft tall monster of a bear.", + "You instinctively reach for your blade, but of course, you don't have one.", + "You start to run away, but the mighty beast - no doubt the shapeshifter himself - rips you open with a mighty swipe of his paw", + "", + "It will be daybreak before you are found by the villagers, and after numerous surgeries and expensive physiotherapy you regain the ability to walk", + "But by then, when no one was looking, Zarban took forty cakes.", + "That's as many as four tens.", + "And that's terrible.", + "", + "GAME OVER! Try again?" + ], + effects: [], + options: [ + { + prompt: "New game", + conditions: [], + results: [ + "intro_cave1" + ] + } + ] + }, + church_bear_armed: { + text: [ + "You turn to face your foe and come face to crotch with a 12ft tall monster of a bear.", + "You instinctively reach for your blade, and draw the corroded shortsword you were given.", + "The mighty beast looms over you, menace in his eyes. The beast drips with magic and malice;", + "You know in your heart that can only be Zarban the undead shapeshifter himself." + ], + effects: [], + options: [ + { + prompt: "Run away very very quickly", + conditions: [], + results: [ + "church_bear_armed_coward" + ] + }, + { + prompt: "Go for the beast's head", + conditions: [], + results: [ + "church_bear_armed_stupid" + ] + }, + { + prompt: "Slash at the beasts legs", + conditions: [], + results: [ + "church_bear_armed_legs" + ] + } + ] + }, + church_bear_armed_coward: { + text: [ + "You start to run away, but the mighty beast rips your back open with a mighty swipe of his paw", + "", + "It will be daybreak before you are found by the villagers, and after numerous surgeries and expensive physiotherapy you regain the ability to walk", + "But by then, Zarban is long gone. He would later go on to start a mediocre reaction channel on TikTok.", + "", + "GAME OVER! Try again?" + ], + effects: [], + options: [ + { + prompt: "New game", + conditions: [], + results: [ + "intro_cave1" + ] + } + ] + }, + church_bear_armed_stupid: { + text: [ + "You stab directly upwards into the beasts head, aiming to finish this now and here.", + "The common steel of the blade cannot deal a finishing blow against such a powerful magical foe, however and the metal passes right through the beast.", + "While you leave your flank fully exposed, Zarban disembowels you with ease.", + "Zarban would later go on to become a successful corporate attorney.", + "", + "GAME OVER! Try again?" + ], + effects: [], + options: [ + { + prompt: "New game", + conditions: [], + results: [ + "intro_cave1" + ] + } + ] + }, + church_bear_armed_legs: { + text: [ + "You slash at the beast's legs, causing him to roar in pain. You can't kill him with this sword, but even if he takes on another form,", + "It will take days for him to heal from such a blow." + ], + effects: [ + { + type: "choices", + target: "injured_zarban", + value: !0 + } + ], + options: [ + { + prompt: "Escape while the beast reels from the blow", + conditions: [], + results: [ + "church_bear_armed_escape" + ] + }, + { + prompt: "Go for the beast's head", + conditions: [], + results: [ + "church_bear_armed_stupid" + ] + } + ] + }, + church_bear_armed_escape: { + text: [ + "With the beast's legs injured, you take your chance to escape into the woods, towards the temple, and Gaylen the town priest.", + "You arrive at the temple late in the afternoon, and see the priest standing outside the ornate stone building.", + "Nearby you can see a graveyard, with a massive ornate tomb at its center.", + "The priest approches you:", + '"Greetings, stranger. How may I be of service?"' + ], + effects: [], + options: [ + { + prompt: "Speak to the priest", + conditions: [], + results: [ + "church_gaylen" + ] + }, + { + prompt: "There is nothing for me here, let's return to town", + conditions: [ + { + type: "inventory", + target: "rusty_sword", + value: !0 + } + ], + results: [ + "jaccuse_arrival" + ] + } + ] + }, + church_gaylen: { + text: [ + '"Yes, strenger, how can I help you?"' + ], + effects: [], + options: [ + { + prompt: "Ask for help against the evil vampire", + conditions: [ + { + type: "inventory", + target: "ancient_armor", + value: !1 + } + ], + results: [ + { + conditions: [ + { + type: "choices", + target: "impressed_gaylen", + value: !0 + } + ], + target: "church_gaylen_success" + }, + "church_gaylen_fail" + ] + }, + { + prompt: "Ask about the temple", + conditions: [], + results: [ + "church_gaylen_temple" + ] + }, + { + prompt: "Ask the priest about Dave's family grave", + conditions: [ + { + type: "inventory", + target: "dave_flowers", + value: !0 + } + ], + results: [ + "church_gaylen_impressed" + ] + }, + { + prompt: "Go to the graveyard", + conditions: [], + results: [ + "church_graveyard" + ] + } + ] + }, + church_gaylen_success: { + text: [ + "The priest stays silent for a time, appearing to pray to himself. Finally he speaks;", + '"As you are a friend to the village, and an honourable soul, I grant you the protection of the great Edwin Rothsten."', + `"May the ancient vampire hunter's armor protect you. And take this key, you'll find Moonsbane in the hunter's tomb."` + ], + effects: [ + { + type: "inventory", + target: "ancient_armor", + value: !0 + }, + { + type: "inventory", + target: "hunter_tomb_key", + value: !0 + }, + { + type: "inventory", + target: "old_armor", + value: !1 + }, + { + type: "choices", + target: "learnt_about_hunter", + value: !0 + } + ], + options: [ + { + prompt: "Ask the priest something else", + conditions: [], + results: [ + "church_gaylen" + ] + }, + { + prompt: "Go to the graveyard", + conditions: [], + results: [ + "church_graveyard" + ] + } + ] + }, + church_gaylen_fail: { + text: [ + '"I will say a prayer of blessing for you stranger."', + '"Sadly I have only your word, so I cannot help you more than that. Good luck to you."' + ], + effects: [], + options: [ + { + prompt: "Ask the priest something else", + conditions: [], + results: [ + "church_gaylen" + ] + }, + { + prompt: "Go to the graveyard", + conditions: [], + results: [ + "church_graveyard" + ] + } + ] + }, + church_gaylen_temple: { + text: [ + '"This temple was founded over 700 years ago during the time of the Aremeic Order."', + '"It was personally commisioned by the famed vampire hunter, and knight of the order Edwin Rothsten."', + '"The legend himself is entombed here, where himself and his enchanted blade Moonsbane can watch over us."', + "", + "The priest points to the ornate tomb in the center of the graveyard - you think to yourself that it might be prudent to 'borrow' Moonsbane." + ], + effects: [ + { + type: "choices", + target: "learnt_about_hunter", + value: !0 + } + ], + options: [ + { + prompt: "Ask the priest something else", + conditions: [], + results: [ + "church_gaylen" + ] + }, + { + prompt: "Go to the graveyard", + conditions: [], + results: [ + "church_graveyard" + ] + } + ] + }, + church_gaylen_impressed: { + text: [ + "You show the flowers to the priest, who smiles warmly at you.", + '"Ah! A friend of lord Audemars is a friend of mine. I will bless these and place them at his family grave for you."' + ], + effects: [ + { + type: "inventory", + target: "dave_flowers", + value: !1 + }, + { + type: "choices", + target: "impressed_gaylen", + value: !0 + } + ], + options: [ + { + prompt: "Ask the priest something else", + conditions: [], + results: [ + "church_gaylen" + ] + }, + { + prompt: "Go to the graveyard", + conditions: [], + results: [ + "church_graveyard" + ] + } + ] + }, + church_graveyard: { + text: [ + "You arrive at the humble graveyard. In its center lies an ornate tomb marked 'Edwin Rothsten, Hunter of the Aremeic Order'", + "The family grave of the Audemars clan sits nearby." + ], + effects: [], + options: [ + { + prompt: "Place the flower's on Dave Audemars' family grave", + conditions: [ + { + type: "inventory", + target: "dave_flowers", + value: !0 + } + ], + results: [ + "church_graveyard_flowers" + ] + }, + { + prompt: "Retrieve the sword from the hunter's tomb", + conditions: [ + { + type: "choices", + target: "learnt_about_hunter", + value: !0 + } + ], + results: [ + { + conditions: [ + { + type: "inventory", + target: "hunter_tomb_key", + value: !0 + } + ], + target: "church_graveyard_key" + }, + "church_graveyard_breakin" + ] + }, + { + prompt: "Return to the village, and confront Zarban", + conditions: [ + { + type: "inventory", + target: "rusty_sword", + value: !0 + } + ], + results: [ + "jaccuse_arrival" + ] + } + ] + }, + church_graveyard_flowers: { + text: [ + "You place the flowers at the foot of the family grave stone, and say a short prayer.", + "You feel content, your promise fulfilled." + ], + effects: [ + { + type: "inventory", + target: "dave_flowers", + value: !1 + } + ], + options: [ + { + prompt: "Retrieve the sword from the hunter's tomb", + conditions: [ + { + type: "choices", + target: "learnt_about_hunter", + value: !0 + } + ], + results: [ + { + conditions: [ + { + type: "inventory", + target: "hunter_tomb_key", + value: !0 + } + ], + target: "church_graveyard_key" + }, + "church_graveyard_breakin" + ] + }, + { + prompt: "Return to the village, and confront Zarban", + conditions: [ + { + type: "inventory", + target: "rusty_sword", + value: !0 + } + ], + results: [ + "jaccuse_arrival" + ] + } + ] + }, + church_graveyard_key: { + text: [ + "You walk up to the tomb, and use a the key to open the ornate lock.", + "You pick up the ancient runic sword and feel its holy power flow through you - Zarban's end is at hand!" + ], + effects: [ + { + type: "inventory", + target: "magic_sword", + value: !0 + }, + { + type: "inventory", + target: "rusty_sword", + value: !1 + }, + { + type: "inventory", + target: "hunter_tomb_key", + value: !1 + } + ], + options: [ + { + prompt: "Return to the village, and confront Zarban", + conditions: [], + results: [ + "jaccuse_arrival" + ] + } + ] + }, + church_graveyard_breakin: { + text: [ + "You walk up to the tomb, and use a nearby rock to smash open the ornate lock.", + "You pick up the ancient runic sword and equip it - Zarban's end is at hand!", + "You'll probably return it when you are finished. Maybe." + ], + effects: [ + { + type: "inventory", + target: "magic_sword", + value: !0 + }, + { + type: "inventory", + target: "rusty_sword", + value: !1 + } + ], + options: [ + { + prompt: "Return to the village, and confront Zarban", + conditions: [], + results: [ + "jaccuse_arrival" + ] + } + ] + } + }, + chapter: "2" + }, + { + $schema: "../schema/chapter.zarban.schema.json", + name: "Chapter 4: J'Accuse!", + records: { + jaccuse_arrival: { + text: [ + "You arrive back in the village at midnight. The stench of evil still hangs in the air - it is time to confront Zarban.", + "But which villager has been replaced by the vile sorcerer?" + ], + effects: [ + { + type: "status", + target: "stamina", + operation: "add", + value: -1 + } + ], + options: [ + { + prompt: "Go to the tavern", + conditions: [], + results: [ + { + conditions: [ + { + type: "status", + target: "stamina", + operation: "eq", + value: 0 + } + ], + target: "jaccuse_end_stamina" + }, + "jaccuse_tavern" + ] + }, + { + prompt: "Go to the farm", + conditions: [], + results: [ + { + conditions: [ + { + type: "status", + target: "stamina", + operation: "eq", + value: 0 + } + ], + target: "jaccuse_end_stamina" + }, + { + conditions: [ + { + type: "choices", + target: "injured_zarban", + value: !0 + } + ], + target: "jaccuse_farm_scar" + }, + "jaccuse_farm" + ] + }, + { + prompt: "Go to Dave's house", + conditions: [], + results: [ + { + conditions: [ + { + type: "status", + target: "stamina", + operation: "eq", + value: 0 + } + ], + target: "jaccuse_end_stamina" + }, + "jaccuse_dave" + ] + }, + { + prompt: "Go to the schoolhouse", + conditions: [], + results: [ + { + conditions: [ + { + type: "status", + target: "stamina", + operation: "eq", + value: 0 + } + ], + target: "jaccuse_end_stamina" + }, + "jaccuse_school" + ] + } + ] + }, + jaccuse_end_stamina: { + text: [ + "Exhausted and thirsty, you collapse to the ground. The villagers find you, and bring you to the inn to recover.", + "Unfortunately, by then Zarban is long gone, and the trail cold. He will later go on to form a knitting circle that only makes ugly sweaters for puppies.", + "", + "GAME OVER! Try again?" + ], + effects: [], + options: [ + { + prompt: "New game", + conditions: [], + results: [ + "intro_cave1" + ] + } + ] + }, + jaccuse_tavern: { + text: [ + "You enter the tavern. The bartender stands at his usual spot, and Dave drinks alone in the corner." + ], + effects: [], + options: [ + { + prompt: "Confront the bartender", + conditions: [], + results: [ + "jaccuse_bartender" + ] + }, + { + prompt: "Confront Dave", + conditions: [], + results: [ + "jaccuse_dave" + ] + }, + { + prompt: "Go somewhere else", + conditions: [], + results: [ + "jaccuse_arrival" + ] + } + ] + }, + jaccuse_bartender: { + text: [ + "You approach the bartender, who smiles warmly on your approach. The smile fades as you draw your blade.", + "As your sword plunges into the man's chest, and you see the light leave his eyes, you know in your heart you've chosen wrong.", + "The innocent man falling to the ground before you is the last thing you ever see as a drunken, angry Dave breaks your neck from behind.", + "Zarban would later escape, and go on to form a boy band that only sings Nickelback covers.", + "", + "GAME OVER! Try again?" + ], + effects: [], + options: [ + { + prompt: "New game", + conditions: [], + results: [ + "intro_cave1" + ] + } + ] + }, + jaccuse_dave: { + text: [ + "Dave meets your gaze as you approach him, the old soldier recognizing the look in your eyes.", + `A single tear falls down his cheek as he mutters to himself "I'm coming home, Amy."`, + "As your blade pierces his heart and Dave Audemars dies before you, you know in your heart you've chosen wrong.", + "A bottle breaks over your head from behind, and you fall to the ground. You'll later awaken just in time for your hanging", + "Zarban would later escape, and go on to become the world's most successful spam email marketer.", + "", + "GAME OVER! Try again?" + ], + effects: [], + options: [ + { + prompt: "New game", + conditions: [], + results: [ + "intro_cave1" + ] + } + ] + }, + jaccuse_farm_scar: { + text: [ + "You arrive at the farm, and find Arnoulf and his daughter working the field by moonlight.", + "The white frills of Amelie's long dress reflect the dim light of the moon" + ], + effects: [], + options: [ + { + prompt: "Confront the old farmer", + conditions: [], + results: [ + "jaccuse_farm_arnoulf" + ] + }, + { + prompt: "Confront the farmer's daughter", + conditions: [], + results: [ + { + conditions: [ + { + type: "inventory", + target: "rusty_sword", + value: !0 + } + ], + target: "jaccuse_farm_amelie_nosword" + }, + { + conditions: [ + { + type: "inventory", + target: "ancient_armor", + value: !0 + } + ], + target: "jaccuse_farm_amelie_good" + }, + { + conditions: [ + { + type: "inventory", + target: "magic_sword", + value: !0 + } + ], + target: "jaccuse_farm_amelie_noarmor" + } + ] + }, + { + prompt: "Go somewhere else", + conditions: [], + results: [ + "jaccuse_arrival" + ] + } + ] + }, + jaccuse_farm: { + text: [ + "You arrive at the farm, and find Arnoulf and his daughter working the field by moonlight." + ], + effects: [], + options: [ + { + prompt: "Confront the old farmer", + conditions: [], + results: [ + "jaccuse_farm_arnoulf" + ] + }, + { + prompt: "Confront the farmer's daughter", + conditions: [], + results: [ + { + conditions: [ + { + type: "inventory", + target: "rusty_sword", + value: !0 + } + ], + target: "jaccuse_farm_amelie_nosword" + }, + { + conditions: [ + { + type: "inventory", + target: "ancient_armor", + value: !0 + } + ], + target: "jaccuse_farm_amelie_good" + }, + { + conditions: [ + { + type: "inventory", + target: "magic_sword", + value: !0 + } + ], + target: "jaccuse_farm_amelie_noarmor" + } + ] + }, + { + prompt: "Go somewhere else", + conditions: [], + results: [ + "jaccuse_arrival" + ] + } + ] + }, + jaccuse_farm_arnoulf: { + text: [ + "You approach the farmer as he toils under the moonlight, and draw your blade.", + "The farmer scowls as he raises his pitchfork to confront you.", + "You manage to get one good slash in on the farmer's neck... just as his pitchfork pierces your lungs.", + "As you both bleed out on the ground, you know you have chosen incorrectly.", + "Zarban would later go on to invent a new type of toothpaste that causes cavities instead of preventing them.", + "", + "GAME OVER! Try again?" + ], + effects: [], + options: [ + { + prompt: "New game", + conditions: [], + results: [ + "intro_cave1" + ] + } + ] + }, + jaccuse_farm_amelie_nosword: { + text: [ + "You approach the young woman and draw your sword.", + "She dons an evil grin and her features twist and distort, as the evil shapeshifter assumes to form of a massive bear!", + "You hack and slash away to no avail, as the common steel of your rusted blade cannot kill the vampire.", + "As the old farmer tries in vain to help you, your neck is ripped open by his mighty jaws, and you bleed out onto the moonlit field.", + "Zarban, revealed and unleashed, will draw strength from the blood of the village by massacring the entire populate of the tiny hamlet.", + "Renewed by his bloodbath, Zarban escapes into the world, more powerful than ever before.", + "", + "The End! Try again?" + ], + effects: [], + options: [ + { + prompt: "New game", + conditions: [], + results: [ + "intro_cave1" + ] + } + ] + }, + jaccuse_farm_amelie_noarmor: { + text: [ + "You approach the young woman and draw your sword.", + "She dons an evil grin and her features twist and distort, as the evil shapeshifter assumes to form of a massive bear!", + "You stab the vile creature through it's dark heart, but in his dying breath, the evil sorcerer utters a final curse.", + "As you fall to the ground, the air sucked out of your lungs, and slowly choke to death on your own blood,", + "You see the evil creature turn to dust before you. You may die, but at least the world is safe from this creature.", + "The village will remember you as a brave hero.", + "", + "The End! Try again?" + ], + effects: [], + options: [ + { + prompt: "New game", + conditions: [], + results: [ + "intro_cave1" + ] + } + ] + }, + jaccuse_farm_amelie_good: { + text: [ + "You approach the young woman and draw your sword.", + "She dons an evil grin and her features twist and distort, as the evil shapeshifter assumes to form of a massive bear!", + "You stab the vile creature through it's dark heart, but in his dying breath, the evil sorcerer utters a final curse.", + "Your enchanted armor glows bright with ancient runic magic as the vampire's curse is nullified.", + "The creature contorts and screeches as it is reduced to dust and ash - the vile one has been vanquished!", + "", + "Congratulations! You are victorious!" + ], + effects: [], + options: [ + { + prompt: "New game", + conditions: [], + results: [ + "intro_cave1" + ] + } + ] + }, + jaccuse_davehouse: { + text: [ + "You arrive at Dave's home at the edge of the village, a once beautiful home, but clearly neglected of late.", + "Vines and weeds grow rampant across the property, and the windows sit greased and dusty.", + "", + "No candles burn within, and the door is locked tight." + ], + effects: [], + options: [ + { + prompt: "Go somewhere else", + conditions: [], + results: [ + "jaccuse_arrival" + ] + } + ] + }, + jaccuse_school: { + text: [ + "You kick down the door of the teacher's humble shack and burst in, sword drawn.", + "The teacher and her 6 year old son scream as you run inside." + ], + effects: [], + options: [ + { + prompt: "Confront the teacher", + conditions: [], + results: [ + "jaccuse_school_teacher" + ] + }, + { + prompt: "Confront the son", + conditions: [], + results: [ + "jaccuse_school_teacher_child" + ] + }, + { + prompt: "Go somewhere else", + conditions: [], + results: [ + "jaccuse_arrival" + ] + } + ] + }, + jaccuse_school_teacher: { + text: [ + "You expertly swing your blade, and take the young woman's head clean off!", + "She dies instantly, since she was a schoolteacher - not a vampire.", + "You run off as the young boy cries and vows to avenge his mother.", + "You spend the rest of your days in hiding, as Zarban goes on to invent a new type of coffee that tastes like burnt popcorn and expired milk.", + "", + "The End! Try again?" + ], + effects: [], + options: [ + { + prompt: "New game", + conditions: [], + results: [ + "intro_cave1" + ] + } + ] + }, + jaccuse_school_teacher_child: { + text: [ + "You expertly swing your blade at the young child, but the mother blocks your blade with her body.", + "The young woman bleeds out in seconds, and the small cut on the boy's arm reveals the pure red blood of a human child.", + "You run off as the young boy cries and vows to avenge his mother.", + "You spend the rest of your days in hiding, as Zarban goes on to open a bakery that only sells cakes made with vegetables instead of sugar.", + "", + "The End! Try again?" + ], + effects: [], + options: [ + { + prompt: "New game", + conditions: [], + results: [ + "intro_cave1" + ] + } + ] + }, + jaccuse_mill: { + text: [ + "You burst into the stone building and charge to the far side of the room, where the family sits around a fireplace.", + "They look up in confused horror at the unexpected home invasion.", + "A bandage is wrapped around the baker's hand, clearly a fresh wound." + ], + effects: [], + options: [ + { + prompt: "Confront the baker", + conditions: [], + results: [ + "jaccuse_mill_baker" + ] + }, + { + prompt: "Confront the seamstress", + conditions: [], + results: [ + "jaccuse_mill_seamstress" + ] + }, + { + prompt: "Confront the 8 year old boy", + conditions: [], + results: [ + "jaccuse_mill_boy" + ] + }, + { + prompt: "Confront the twins", + conditions: [], + results: [ + "jaccuse_mill_babies" + ] + }, + { + prompt: "Go somewhere else", + conditions: [], + results: [ + "jaccuse_arrival" + ] + } + ] + }, + jaccuse_mill_baker: { + text: [ + "You draw your blade and eviscerate the baker and father.", + "As his guts spill all over the floor of the mill, the grieving family holds you down until the authorities arrive.", + "You will be sentenced to life in prison without parole, from where you will be blissfully unaware of all the puppies Zarban would later eat.", + "", + "The End! Try again?" + ], + effects: [], + options: [ + { + prompt: "New game", + conditions: [], + results: [ + "intro_cave1" + ] + } + ] + }, + jaccuse_mill_seamstress: { + text: [ + "You draw your blade and stab the young mother of 3 through the eye.", + "As she falls to the ground and you realize the gravity of your mistake, a cast iron pan to the forehead puts you out of everyone's misery.", + "Zarban would later go on to traffic in baby spines for fun and profit.", + "", + "The End! Try again?" + ], + effects: [], + options: [ + { + prompt: "New game", + conditions: [], + results: [ + "intro_cave1" + ] + } + ] + }, + jaccuse_mill_boy: { + text: [ + "You draw your sword and slash at the young boy's throat.", + "The boy uses his arms to shield himself, and survives - barely - your blow.", + "The pure red blood of a human tells you that a mistake has been made as his mother pierces your brain with a fork through the eye.", + "Zarban would later go on to rethink his life and become a nun. Just kidding he eats children now.", + "", + "The End! Try again?" + ], + effects: [], + options: [ + { + prompt: "New game", + conditions: [], + results: [ + "intro_cave1" + ] + } + ] + }, + jaccuse_mill_babies: { + text: [ + "You draw your sword and approach the sleeping newborn babies.", + "You stop yourself from murdering two babies at the last second and decide to go to the tavern to rethink your life.", + "The closecall has scarred you, and you no longer wish to hunt vampires.", + "Zarban would later to on to invest heavily into dogecoin.", + "", + "The End! Try again?" + ], + effects: [], + options: [ + { + prompt: "New game", + conditions: [], + results: [ + "intro_cave1" + ] + } + ] + } + }, + chapter: "3" + } +], K = { + $schema: "../schema/player.choices.zarban.schema.json", + records: { + made_dave_go_home: { + description: "Spoke to Dave in the tavern, making him go back to his home" + }, + made_dave_sad: { + description: "Was rude to Dave, who will no longer help you" + }, + learnt_about_priest: { + description: "Learnt about the church a day's walk down the road from the village" + }, + accepted_bread: { + description: "Accepted bread from the baker" + }, + snooped_on_teacher: { + description: "Snooped on the teacher's shack" + }, + insulted_teacher: { + description: "Teacher found out about your snooping" + }, + impressed_gaylen: { + description: "Impressed the priest with your kindness" + }, + learnt_about_hunter: { + description: "Learnt about the famous hunter (and his sword)" + }, + injured_zarban: { + description: "Injured Zarban's legs" + } } +}, V = { + $schema: "../schema/player.inventory.zarban.schema.json", + records: { + hunter_sword: { + description: "Vampire hunter's sword", + equipped: !1, + effects: [] + }, + hunter_armor: { + description: "Vampire hunter's armor", + equipped: !1, + effects: [] + }, + rusty_sword: { + description: "Rusty shortsword", + equipped: !1, + effects: [] + }, + old_armor: { + description: "Soldier's leather cuirass", + equipped: !1, + effects: [ + { + type: "status", + target: "stamina", + operation: "add_max", + value: 2 + }, + { + type: "status", + target: "stamina", + operation: "add", + value: 2 + } + ] + }, + magic_sword: { + description: "Moonsbane", + equipped: !1, + effects: [] + }, + ancient_armor: { + description: "Edwin Rothsten's armor", + equipped: !1, + effects: [ + { + type: "status", + target: "stamina", + operation: "add_max", + value: 5 + }, + { + type: "status", + target: "stamina", + operation: "add", + value: 5 + } + ] + }, + hunter_tomb_key: { + description: "Key to the vampire hunter's tomb", + equipped: !1, + effects: [] + }, + dave_flowers: { + description: "Flower's for Dave's family grave", + equipped: !1, + effects: [] + } + } +}, P = { + $schema: "../schema/player.status.zarban.schema.json", + records: { + stamina: { + hidden: !1, + default: 0, + maximum: 3 + }, + alcoholism: { + hidden: !0, + default: 0, + maximum: 2 + } + } +}, U = { + $schema: H, + entrypoint: W, + chapters: J, + choices: K, + inventory: V, + status: P +}; +let p = U; +p.data && (p = y.base64Decode(p.data), p = JSON.parse(p)); +class u { + constructor(e) { + y.assign(this, p), e !== void 0 && y.assign(this, e), this.choices = new N(this.choices), this.inventory = new $(this.inventory), this.status = new M(this.status); + for (const t in this.chapters) + this.chapters[t] = new R(this.chapters[t]); + this.currentStoryKey === void 0 && this.setStory(this.entrypoint); + } + /** + * Return the current story + * @returns Story + */ currentStory() { return this.getStory(this.currentStoryKey); } + /** + * Return the current chapter + * @returns Chapter + */ currentChapter() { return this.chapters[this.currentChapterKey]; } + /** + * Return true if this is new game + * @returns bool + */ isNewGame() { return this.currentStoryKey == this.entrypoint; } - validateConditions(e2) { - let t2 = this.getAdjustedStats(); - for (const o2 of e2) - if (!o2.verify(t2)) - return false; - return true; - } - nextStory(e2) { - let t2 = this.currentStory().options.filter((e3) => this.validateConditions(e3.conditions))[e2 - 1]; - if (void 0 === t2) - return false; - for (const e3 of t2.results) - if (this.validateConditions(e3.conditions)) - return this.setStory(e3.target), true; - return false; - } - setStory(e2) { - for (const t2 in Object.keys(this.chapters)) { - let o2 = this.chapters[t2].getStory(e2); - if (false !== o2) { - this.currentChapterKey = t2, this.currentStoryKey = e2; - for (const e3 of o2.effects) - e3.apply(this); - return true; + /** + * Validate an option's conditions + * @param {StoryOption} option + * @returns bool + */ + validateConditions(e) { + let t = this.getAdjustedStats(); + for (const o of e) + if (!o.verify(t)) + return !1; + return !0; + } + /** + * Based on a selection, find the next available story + * @param {Number} option_id + * @returns String, or false for invalid selections + */ + nextStory(e) { + let t = this.currentStory().options.filter((o) => this.validateConditions(o.conditions))[e - 1]; + if (t === void 0) + return !1; + for (const o of t.results) + if (this.validateConditions(o.conditions)) + return this.setStory(o.target), !0; + return !1; + } + /** + * Update current game story + * @param {String} story_key + */ + setStory(e) { + for (const t in Object.keys(this.chapters)) { + let o = this.chapters[t].getStory(e); + if (o !== !1) { + this.currentChapterKey = t, this.currentStoryKey = e; + for (const r of o.effects) + r.apply(this); + return !0; } } - return false; + return !1; } + /** + * Get inventory-effect adjusted stats + * @returns a copy of the player + */ getAdjustedStats() { - let e2 = new y(this); - for (const t2 of this.inventory.activeEffects()) - t2.apply(e2); + let e = new u(this); + for (const t of this.inventory.activeEffects()) + t.apply(e); return this; } - getStory(e2) { - for (const t2 of Object.values(this.chapters)) { - let o2 = t2.getStory(e2); - if (false !== o2) - return o2; + /** + * Retrieve a story by ID + * @param {String} story_key + * @returns A Story object, or false + */ + getStory(e) { + for (const t of Object.values(this.chapters)) { + let o = t.getStory(e); + if (o !== !1) + return o; } - throw new Error(`Invalid story ID referenced: ${e2}!`); + throw new Error(`Invalid story ID referenced: ${e}!`); } + /** + * Save current game status to a string + * @returns String + */ save() { - const e2 = { currentChapterKey: this.currentChapterKey, currentStoryKey: this.currentStoryKey, status: {}, choices: this.choices.list_chosen(), inventory: this.inventory.list_equipped() }; - return this.status.list().forEach((t2) => { - e2.status[t2] = { value: this.status.get(t2).value, maximum: this.status.get(t2).maximum }; - }), encodeURIComponent(JSON.stringify(e2)); - } - static restore(e2) { + const e = { + currentChapterKey: this.currentChapterKey, + currentStoryKey: this.currentStoryKey, + status: {}, + choices: this.choices.list_chosen(), + inventory: this.inventory.list_equipped() + }; + return this.status.list().forEach((t) => { + e.status[t] = { + value: this.status.get(t).value, + maximum: this.status.get(t).maximum + }; + }), encodeURIComponent( + JSON.stringify(e) + ); + } + /** + * Restore a game save from the save data + * @param {String} data + * @returns Player + */ + static restore(e) { try { - const t2 = JSON.parse(decodeURIComponent(e2)), o2 = new y({ currentChapterKey: t2.currentChapterKey, currentStoryKey: t2.currentStoryKey }); - return t2.choices.forEach((e3) => { - o2.choices.records[e3].enabled = true; - }), t2.inventory.forEach((e3) => { - o2.inventory.records[e3].equipped = true; - }), Object.keys(t2.status).forEach((e3) => { - o2.status.records[e3].value = t2.status[e3].value, o2.status.records[e3].maximum = t2.status[e3].maximum; - }), o2; - } catch (e3) { - return console.log(e3), new y(); + const t = JSON.parse( + decodeURIComponent(e) + ), o = new u({ + currentChapterKey: t.currentChapterKey, + currentStoryKey: t.currentStoryKey + }); + return t.choices.forEach((r) => { + o.choices.records[r].enabled = !0; + }), t.inventory.forEach((r) => { + o.inventory.records[r].equipped = !0; + }), Object.keys(t.status).forEach((r) => { + o.status.records[r].value = t.status[r].value, o.status.records[r].maximum = t.status[r].maximum; + }), o; + } catch (t) { + return console.log(t), new u(); } } } -class f { - static getTitledBox(e2, t2) { - let o2 = Math.max(...t2.map((e3) => e3.length).concat([e2.length])) + 2, a2 = [`╔${"═".repeat(o2)}╗`, `║ ${e2}${" ".repeat(o2 - e2.length - 1)}║`, `╟${"─".repeat(o2)}╢`]; - for (const e3 of t2) - a2.push(`║ ${e3}${" ".repeat(o2 - e3.length - 1)}║`); - return a2.push(`╚${"═".repeat(o2)}╝`), a2; - } - static getPlayerDetailString(e2, t2 = false) { - let o2 = []; - for (const a2 of t2 ? e2.status.list() : e2.status.list_visible()) { - let t3 = e2.status.get(a2), r2 = a2.charAt(0).toUpperCase() + a2.slice(1); - o2.push(`${r2} : ${t3.value} / ${t3.maximum}`); +class A { + static getTitledBox(e, t) { + let o = Math.max(...t.map((s) => s.length).concat([e.length])) + 2, r = [ + `╔${"═".repeat(o)}╗`, + `║ ${e}${" ".repeat(o - e.length - 1)}║`, + `╟${"─".repeat(o)}╢` + ]; + for (const s of t) + r.push(`║ ${s}${" ".repeat(o - s.length - 1)}║`); + return r.push(`╚${"═".repeat(o)}╝`), r; + } + static getPlayerDetailString(e, t = !1) { + let o = []; + for (const s of t ? e.status.list() : e.status.list_visible()) { + let i = e.status.get(s), g = s.charAt(0).toUpperCase() + s.slice(1); + o.push(`${g} : ${i.value} / ${i.maximum}`); } - if (e2.inventory.all_equipped().length) { - o2.push(""), o2.push("Equipment:"); - for (const t3 of e2.inventory.all_equipped()) - o2.push(`- ${t3.description}`); + if (e.inventory.all_equipped().length) { + o.push(""), o.push("Equipment:"); + for (const s of e.inventory.all_equipped()) + o.push(`- ${s.description}`); } - if (t2) { - o2.push(""), o2.push("Choices:"); - for (const t3 of e2.choices.list().filter((t4) => e2.choices.chose(t4))) - o2.push(`- ${t3}`); + if (t) { + o.push(""), o.push("Choices:"); + for (const s of e.choices.list().filter((i) => e.choices.chose(i))) + o.push(`- ${s}`); } - return o2; - } - static getInterfaceString(e2, t2 = false, o2 = false) { - let a2 = e2.getAdjustedStats(), r2 = a2.currentChapter().name, s2 = ["", ...a2.currentStory().text, "", ...this.getPlayerDetailString(a2, o2)], n2 = [(t2 ? "Invalid selection. " : "") + "What do you do?", ...a2.currentStory().options.filter((e3) => a2.validateConditions(e3.conditions)).map((e3, t3) => `${t3 + 1}) ${e3}`)]; - return [...this.getTitledBox(r2, s2), "", ...n2].join("\n"); - } - static getInterfaceStrings(e2) { - const t2 = e2.getAdjustedStats(); - return { title: t2.currentChapter().name, description: [...t2.currentStory().text, "", ...t2.status.list_visible().map((e3) => `${e3}: ${t2.status.get(e3).value}/${t2.status.get(e3).maximum}`), "", "Equipment:", ...t2.inventory.all_equipped().map((e3) => `- ${e3.description}`)], options: t2.currentStory().options.filter((e3) => t2.validateConditions(e3.conditions)) }; + return o; + } + static getInterfaceString(e, t = !1, o = !1) { + let r = e.getAdjustedStats(), s = r.currentChapter().name, i = [ + "", + ...r.currentStory().text, + "", + ...this.getPlayerDetailString(r, o) + ], g = r.currentStory().options.filter((f) => r.validateConditions(f.conditions)), j = [ + `${t ? "Invalid selection. " : ""}What do you do?`, + ...g.map((f, I) => `${I + 1}) ${f}`) + ]; + return [ + ...this.getTitledBox(s, i), + "", + ...j + ].join(` +`); + } + static getInterfaceStrings(e) { + const t = e.getAdjustedStats(); + return { + title: t.currentChapter().name, + description: [ + ...t.currentStory().text, + "", + ...t.status.list_visible().map((o) => `${o}: ${t.status.get(o).value}/${t.status.get(o).maximum}`), + "", + "Equipment:", + ...t.inventory.all_equipped().map((o) => `- ${o.description}`) + ], + options: t.currentStory().options.filter((o) => t.validateConditions(o.conditions)) + }; } } -class v { - constructor(e2) { - this.player = e2 ? y.restore(e2) : new y(); +class x { + constructor(e) { + this.player = e ? u.restore(e) : new u(); } + /** + * Get the current story text + */ getInterfaceStrings() { - return f.getInterfaceStrings(this.player); + return A.getInterfaceStrings(this.player); } + /** + * Return the current game state + */ save() { return this.player.save(); } + /** + * Reset the game state + */ reset() { - this.player = new y(), this.draw(); + this.player = new u(), this.draw(); } + /** + * Render the current game state + */ draw() { return ""; } - step(e2) { - return this.error = !!e2 && !this.player.nextStory(e2), this.draw(); + /** + * Advance the game state + */ + step(e) { + return this.error = e ? !this.player.nextStory(e) : !1, this.draw(); } } -const b = { Float: "Float", Integer: "Integer", Numeric: "Numeric", String: "String", Boolean: "Boolean", Array: "Array", Object: "Object", Any: "" }; -class _ { - static typeOf(e2) { - let t2 = Object.keys(e2); - return !!t2.length && t2[0]; - } - static cooerce(e2, t2) { - switch (t2) { +const a = { + Float: "Float", + Integer: "Integer", + Numeric: "Numeric", + // These can be converted from any type + String: "String", + Boolean: "Boolean", + Array: "Array", + Object: "Object", + Any: "" +}; +class l { + /** + * Determine the type of an incoming value + * @param {Object} wrappedValue + * @returns Type of the value given + */ + static typeOf(e) { + let t = Object.keys(e); + return t.length ? t[0] : !1; + } + /** + * Cooerce a value to a given type + * Should mimic Lavendeux's type cooersion + * @param {Any} value + * @param {String} targetType Type to wrap as + * @returns New value + */ + static cooerce(e, t) { + switch (t) { case "Integer": - return Math.floor(Number(e2)); + return Math.floor(Number(e)); case "Numeric": case "Float": - return Number(e2); + return Number(e); case "Boolean": - return !!e2; + return !!e; case "String": - return "object" == typeof e2 ? JSON.stringify(e2) : `${e2}`; + return typeof e == "object" ? JSON.stringify(e) : `${e}`; case "Array": - return Array.isArray(e2) ? e2 : "object" == typeof e2 ? Object.values(e2) : [e2]; + return Array.isArray(e) ? e : typeof e == "object" ? Object.values(e) : [e]; case "Object": - return "object" == typeof e2 ? Object.assign({}, e2) : { 0: e2 }; + return typeof e == "object" ? Object.assign({}, e) : { 0: e }; default: - return e2; + return e; } } - static unwrap(e2, t2 = b.Any) { - let o2 = this.typeOf(e2), a2 = Object.values(e2)[0]; - switch (o2) { + /** + * Return a raw value + * @param {Object} wrappedValue + * @returns value + */ + static unwrap(e, t = a.Any) { + let o = this.typeOf(e), r = Object.values(e)[0]; + switch (o) { case "Object": - a2 = a2.map(([e3, t3]) => [this.unwrap(e3, b.String), this.unwrap(t3)]), a2 = Object.fromEntries(a2); + r = r.map(([s, i]) => [ + this.unwrap(s, a.String), + this.unwrap(i) + ]), r = Object.fromEntries(r); break; case "Array": - a2 = a2.map((e3) => this.unwrap(e3)); - } - return _.cooerce(a2, t2); - } - static wrap(e2, t2 = b.Any) { - if (e2 = this.cooerce(e2, t2), Array.isArray(e2)) - return { Array: e2.map((e3) => this.wrap(e3)) }; - if ("object" == typeof e2) { - let t3 = []; - return Object.keys(e2).forEach((o2) => { - t3.push([this.wrap(o2), this.wrap(e2[o2])]); - }), { Object: t3 }; + r = r.map((s) => this.unwrap(s)); + break; } - return "string" == typeof e2 || e2 instanceof String ? { String: e2 } : Number.isInteger(e2) ? { Integer: e2 } : Number(e2) === e2 ? { Float: e2 } : { Boolean: 1 == e2 }; + return l.cooerce(r, t); + } + /** + * Wrap a value for returning to Lavendeux + * @param {Any} value + * @param {String} targetType Type to wrap as + * @returns Wrapped value + */ + static wrap(e, t = a.Any) { + if (e = this.cooerce(e, t), Array.isArray(e)) + return { Array: e.map((o) => this.wrap(o)) }; + if (typeof e == "object") { + let o = []; + return Object.keys(e).forEach((r) => { + o.push([ + this.wrap(r), + this.wrap(e[r]) + ]); + }), { Object: o }; + } else + return typeof e == "string" || e instanceof String ? { String: e } : Number.isInteger(e) ? { Integer: e } : Number(e) === e ? { Float: e } : { Boolean: e == !0 }; } } -class w { - constructor(e2, t2, o2) { - this.name = e2.replace("@", ""), this.callback = o2, this.argumentTypes = [], this.returnType = t2, this.registeredName = w.getRegisteredName(e2); +let T, S, v; +if (typeof v > "u") { + T = !0; + const n = {}; + v = () => n, S = (e) => Object.assign(n, e); +} +class m { + constructor(e, t, o) { + this.name = e.replace("@", ""), this.callback = o, this.argumentTypes = [], this.returnType = t, this.registeredName = m.getRegisteredName(e); } + /** + * Will return true if the state functions are available + * Will be false in very old versions of Lavendeux + */ static isStateAvailable() { - return "function" == typeof getState; - } - static getRegisteredName(e2) { - return `lavendeuxfunction_${e2}`; - } - addArgument(e2 = b.Any) { - return this.argumentTypes.push(e2), this; - } + return T; + } + /** + * Get a unique identifier for a function + * @returns A unique name for this function + */ + static getRegisteredName(e) { + return `lavendeuxfunction_${e}`; + } + /** + * Add an untyped argument to the function + * @param {Boolean} optional True if the argument is optional. + */ + addArgument(e = a.Any) { + return this.argumentTypes.push(e), this; + } + /** + * Add an integer typed argument to the function + */ addIntegerArgument() { - return this.addArgument(b.Integer); + return this.addArgument(a.Integer); } + /** + * Add a float typed argument to the function + */ addFloatArgument() { - return this.addArgument(b.Float); + return this.addArgument(a.Float); } + /** + * Add a int or float typed argument to the function + */ addNumericArgument() { - return this.addArgument(b.Numeric); + return this.addArgument(a.Numeric); } + /** + * Add a string typed argument to the function + */ addStringArgument() { - return this.addArgument(b.String); + return this.addArgument(a.String); } + /** + * Add a boolean typed argument to the function + */ addBooleanArgument() { - return this.addArgument(b.Boolean); + return this.addArgument(a.Boolean); } + /** + * Add an array typed argument to the function + */ addArrayArgument() { - return this.addArgument(b.Array); + return this.addArgument(a.Array); } + /** + * Add an object typed argument to the function + */ addObjectArgument() { - return this.addArgument(b.Object); - } - decodeArguments(e2) { - if (e2.length < this.argumentTypes.length) + return this.addArgument(a.Object); + } + /** + * Validates and decodes arguments + * Will throw an error if argument count or types are unexpected + * @returns Unwrapped arguments + */ + decodeArguments(e) { + if (e.length < this.argumentTypes.length) throw new Error(`Missing a parameter for ${this.name}: Expected ${this.argumentTypes.length} arguments`); - return this.argumentTypes.forEach((t2, o2) => { - let a2 = _.typeOf(e2[o2]); - if (t2 == b.Numeric && ![b.Integer, b.Float].includes(a2) || [b.Integer, b.Float].includes(t2) && t2 != a2) - throw new Error(`Invalid value for parameter ${o2 + 1} of ${this.name}: Expected ${e2[o2].type}`); - }), e2.map((e3, t2) => { - let o2 = this.argumentTypes[t2] ? this.argumentTypes[t2] : b.Any; - return _.unwrap(e3, o2); + return this.argumentTypes.forEach((t, o) => { + let r = l.typeOf(e[o]); + if (t == a.Numeric && ![a.Integer, a.Float].includes(r) || [a.Integer, a.Float].includes(t) && t != r) + throw new Error(`Invalid type for parameter ${o + 1} of ${this.name}: Expected ${t}`); + }), e.map((t, o) => { + let r = this.argumentTypes[o] ? this.argumentTypes[o] : a.Any; + return l.unwrap(t, r); }); } + /** + * Return an object containing the variables available in the parser + * @returns State + */ getState() { - const e2 = w.isStateAvailable() ? getState() : {}; - return Object.keys(e2).map((t2) => { - e2[t2] = _.unwrap(e2[t2]); - }), e2; - } - setState(e2) { - w.isStateAvailable() && (Object.keys(e2).map((t2) => { - e2[t2] = _.wrap(e2[t2]); - }), setState(e2)); - } - call(e2) { - e2 = this.decodeArguments(e2); - let t2 = this.getState(), o2 = _.wrap(this.callback(...e2, t2), this.returnType); - return this.setState(t2), o2; + const e = v(); + return Object.keys(e).map((t) => { + e[t] = l.unwrap(e[t]); + }), e; + } + /** + * Update the parser state + * @param {Object} state + */ + setState(e) { + Object.keys(e).map((t) => { + e[t] = l.wrap(e[t]); + }), S(e); + } + call(e) { + e = this.decodeArguments(e); + let t = this.getState(), o = l.wrap( + this.callback(...e, t), + this.returnType + ); + return this.setState(t), o; } } -class k extends w { - constructor(e2, t2, o2) { - super(e2, b.String, o2), this.argumentTypes = [t2], this.registeredName = k.getRegisteredName(e2); - } - static getRegisteredName(e2) { - return `lavendeuxdecorator_${e2}`; - } - call(e2) { - return _.unwrap(super.call([e2]), b.String); +class b extends m { + constructor(e, t, o) { + super(e, a.String, o), this.argumentTypes = [t], this.registeredName = b.getRegisteredName(e); + } + /** + * Get a unique identifier for a decorator + * @returns A unique name for this decorator + */ + static getRegisteredName(e) { + return `lavendeuxdecorator_${e}`; + } + call(e) { + return l.unwrap( + super.call([e]), + a.String + ); } } -class A { - constructor(e2, t2, o2) { - this.name = e2, this.author = t2, this.version = o2, this.functions = {}, this.decorators = {}, this.allHandlers = {}; - } - static register(e2) { - globalThis.extension = () => e2.definition(), Object.values(e2.allHandlers).forEach((e3) => { - globalThis[e3.registeredName] = (t2) => e3.call(t2); +class _ { + /** + * Build a new extension + */ + constructor(e, t, o) { + this.name = e, this.author = t, this.version = o, this.functions = {}, this.decorators = {}, this.allHandlers = {}; + } + static register(e) { + globalThis.extension = () => e.definition(), globalThis.extensionInstance = e, Object.values(e.allHandlers).forEach((t) => { + globalThis[t.registeredName] = (o) => t.call(o); }); } - definition() { - return { name: this.name, author: this.author, version: this.version, functions: this.functions, decorators: this.decorators }; - } - getFunctionCallback(e2) { - this.allHandlers[this.functions[e2]].callback; - } - getDecoratorCallback(e2) { - this.allHandlers[this.decorators[e2]].callback; + static registeredInstance() { + return globalThis.extensionInstance; } - addFunction(e2, t2, o2 = b.Any) { - let a2 = new w(e2, o2, t2); - return this.allHandlers[a2.registeredName] = a2, this.functions[e2] = a2.registeredName, a2; - } - addIntegerFunction(e2, t2) { - return this.addFunction(e2, t2, b.Integer); - } - addFloatFunction(e2, t2) { - return this.addFunction(e2, t2, b.Float); - } - addNumericFunction(e2, t2) { - return this.addFunction(e2, t2, b.Numeric); - } - addStringFunction(e2, t2) { - return this.addFunction(e2, t2, b.String); - } - addBooleanFunction(e2, t2) { - return this.addFunction(e2, t2, b.Boolean); - } - addArrayFunction(e2, t2) { - return this.addFunction(e2, t2, b.Array); - } - addObjectFunction(e2, t2) { - return this.addFunction(e2, t2, b.Object); - } - addDecorator(e2, t2, o2 = b.Any) { - let a2 = new k(e2, o2, t2); - return this.allHandlers[a2.registeredName] = a2, this.decorators[e2] = a2.registeredName, a2; - } - addIntegerDecorator(e2, t2) { - return this.addDecorator(e2, t2, b.Integer); - } - addFloatDecorator(e2, t2) { - return this.addDecorator(e2, t2, b.Float); - } - addNumericDecorator(e2, t2) { - return this.addDecorator(e2, t2, b.Numeric); - } - addStringDecorator(e2, t2) { - return this.addDecorator(e2, t2, b.String); - } - addBooleanDecorator(e2, t2) { - return this.addDecorator(e2, t2, b.Boolean); - } - addArrayDecorator(e2, t2) { - return this.addDecorator(e2, t2, b.Array); - } - addObjectDecorator(e2, t2) { - return this.addDecorator(e2, t2, b.Object); + /** + * Returns the structure expected by lavendeux + */ + definition() { + return { + name: this.name, + author: this.author, + version: this.version, + functions: this.functions, + decorators: this.decorators + }; + } + /** + * Get the inner callback for a registered function + * @param {String} name Function name to retrieve + * @returns Callback function + */ + getFunctionCallback(e) { + return this.allHandlers[this.functions[e]].callback; + } + /** + * Get the inner callback for a registered decorator + * @param {String} name Function name to retrieve + * @returns Callback function + */ + getDecoratorCallback(e) { + return this.allHandlers[this.decorators[e]].callback; + } + /** + * Add a callable function + * @param {String} name The name of the new function + * @param {*} callback Callback function to execute + * @param {String} expectedType The type returned by the function (Types) + */ + addFunction(e, t, o = a.Any) { + let r = new m(e, o, t); + return this.allHandlers[r.registeredName] = r, this.functions[e] = r.registeredName, r; + } + /** + * Add a callable function that returns the integer type + * @param {String} name The name of the new function + * @param {*} callback Callback function to execute + */ + addIntegerFunction(e, t) { + return this.addFunction(e, t, a.Integer); + } + /** + * Add a callable function that returns the float type + * @param {String} name The name of the new function + * @param {*} callback Callback function to execute + */ + addFloatFunction(e, t) { + return this.addFunction(e, t, a.Float); + } + /** + * Add a callable function that returns the integer or float type + * @param {String} name The name of the new function + * @param {*} callback Callback function to execute + */ + addNumericFunction(e, t) { + return this.addFunction(e, t, a.Numeric); + } + /** + * Add a callable function that returns the string type + * @param {String} name The name of the new function + * @param {*} callback Callback function to execute + */ + addStringFunction(e, t) { + return this.addFunction(e, t, a.String); + } + /** + * Add a callable function that returns the boolean type + * @param {String} name The name of the new function + * @param {*} callback Callback function to execute + */ + addBooleanFunction(e, t) { + return this.addFunction(e, t, a.Boolean); + } + /** + * Add a callable function that returns the array type + * @param {String} name The name of the new function + * @param {*} callback Callback function to execute + */ + addArrayFunction(e, t) { + return this.addFunction(e, t, a.Array); + } + /** + * Add a callable function that returns the object type + * @param {String} name The name of the new function + * @param {*} callback Callback function to execute + */ + addObjectFunction(e, t) { + return this.addFunction(e, t, a.Object); + } + /** + * Add a callable decorator + * @param {String} name The name of the new decorator + * @param {*} callback Callback function to execute + * @param {String} expectedType The type expected by the decorator (Types) + */ + addDecorator(e, t, o = a.Any) { + let r = new b(e, o, t); + return this.allHandlers[r.registeredName] = r, this.decorators[e] = r.registeredName, r; + } + /** + * Add a callable decorator that wraps the integer type + * @param {String} name The name of the new decorator + * @param {*} callback Callback function to execute + */ + addIntegerDecorator(e, t) { + return this.addDecorator(e, t, a.Integer); + } + /** + * Add a callable decorator that wraps the float type + * @param {String} name The name of the new decorator + * @param {*} callback Callback function to execute + */ + addFloatDecorator(e, t) { + return this.addDecorator(e, t, a.Float); + } + /** + * Add a callable decorator that wraps the integer or float type + * @param {String} name The name of the new decorator + * @param {*} callback Callback function to execute + */ + addNumericDecorator(e, t) { + return this.addDecorator(e, t, a.Numeric); + } + /** + * Add a callable decorator that wraps the string type + * @param {String} name The name of the new decorator + * @param {*} callback Callback function to execute + */ + addStringDecorator(e, t) { + return this.addDecorator(e, t, a.String); + } + /** + * Add a callable decorator that wraps the boolean type + * @param {String} name The name of the new decorator + * @param {*} callback Callback function to execute + */ + addBooleanDecorator(e, t) { + return this.addDecorator(e, t, a.Boolean); + } + /** + * Add a callable decorator that wraps the array type + * @param {String} name The name of the new decorator + * @param {*} callback Callback function to execute + */ + addArrayDecorator(e, t) { + return this.addDecorator(e, t, a.Array); + } + /** + * Add a callable decorator that wraps the object type + * @param {String} name The name of the new decorator + * @param {*} callback Callback function to execute + */ + addObjectDecorator(e, t) { + return this.addDecorator(e, t, a.Object); } } -class x extends v { - constructor(e2) { - super(e2); +class h extends x { + constructor(e) { + super(e); } + /** + * Render the current game state + */ draw() { - const e2 = this.getInterfaceStrings(); - return ["", ...f.getTitledBox(e2.title, e2.description), this.error ? "\nInvalid option!" : "", "What do you do?", ...e2.options.map((e3, t2) => `${t2 + 1} @zarban) ${e3}`), "", 'You can choose an option from above, such as "1 @zarban" and "zarban(2)" or start a new game with "start/restart @zarban" or "zarban("start")"', "Type your selection below and use Lavendeux to continue your adventure!\n"].join("\n"); - } - static registerExtension(e2, t2, o2) { - let a2 = new A(e2, t2, o2); - a2.addStringDecorator("zarban", x.callback), a2.addStringDecorator("Zarban", x.callback), a2.addStringFunction("zarban", x.callback).addStringArgument(), a2.addStringFunction("Zarban", x.callback).addStringArgument(), A.register(a2); - } - static callback(e2, t2) { - ["start", "restart", ""].includes(e2.toLowerCase()) && delete t2.zarban_save; - const o2 = new x(t2.zarban_save), a2 = o2.step(!!t2.zarban_save && e2); - return t2.zarban_save = o2.save(), a2; + const e = this.getInterfaceStrings(); + return [ + "", + ...A.getTitledBox(e.title, e.description), + this.error ? ` +Invalid option!` : "", + "What do you do?", + ...e.options.map((o, r) => `${r + 1} @zarban) ${o}`), + "", + 'You can choose an option from above, such as "1 @zarban" and "zarban(2)" or start a new game with "start/restart @zarban" or "zarban("start")"', + `Type your selection below and use Lavendeux to continue your adventure! +` + ].join(` +`); + } + /** + * Register zarban as a lavendeux extension + */ + static registerExtension(e, t, o) { + let r = new _(e, t, o); + r.addStringDecorator("zarban", h.callback), r.addStringDecorator("Zarban", h.callback), r.addStringFunction("zarban", h.callback).addStringArgument(), r.addStringFunction("Zarban", h.callback).addStringArgument(), _.register(r); + } + /** + * Callback method for running zarban through lavendeux + */ + static callback(e, t) { + ["start", "restart", ""].includes(e.toLowerCase()) && delete t.zarban_save; + const o = new h(t.zarban_save), r = o.step(t.zarban_save ? e : !1); + return t.zarban_save = o.save(), r; } } -x.registerExtension("zarbans_grotto", "@rscarson", "1.0.0"); -class T { - constructor(e2) { - this.e = document.createElement(e2); +h.registerExtension(Y, E, O); +class d { + constructor(e) { + this.e = document.createElement(e); } - setAttribute(e2, t2) { - return this.e.setAttribute(e2, t2), this; + setAttribute(e, t) { + return this.e.setAttribute(e, t), this; } - setInnerHTML(e2) { - return this.e.innerHTML = e2, this; + setInnerHTML(e) { + return this.e.innerHTML = e, this; } - setOnClick(e2) { - return this.e.onclick = e2, this; + setOnClick(e) { + return this.e.onclick = e, this; } - setStyle(e2, t2) { - return this.e.style[e2] = t2, this; + setStyle(e, t) { + return this.e.style[e] = t, this; } element() { return this.e; } } -const j = "zarban_gameboard", S = "zarban_controls"; -class I extends v { - constructor(e2) { - super(localStorage[`zarban_${e2}`]), this.container = document.getElementById(e2), this.injectCSS(e2), this.initContainer(), globalThis.advanceGame = (e3) => this.step(e3), advanceGame(); - } +const w = "zarban_gameboard", k = "zarban_controls"; +class Q extends x { + constructor(e) { + const t = localStorage[`zarban_${e}`]; + super(t), this.container = document.getElementById(e), this.injectCSS(e), this.initContainer(), globalThis.advanceGame = (o) => this.step(o), advanceGame(); + } + /** + * Create the container divs for the game + */ initContainer() { - this.container.dataZarbanRunner = this, this.container.innerHTML = "", this.container.appendChild(new T("div").setAttribute("class", j).element()), this.container.appendChild(new T("div").setAttribute("class", S).element()); - } + this.container.dataZarbanRunner = this, this.container.innerHTML = "", this.container.appendChild( + new d("div").setAttribute("class", w).element() + ), this.container.appendChild( + new d("div").setAttribute("class", k).element() + ); + } + /** + * Inject game style into the page + */ injectCSS() { - let e2 = document.createElement("style"); - e2.type = "text/css", e2.innerHTML = ` + let e = document.createElement("style"); + e.type = "text/css", e.innerHTML = ` body,html { background-color: #333; color: #00AA20; font-family: "Lucida Console", "Courier New", monospace; font-size: 1em; @@ -652,24 +4045,41 @@ class I extends v { } #${this.container.id} a:hover { color: #00CC40; } .zarban_controls a { margin-top: 10px; } - `, document.head.appendChild(e2); + `, document.head.appendChild(e); } + /** + * Save the game state to local storage + */ save() { localStorage[`zarban_${this.container.id}`] = super.save(); } + /** + * Render the current game state + */ draw() { - const e2 = this.getInterfaceStrings(), t2 = this.container.getElementsByClassName(j)[0], o2 = this.container.getElementsByClassName(S)[0]; - e2.description = e2.description.map((e3) => 0 == e3.length ? "
" : e3), t2.innerHTML = "", t2.appendChild(new T("a").setAttribute("href", "#").setStyle("float", "right").setInnerHTML("[ New Game ]").setOnClick(() => this.reset()).element()), t2.appendChild(new T("h4").setInnerHTML(e2.title).element()), e2.description.map((e3) => { - t2.appendChild(new T("p").setInnerHTML(e3).element()); + const e = this.getInterfaceStrings(), t = this.container.getElementsByClassName(w)[0], o = this.container.getElementsByClassName(k)[0]; + e.description = e.description.map((s) => s.length == 0 ? "
" : s), t.innerHTML = "", t.appendChild( + new d("a").setAttribute("href", "#").setStyle("float", "right").setInnerHTML("[ New Game ]").setOnClick(() => this.reset()).element() + ), t.appendChild( + new d("h4").setInnerHTML(e.title).element() + ), e.description.map((s) => { + t.appendChild( + new d("p").setInnerHTML(s).element() + ); }); - const a2 = document.createElement("p"); - a2.innerHTML = "What do you do?
"; - for (const t3 in e2.options) - a2.appendChild(new T("a").setAttribute("href", "#").setInnerHTML(`> ${e2.options[t3]}`).setOnClick(() => this.step(parseInt(t3) + 1)).element()); - o2.innerHTML = "", o2.appendChild(a2); - } - step(e2) { - super.step(e2), this.save(); + const r = document.createElement("p"); + r.innerHTML = "What do you do?
"; + for (const s in e.options) + r.appendChild( + new d("a").setAttribute("href", "#").setInnerHTML(`> ${e.options[s]}`).setOnClick(() => this.step(parseInt(s) + 1)).element() + ); + o.innerHTML = "", o.appendChild(r); + } + /** + * Advance the game state + */ + step(e) { + super.step(e), this.save(); } } -globalThis.zarban = (e2) => new I(e2); +globalThis.play_zarban = (n) => new Q(n); diff --git a/example_scripts/populate_state.lav b/example_scripts/populate_state.lav index cb8b0ec..2e50cb9 100644 --- a/example_scripts/populate_state.lav +++ b/example_scripts/populate_state.lav @@ -1,4 +1,6 @@ +// You can register functions and variables factorial(x) = x==0 ? 1 : (x * factorial(x - 1) ) important_value = 5 +// Or call functions to set up the parser api_register("animechan", "https://animechan.vercel.app/api/random") \ No newline at end of file diff --git a/examples/adding_functionality.rs b/examples/adding_functionality.rs index 80c2f19..8a4b250 100644 --- a/examples/adding_functionality.rs +++ b/examples/adding_functionality.rs @@ -1,35 +1,39 @@ +use lavendeux_parser::Error; +use lavendeux_parser::ExpectedTypes; +use lavendeux_parser::{DecoratorDefinition, FunctionArgument, FunctionDefinition}; use lavendeux_parser::{ParserState, Token, Value}; -use lavendeux_parser::{DecoratorDefinition, FunctionDefinition, FunctionArgument}; -use lavendeux_parser::errors::*; -fn main() -> Result<(), ParserError> { +fn main() -> Result<(), Error> { // Load the extensions into our parser - let mut state : ParserState = ParserState::new(); + let mut state: ParserState = ParserState::new(); // Register a new function state.functions.register(FunctionDefinition { name: "echo", category: None, description: "Echo back the provided input", - arguments: || vec![ - FunctionArgument::new_required("input", ExpectedTypes::String), - ], + arguments: || { + vec![FunctionArgument::new_required( + "input", + ExpectedTypes::String, + )] + }, handler: |_function, _token, _state, args| { Ok(Value::String(args.get("input").required().as_string())) - } + }, }); - + // Register a new decorator state.decorators.register(DecoratorDefinition { name: &["upper", "uppercase"], description: "Outputs an uppercase version of the input", argument: ExpectedTypes::Any, - handler: |_, _token, input| Ok(input.as_string().to_uppercase()) + handler: |_, _token, input| Ok(input.as_string().to_uppercase()), }); - + // Now we can use the new functions and @decorators let token = Token::new("echo('test') @upper", &mut state)?; assert_eq!(token.text(), "TEST"); Ok(()) -} \ No newline at end of file +} diff --git a/examples/basic_expressions.rs b/examples/basic_expressions.rs index 04af0f3..25e15d8 100644 --- a/examples/basic_expressions.rs +++ b/examples/basic_expressions.rs @@ -1,6 +1,6 @@ -use lavendeux_parser::{ParserState, ParserError, Token}; +use lavendeux_parser::{Error, ParserState, Token}; -fn main() -> Result<(), ParserError> { +fn main() -> Result<(), Error> { let expression = " factorial(x) = x==0 ? 1 : (x * factorial(x - 1) ) x = factorial(5) @@ -14,11 +14,11 @@ true "; // Tokenize the expression - let mut state : ParserState = ParserState::new(); + let mut state: ParserState = ParserState::new(); let lines = Token::new(expression, &mut state)?; - + assert_eq!(lines.text(), expected_result); assert_eq!(lines.child(1).unwrap().value(), 120); Ok(()) -} \ No newline at end of file +} diff --git a/examples/interactive_console.rs b/examples/interactive_console.rs index ec39d9f..21de2b5 100644 --- a/examples/interactive_console.rs +++ b/examples/interactive_console.rs @@ -1,26 +1,28 @@ -use lavendeux_parser::{ ParserState, Token }; +use lavendeux_parser::{ParserState, Token}; use std::collections::VecDeque; -use std::io::{ stdin, stdout, Write }; use std::env; +use std::io::{stdin, stdout, Write}; /// Get the next command from the user fn next_command() -> String { let mut input = String::new(); print!("> "); - let _=stdout().flush(); - stdin().read_line(&mut input).expect("error: unable to read user input"); + let _ = stdout().flush(); + stdin() + .read_line(&mut input) + .expect("error: unable to read user input"); return input.trim().to_string(); } fn main() { - let mut state : ParserState = ParserState::new(); + let mut state: ParserState = ParserState::new(); // Load extensions - let results = state.extensions.load_all("./example_extensions"); + let results = state.extensions.load_all("example_extensions"); for result in results { if let Err(err) = result { - println!("{}", err); + println!("could not load an extension: {}", err); } } @@ -35,7 +37,7 @@ fn main() { loop { // Make sure we have a command ready if stack.is_empty() { - stack.push_back( next_command() ); + stack.push_back(next_command()); } let cmd = stack.pop_front().unwrap(); @@ -47,8 +49,8 @@ fn main() { // Process the command match Token::new(&cmd, &mut state) { Ok(result) => println!("{}", result.text()), - Err(e) => eprintln!("{}: {}", cmd, e) + Err(e) => eprintln!("{}: {}", cmd, e), } } } -} \ No newline at end of file +} diff --git a/examples/using_extensions.rs b/examples/using_extensions.rs index 29fba24..63c2286 100644 --- a/examples/using_extensions.rs +++ b/examples/using_extensions.rs @@ -1,8 +1,8 @@ -use lavendeux_parser::{ParserState, ParserError, Token}; +use lavendeux_parser::{Error, ParserState, Token}; -fn main() -> Result<(), ParserError> { +fn main() -> Result<(), Error> { // Load the extensions into our parser - let mut state : ParserState = ParserState::new(); + let mut state: ParserState = ParserState::new(); let results = state.extensions.load_all("./example_extensions"); for result in results { @@ -10,7 +10,7 @@ fn main() -> Result<(), ParserError> { println!("{}", err); } } - + // Now we can use the new functions and @decorators let command = "complement(0xFF0000) @colour"; println!("Running: {}", command); @@ -20,4 +20,4 @@ fn main() -> Result<(), ParserError> { assert_eq!(token.text(), "#ffff00"); Ok(()) -} \ No newline at end of file +} diff --git a/src/decorators.rs b/src/decorators.rs deleted file mode 100644 index 0b18390..0000000 --- a/src/decorators.rs +++ /dev/null @@ -1,542 +0,0 @@ -use crate::{Value, errors::*, Token, value::ObjectType}; - -use std::collections::HashMap; -use chrono::prelude::*; - -/// Handler for executing a decorator -pub type DecoratorHandler = fn(&DecoratorDefinition, &Token, &Value) -> Result; - -/// Holds a set of callable decorators -#[derive(Clone)] -pub struct DecoratorTable(HashMap); -impl DecoratorTable { - /// Initialize a new decorator table, complete with default builtin decorators - pub fn new() -> DecoratorTable { - let mut table : DecoratorTable = DecoratorTable(HashMap::new()); - - table.register(DEFAULT); - table.register(HEX); - table.register(OCT); - table.register(BIN); - - table.register(SCI); - table.register(FLOAT); - table.register(INT); - table.register(BOOL); - table.register(ARRAY); - table.register(OBJECT); - - table.register(UTC); - table.register(DOLLAR); - table.register(EURO); - table.register(POUND); - table.register(YEN); - - table.register(ROMAN); - table.register(ORDINAL); - table.register(PERCENTAGE); - - table - } - - /// Register a decorator in the table - /// - /// # Arguments - /// * `name` - Decorator name - /// * `handler` - Decorator handler - pub fn register(&mut self, definition: DecoratorDefinition) { - for name in definition.name() { - self.0.insert(name.to_string(), definition.clone()); - } - } - - /// Check if the table contains a decorator by the given name - /// - /// # Arguments - /// * `name` - Decorator name - pub fn has(&self, name: &str) -> bool { - self.0.contains_key(name) - } - - /// Return a given decorator - /// - /// # Arguments - /// * `name` - Function name - pub fn get(&self, name: &str) -> Option<&DecoratorDefinition> { - self.0.get(name) - } - - /// Get a collection of all included decorators - pub fn all(&self) -> Vec<&DecoratorDefinition> { - let mut a: Vec<&DecoratorDefinition> = self.0.values().collect(); - a.sort_by(|f1, f2|f1.name()[0].cmp(f2.name()[0])); - a - } - - /// Call a decorator - /// - /// # Arguments - /// * `name` - Decorator name - /// * `args` - Decorator arguments - pub fn call(&self, name: &str, token: &Token, arg: &Value) -> Result { - match self.0.get(name) { - Some(f) => f.call(token, arg), - None => Err(DecoratorNameError::new(token, name).into()) - } - } -} - -impl Default for DecoratorTable { - fn default() -> Self { - Self::new() - } -} - -/// Holds the definition of a builtin callable decorator -#[derive(Clone)] -pub struct DecoratorDefinition { - /// Decorator call name - pub name: &'static [&'static str], - - /// Decorator short description - pub description: &'static str, - - /// Type of input the decorator expects - pub argument: ExpectedTypes, - - /// Handler function - pub handler: DecoratorHandler -} -impl DecoratorDefinition { - /// Return the decorator's names - pub fn name(&self) -> &[&str] { - self.name - } - - /// Return the decorator's description - pub fn description(&self) -> &str { - self.description - } - - /// Return the decorator's argument type - pub fn arg(&self) -> ExpectedTypes { - self.argument.clone() - } - - /// Return the decorator's signature - pub fn signature(&self) -> String { - self.name.iter().map(|n|format!("@{n}")).collect::>().join("/") - } - - /// Return the decorator's signature - pub fn help(&self) -> String { - format!("{}: {}", self.signature(), self.description) - } - - /// Validate decorator arguments, and return an error if one exists - /// - /// # Arguments - /// * `arg` - Decorator input - pub fn validate(&self, token: &Token, arg: &Value) -> Option { - if !self.arg().matches(arg) { - Some(DecoratorArgTypeError::new(token, &self.signature(), self.arg()).into()) - } else { - None - } - } - - // Call the associated decorator handler - /// - /// # Arguments - /// * `arg` - Decorator input - pub fn call(&self, token: &Token, arg: &Value) -> Result { - if let Some(error) = self.validate(token, arg) { - Err(error) - } else { - (self.handler)(self, token, arg) - } - } -} - -fn decorator_currency(input: &Value, symbol: &str) -> Result { - let n = input.as_float().unwrap(); - let mut f = format!("{}{:.2}", symbol, n); - if !f.contains('.') { - f += ".0"; - } - f = f - .chars().rev().collect::>() - .chunks(3).map(|c| c.iter().collect::()).collect::>().join(",") - .replacen(',', "", 1) - .chars().rev().collect::(); - if f.chars().nth(1).unwrap() == ',' { - f = f.replacen(',', "", 1); - } - Ok(f) -} - -fn pluralized_decorator(decorator: &DecoratorDefinition, token: &Token, input: &Value) -> Result { - match input { - Value::Array(v) => { - let mut output : Vec = Vec::new(); - for value in v { - match decorator.call(token, value) { - Ok(s) => output.push(Value::from(s)), - Err(e) => return Err(e) - } - } - Ok(Value::from(output).as_string()) - }, - - Value::Object(v) => { - let mut output : ObjectType = ObjectType::new(); - for (value, key) in v { - match decorator.call(token, value) { - Ok(s) => {output.insert(key.clone(), Value::from(s));}, - Err(e) => return Err(e) - } - } - Ok(Value::from(output).as_string()) - }, - - _ => decorator.call(token, input) - } -} - -const DEFAULT : DecoratorDefinition = DecoratorDefinition { - name: &["default"], - description: "Default formatter, type dependent", - argument: ExpectedTypes::Any, - handler: |_, token, input| match input { - Value::Boolean(_) => (BOOL.handler)(&BOOL, token, input), - Value::Integer(_) => (INT.handler)(&INT, token, input), - Value::Float(_) => (FLOAT.handler)(&FLOAT, token, input), - Value::Array(_) => (ARRAY.handler)(&ARRAY, token, input), - Value::Object(_) => (OBJECT.handler)(&OBJECT, token, input), - Value::String(s) => Ok(s.to_string()), - Value::Identifier(_) => Ok("".to_string()), - Value::None => Ok("".to_string()) - } -}; - -const HEX : DecoratorDefinition = DecoratorDefinition { - name: &["hex"], - description: "Base 16 number formatting, such as 0xFF", - argument: ExpectedTypes::IntOrFloat, - handler: |decorator, token, input| { - if decorator.arg().strict_matches(input) { - Ok(format!("{:#0x}", input.as_int().unwrap())) - } else { - pluralized_decorator(decorator, token, input) - } - } -}; - -const OCT : DecoratorDefinition = DecoratorDefinition { - name: &["oct"], - description: "Base 8 number formatting, such as 0b77", - argument: ExpectedTypes::IntOrFloat, - handler: |decorator, token, input| { - if decorator.arg().strict_matches(input) { - Ok(format!("{:#0o}", input.as_int().unwrap())) - } else { - pluralized_decorator(decorator, token, input) - } - } -}; - -const BIN : DecoratorDefinition = DecoratorDefinition { - name: &["bin"], - description: "Base 2 number formatting, such as 0b11", - argument: ExpectedTypes::IntOrFloat, - handler: |decorator, token, input| { - if decorator.arg().strict_matches(input) { - Ok(format!("{:#0b}", input.as_int().unwrap())) - } else { - pluralized_decorator(decorator, token, input) - } - } -}; - -const SCI : DecoratorDefinition = DecoratorDefinition { - name: &["sci"], - description: "Scientific number formatting, such as 1.2Ee-3", - argument: ExpectedTypes::IntOrFloat, - handler: |decorator, token, input| { - if decorator.arg().strict_matches(input) { - Ok(format!("{:e}", input.as_float().unwrap())) - } else { - pluralized_decorator(decorator, token, input) - } - } -}; - -const UTC : DecoratorDefinition = DecoratorDefinition { - name: &["utc"], - description: "Interprets an integer as a timestamp, and formats it in UTC standard", - argument: ExpectedTypes::IntOrFloat, - handler: |decorator, token, input| { - if decorator.arg().strict_matches(input) { - let n = input.as_int().unwrap(); - match NaiveDateTime::from_timestamp_millis(n*1000) { - Some(t) => { - let datetime: DateTime = DateTime::from_utc(t, Utc); - Ok(datetime.format("%Y-%m-%d %H:%M:%S").to_string()) - }, - None => Err(RangeError::new(token, input).into()) - } - } else { - pluralized_decorator(decorator, token, input) - } - } -}; - -const DOLLAR : DecoratorDefinition = DecoratorDefinition { - name: &["dollar", "dollars", "usd", "aud", "cad"], - description: "Format a number as a dollar amount", - argument: ExpectedTypes::IntOrFloat, - handler: |decorator, token, input| { - if decorator.arg().strict_matches(input) { - decorator_currency(input, "$") - } else { - pluralized_decorator(decorator, token, input) - } - } -}; - -const EURO : DecoratorDefinition = DecoratorDefinition { - name: &["euro", "euros"], - description: "Format a number as a euro amount", - argument: ExpectedTypes::IntOrFloat, - handler: |decorator, token, input| { - if decorator.arg().strict_matches(input) { - decorator_currency(input, "€") - } else { - pluralized_decorator(decorator, token, input) - } - } -}; - -const POUND : DecoratorDefinition = DecoratorDefinition { - name: &["pound", "pounds"], - description: "Format a number as a pound amount", - argument: ExpectedTypes::IntOrFloat, - handler: |decorator, token, input| { - if decorator.arg().strict_matches(input) { - decorator_currency(input, "£") - } else { - pluralized_decorator(decorator, token, input) - } - } -}; - -const YEN : DecoratorDefinition = DecoratorDefinition { - name: &["yen"], - description: "Format a number as a yen amount", - argument: ExpectedTypes::IntOrFloat, - handler: |decorator, token, input| { - if decorator.arg().strict_matches(input) { - decorator_currency(input, "¥") - } else { - pluralized_decorator(decorator, token, input) - } - } -}; - -const FLOAT : DecoratorDefinition = DecoratorDefinition { - name: &["float"], - description: "Format a number as floating point", - argument: ExpectedTypes::IntOrFloat, - handler: |decorator, token, input| { - if decorator.arg().strict_matches(input) { - Ok(Value::Float(input.as_float().unwrap()).as_string()) - } else { - pluralized_decorator(decorator, token, input) - } - } -}; - -const INT : DecoratorDefinition = DecoratorDefinition { - name: &["int", "integer"], - description: "Format a number as an integer", - argument: ExpectedTypes::IntOrFloat, - handler: |decorator, token, input| { - if decorator.arg().strict_matches(input) { - Ok(Value::Integer(input.as_int().unwrap()).as_string()) - } else { - pluralized_decorator(decorator, token, input) - } - } -}; - -const BOOL : DecoratorDefinition = DecoratorDefinition { - name: &["bool", "boolean"], - description: "Format a number as a boolean", - argument: ExpectedTypes::Any, - handler: |_, _, input| Ok(Value::Boolean(input.as_bool()).as_string()) -}; - -const ARRAY : DecoratorDefinition = DecoratorDefinition { - name: &["array"], - description: "Format a number as an array", - argument: ExpectedTypes::Any, - handler: |_, _, input| Ok(Value::Array(input.as_array()).as_string()) -}; - -const OBJECT : DecoratorDefinition = DecoratorDefinition { - name: &["object"], - description: "Format a number as an object", - argument: ExpectedTypes::Any, - handler: |_, _, input| Ok(Value::Object(input.as_object()).as_string()) -}; - -const PERCENTAGE : DecoratorDefinition = DecoratorDefinition { - name: &["percentage", "percent"], - description: "Format a floating point number as a percentage", - argument: ExpectedTypes::IntOrFloat, - handler: |decorator, token, input| { - if decorator.arg().strict_matches(input) { - Ok(format!("{}%", input.as_float().unwrap()*100.0)) - } else { - pluralized_decorator(decorator, token, input) - } - } -}; - -const ORDINAL : DecoratorDefinition = DecoratorDefinition { - name: &["percentage", "percent"], - description: "Format an integer as an ordinal (1st, 38th, etc)", - argument: ExpectedTypes::IntOrFloat, - handler: |decorator, token, input| { - if decorator.arg().strict_matches(input) { - let v = Value::Integer(input.as_int().unwrap()).as_string(); - let suffix = - if v.ends_with('1') { "st" } - else if v.ends_with('2') { "nd" } - else if v.ends_with('3') { "rd" } - else { "th" }; - Ok(format!("{}{}", v, suffix)) - } else { - pluralized_decorator(decorator, token, input) - } - } -}; - -const ROMAN : DecoratorDefinition = DecoratorDefinition { - name: &["roman"], - description: "Format an integer as a roman numeral", - argument: ExpectedTypes::IntOrFloat, - handler: |decorator, token, input| { - if decorator.arg().strict_matches(input) { - let mut value = input.as_int().unwrap(); - if value > 3999 { - return Err(OverflowError::new(token).into()); - } - - let roman_numerals = vec![ - (1000, "M"), (900, "CM"), - (500, "D"), (400, "CD"), - (100, "C"), (90, "XC"), - (50, "L"), (40, "XL"), - (10, "X"), (9, "IX"), - (5, "V"), (4, "IV"), - (1, "I"), - ]; - let mut roman_numeral = String::new(); - for (n, r) in roman_numerals { - while value >= n { - roman_numeral.push_str(r); - value -= n; - } - } - Ok(roman_numeral) - } else { - pluralized_decorator(decorator, token, input) - } - } -}; - -#[cfg(test)] -mod test_builtin_functions { - use super::*; - - #[test] - fn test_default() { - } - - #[test] - fn test_hex() { - assert_eq!("0xff", HEX.call(&Token::dummy(""), &Value::Integer(255)).unwrap()); - assert_eq!("0xff", HEX.call(&Token::dummy(""), &Value::Float(255.1)).unwrap()); - } - - #[test] - fn test_bin() { - assert_eq!("0b11111111", BIN.call(&Token::dummy(""), &Value::Integer(255)).unwrap()); - assert_eq!("0b11111111", BIN.call(&Token::dummy(""), &Value::Float(255.1)).unwrap()); - } - - #[test] - fn test_oct() { - assert_eq!("0o10", OCT.call(&Token::dummy(""), &Value::Integer(8)).unwrap()); - assert_eq!("0o10", OCT.call(&Token::dummy(""), &Value::Float(8.1)).unwrap()); - } - - #[test] - fn test_sci() { - assert_eq!("8e0", SCI.call(&Token::dummy(""), &Value::Integer(8)).unwrap()); - assert_eq!("-8.1e1", SCI.call(&Token::dummy(""), &Value::Float(-81.0)).unwrap()); - assert_eq!("8.1e-2", SCI.call(&Token::dummy(""), &Value::Float(0.081)).unwrap()); - } - - #[test] - fn test_float() { - assert_eq!("8.0", FLOAT.call(&Token::dummy(""), &Value::Integer(8)).unwrap()); - assert_eq!("81.0", FLOAT.call(&Token::dummy(""), &Value::Float(81.0)).unwrap()); - assert_eq!("0.0", FLOAT.call(&Token::dummy(""), &Value::Float(0.0000000001)).unwrap()); - assert_eq!("0.081", FLOAT.call(&Token::dummy(""), &Value::Float(0.081)).unwrap()); - } - - #[test] - fn test_int() { - assert_eq!("-8", INT.call(&Token::dummy(""), &Value::Integer(-8)).unwrap()); - assert_eq!("81", INT.call(&Token::dummy(""), &Value::Float(81.0)).unwrap()); - assert_eq!("0", INT.call(&Token::dummy(""), &Value::Float(0.081)).unwrap()); - } - - #[test] - fn test_bool() { - assert_eq!("false", BOOL.call(&Token::dummy(""), &Value::Integer(0)).unwrap()); - assert_eq!("true", BOOL.call(&Token::dummy(""), &Value::Integer(81)).unwrap()); - assert_eq!("true", BOOL.call(&Token::dummy(""), &Value::Float(0.081)).unwrap()); - } - - #[test] - fn test_dollars() { - assert_eq!("¥100.00", YEN.call(&Token::dummy(""), &Value::Integer(100)).unwrap()); - assert_eq!("$1,000.00", DOLLAR.call(&Token::dummy(""), &Value::Integer(1000)).unwrap()); - assert_eq!("€10,000.00", EURO.call(&Token::dummy(""), &Value::Integer(10000)).unwrap()); - assert_eq!("£100,000.00", POUND.call(&Token::dummy(""), &Value::Integer(100000)).unwrap()); - assert_eq!("£1,000,000.00", POUND.call(&Token::dummy(""), &Value::Integer(1000000)).unwrap()); - } - - #[test] - fn test_utc() { - assert_eq!("2022-03-20 14:05:33", UTC.call(&Token::dummy(""), &Value::Integer(1647785133)).unwrap()); - } - - #[test] - fn test_ordinal() { - assert_eq!("32nd", ORDINAL.call(&Token::dummy(""), &Value::Integer(32)).unwrap()); - } - - #[test] - fn test_percentage() { - assert_eq!("32.5%", PERCENTAGE.call(&Token::dummy(""), &Value::Float(0.325)).unwrap()); - } - - #[test] - fn test_roman() { - assert_eq!("XXVI", ROMAN.call(&Token::dummy(""), &Value::Integer(26)).unwrap()); - } -} \ No newline at end of file diff --git a/src/decorators/currency.rs b/src/decorators/currency.rs new file mode 100644 index 0000000..513586f --- /dev/null +++ b/src/decorators/currency.rs @@ -0,0 +1,119 @@ +use super::pluralized_decorator; +use crate::{Error, ExpectedTypes, Value}; + +fn decorator_currency(input: &Value, symbol: &str) -> Result { + let n = input.as_float().unwrap(); + let mut f = format!("{}{:.2}", symbol, n); + if !f.contains('.') { + f += ".0"; + } + f = f + .chars() + .rev() + .collect::>() + .chunks(3) + .map(|c| c.iter().collect::()) + .collect::>() + .join(",") + .replacen(',', "", 1) + .chars() + .rev() + .collect::(); + if f.chars().nth(1).unwrap() == ',' { + f = f.replacen(',', "", 1); + } + Ok(f) +} + +define_decorator!( + name = dollar, + aliases = ["dollars", "usd", "aud", "cad"], + description = "Format a number as a dollar amount", + input = ExpectedTypes::IntOrFloat, + handler = |decorator, token, input| { + if decorator.arg().strict_matches(input) { + decorator_currency(input, "$") + } else { + pluralized_decorator(decorator, token, input) + } + } +); + +define_decorator!( + name = euro, + aliases = ["euros"], + description = "Format a number as a euro amount", + input = ExpectedTypes::IntOrFloat, + handler = |decorator, token, input| { + if decorator.arg().strict_matches(input) { + decorator_currency(input, "€") + } else { + pluralized_decorator(decorator, token, input) + } + } +); + +define_decorator!( + name = pound, + aliases = ["pounds"], + description = "Format a number as a pound amount", + input = ExpectedTypes::IntOrFloat, + handler = |decorator, token, input| { + if decorator.arg().strict_matches(input) { + decorator_currency(input, "£") + } else { + pluralized_decorator(decorator, token, input) + } + } +); + +define_decorator!( + name = yen, + description = "Format a number as a yen amount", + input = ExpectedTypes::IntOrFloat, + handler = |decorator, token, input| { + if decorator.arg().strict_matches(input) { + decorator_currency(input, "¥") + } else { + pluralized_decorator(decorator, token, input) + } + } +); + +#[cfg(test)] +mod test_builtin_functions { + use crate::Token; + + use super::*; + + #[test] + fn test_currencies() { + assert_eq!( + "¥100.00", + yen.call(&Token::dummy(""), &Value::Integer(100)).unwrap() + ); + assert_eq!( + "$1,000.00", + dollar + .call(&Token::dummy(""), &Value::Integer(1000)) + .unwrap() + ); + assert_eq!( + "€10,000.00", + euro.call(&Token::dummy(""), &Value::Integer(10000)) + .unwrap() + ); + assert_eq!( + "£100,000.00", + pound + .call(&Token::dummy(""), &Value::Integer(100000)) + .unwrap() + ); + assert_eq!( + "£1,000,000.00", + pound + .call(&Token::dummy(""), &Value::Integer(1000000)) + .unwrap() + ); + } +} diff --git a/src/decorators/mod.rs b/src/decorators/mod.rs new file mode 100644 index 0000000..ee68daa --- /dev/null +++ b/src/decorators/mod.rs @@ -0,0 +1,233 @@ +use crate::{value::ObjectType, Error, ExpectedTypes, Token, Value}; +use std::collections::HashMap; + +#[macro_use] +pub mod decorator_macros { + /// Defines a function for registration as a builtin + #[macro_export] + macro_rules! define_decorator { + ( + name = $function_name:ident, + $(aliases = [$($function_alias:literal),+],)? + description = $function_desc:literal, + input = $function_arg:expr, + handler = $function_impl:expr + ) => { + /// Decorator definition for use with Lavendeux + /// It should be registered with 'decorator_table.register() + #[allow(non_upper_case_globals, unused_variables)] + pub const $function_name: $crate::DecoratorDefinition = $crate::DecoratorDefinition { + name: &[stringify!($function_name)$(, $($function_alias),+)?], + description: $function_desc, + argument: $function_arg, + handler: $function_impl, + }; + }; + } +} + +mod currency; +mod numeric; +mod primitives; +mod string; + +/// Handler for executing a decorator +pub type DecoratorHandler = fn(&DecoratorDefinition, &Token, &Value) -> Result; + +/// Holds a set of callable decorators +#[derive(Clone)] +pub struct DecoratorTable(HashMap); +impl DecoratorTable { + /// Initialize a new decorator table, complete with default builtin decorators + pub fn new() -> DecoratorTable { + let mut table: DecoratorTable = DecoratorTable(HashMap::new()); + + table.register(numeric::hex); + table.register(numeric::oct); + table.register(numeric::bin); + table.register(numeric::sci); + table.register(numeric::utc); + + table.register(currency::dollar); + table.register(currency::euro); + table.register(currency::pound); + table.register(currency::yen); + + table.register(primitives::DEFAULT); + table.register(primitives::FLOAT); + table.register(primitives::INT); + table.register(primitives::BOOL); + table.register(primitives::ARRAY); + table.register(primitives::OBJECT); + + table.register(string::ROMAN); + table.register(string::ORDINAL); + table.register(string::PERCENTAGE); + + table + } + + /// Register a decorator in the table + /// + /// # Arguments + /// * `name` - Decorator name + /// * `handler` - Decorator handler + pub fn register(&mut self, definition: DecoratorDefinition) { + for name in definition.name() { + self.0.insert(name.to_string(), definition.clone()); + } + } + + /// Check if the table contains a decorator by the given name + /// + /// # Arguments + /// * `name` - Decorator name + pub fn has(&self, name: &str) -> bool { + self.0.contains_key(name) + } + + /// Return a given decorator + /// + /// # Arguments + /// * `name` - Function name + pub fn get(&self, name: &str) -> Option<&DecoratorDefinition> { + self.0.get(name) + } + + /// Get a collection of all included decorators + pub fn all(&self) -> Vec<&DecoratorDefinition> { + let mut a: Vec<&DecoratorDefinition> = self.0.values().collect(); + a.sort_by(|f1, f2| f1.name()[0].cmp(f2.name()[0])); + a + } + + /// Call a decorator + /// + /// # Arguments + /// * `name` - Decorator name + /// * `args` - Decorator arguments + pub fn call(&self, name: &str, token: &Token, arg: &Value) -> Result { + match self.0.get(name) { + Some(f) => f.call(token, arg), + None => Err(Error::DecoratorName { + name: name.to_string(), + token: token.clone(), + }), + } + } +} + +impl Default for DecoratorTable { + fn default() -> Self { + Self::new() + } +} + +/// Holds the definition of a builtin callable decorator +#[derive(Clone)] +pub struct DecoratorDefinition { + /// Decorator call name + pub name: &'static [&'static str], + + /// Decorator short description + pub description: &'static str, + + /// Type of input the decorator expects + pub argument: ExpectedTypes, + + /// Handler function + pub handler: DecoratorHandler, +} +impl DecoratorDefinition { + /// Return the decorator's names + pub fn name(&self) -> &[&str] { + self.name + } + + /// Return the decorator's description + pub fn description(&self) -> &str { + self.description + } + + /// Return the decorator's argument type + pub fn arg(&self) -> ExpectedTypes { + self.argument.clone() + } + + /// Return the decorator's signature + pub fn signature(&self) -> String { + self.name + .iter() + .map(|n| format!("@{n}")) + .collect::>() + .join("/") + } + + /// Return the decorator's signature + pub fn help(&self) -> String { + format!("{}: {}", self.signature(), self.description) + } + + /// Validate decorator arguments, and return an error if one exists + /// + /// # Arguments + /// * `arg` - Decorator input + pub fn validate(&self, token: &Token, arg: &Value) -> Option { + if !self.arg().matches(arg) { + Some(Error::DecoratorArgumentType { + name: self.signature(), + expected_type: self.arg(), + token: token.clone(), + }) + } else { + None + } + } + + // Call the associated decorator handler + /// + /// # Arguments + /// * `arg` - Decorator input + pub fn call(&self, token: &Token, arg: &Value) -> Result { + if let Some(error) = self.validate(token, arg) { + Err(error) + } else { + (self.handler)(self, token, arg) + } + } +} + +/// Runs a decorator on plural types +pub fn pluralized_decorator( + decorator: &DecoratorDefinition, + token: &Token, + input: &Value, +) -> Result { + match input { + Value::Array(v) => { + let mut output: Vec = Vec::new(); + for value in v { + match decorator.call(token, value) { + Ok(s) => output.push(Value::from(s)), + Err(e) => return Err(e), + } + } + Ok(Value::from(output).as_string()) + } + + Value::Object(v) => { + let mut output: ObjectType = ObjectType::new(); + for (value, key) in v { + match decorator.call(token, value) { + Ok(s) => { + output.insert(key.clone(), Value::from(s)); + } + Err(e) => return Err(e), + } + } + Ok(Value::from(output).as_string()) + } + + _ => decorator.call(token, input), + } +} diff --git a/src/decorators/numeric.rs b/src/decorators/numeric.rs new file mode 100644 index 0000000..c3dd4ec --- /dev/null +++ b/src/decorators/numeric.rs @@ -0,0 +1,136 @@ +use super::pluralized_decorator; +use crate::{Error, ExpectedTypes}; +use chrono::{DateTime, NaiveDateTime, Utc}; + +define_decorator!( + name = hex, + description = "Base 16 number formatting, such as 0xFF", + input = ExpectedTypes::IntOrFloat, + handler = |decorator, token, input| { + if decorator.arg().strict_matches(input) { + Ok(format!("{:#0x}", input.as_int().unwrap())) + } else { + pluralized_decorator(decorator, token, input) + } + } +); + +define_decorator!( + name = oct, + description = "Base 8 number formatting, such as 0b77", + input = ExpectedTypes::IntOrFloat, + handler = |decorator, token, input| { + if decorator.arg().strict_matches(input) { + Ok(format!("{:#0o}", input.as_int().unwrap())) + } else { + pluralized_decorator(decorator, token, input) + } + } +); + +define_decorator!( + name = bin, + description = "Base 2 number formatting, such as 0b11", + input = ExpectedTypes::IntOrFloat, + handler = |decorator, token, input| { + if decorator.arg().strict_matches(input) { + Ok(format!("{:#0b}", input.as_int().unwrap())) + } else { + pluralized_decorator(decorator, token, input) + } + } +); + +define_decorator!( + name = sci, + description = "Scientific number formatting, such as 1.2Ee-3", + input = ExpectedTypes::IntOrFloat, + handler = |decorator, token, input| { + if decorator.arg().strict_matches(input) { + Ok(format!("{:e}", input.as_float().unwrap())) + } else { + pluralized_decorator(decorator, token, input) + } + } +); + +define_decorator!( + name = utc, + description = "Interprets an integer as a timestamp, and formats it in UTC standard", + input = ExpectedTypes::IntOrFloat, + handler = |decorator, token, input| { + if decorator.arg().strict_matches(input) { + let n = input.as_int().unwrap(); + match NaiveDateTime::from_timestamp_millis(n * 1000) { + Some(t) => { + let datetime: DateTime = DateTime::from_naive_utc_and_offset(t, Utc); + Ok(datetime.format("%Y-%m-%d %H:%M:%S").to_string()) + } + None => Err(Error::Range { + value: input.clone(), + token: token.clone(), + }), + } + } else { + pluralized_decorator(decorator, token, input) + } + } +); + +#[cfg(test)] +mod test_builtin_functions { + use super::*; + use crate::{Token, Value}; + + #[test] + fn test_hex() { + assert_eq!( + "0xff", + hex.call(&Token::dummy(""), &Value::Integer(255)).unwrap() + ); + assert_eq!( + "0xff", + hex.call(&Token::dummy(""), &Value::Float(255.1)).unwrap() + ); + } + + #[test] + fn test_bin() { + assert_eq!( + "0b11111111", + bin.call(&Token::dummy(""), &Value::Integer(255)).unwrap() + ); + assert_eq!( + "0b11111111", + bin.call(&Token::dummy(""), &Value::Float(255.1)).unwrap() + ); + } + + #[test] + fn test_oct() { + assert_eq!( + "0o10", + oct.call(&Token::dummy(""), &Value::Integer(8)).unwrap() + ); + assert_eq!( + "0o10", + oct.call(&Token::dummy(""), &Value::Float(8.1)).unwrap() + ); + } + + #[test] + fn test_sci() { + assert_eq!( + "8e0", + sci.call(&Token::dummy(""), &Value::Integer(8)).unwrap() + ); + assert_eq!( + "-8.1e1", + sci.call(&Token::dummy(""), &Value::Float(-81.0)).unwrap() + ); + assert_eq!( + "8.1e-2", + sci.call(&Token::dummy(""), &Value::Float(0.081)).unwrap() + ); + } +} diff --git a/src/decorators/primitives.rs b/src/decorators/primitives.rs new file mode 100644 index 0000000..aa3aa45 --- /dev/null +++ b/src/decorators/primitives.rs @@ -0,0 +1,127 @@ +use crate::{DecoratorDefinition, ExpectedTypes, Value}; + +use super::pluralized_decorator; + +pub const DEFAULT: DecoratorDefinition = DecoratorDefinition { + name: &["default"], + description: "Default formatter, type dependent", + argument: ExpectedTypes::Any, + handler: |_, token, input| match input { + Value::Boolean(_) => (BOOL.handler)(&BOOL, token, input), + Value::Integer(_) => (INT.handler)(&INT, token, input), + Value::Float(_) => (FLOAT.handler)(&FLOAT, token, input), + Value::Array(_) => (ARRAY.handler)(&ARRAY, token, input), + Value::Object(_) => (OBJECT.handler)(&OBJECT, token, input), + Value::String(s) => Ok(s.to_string()), + Value::Identifier(_) => Ok("".to_string()), + Value::None => Ok("".to_string()), + }, +}; + +pub const FLOAT: DecoratorDefinition = DecoratorDefinition { + name: &["float"], + description: "Format a number as floating point", + argument: ExpectedTypes::IntOrFloat, + handler: |decorator, token, input| { + if decorator.arg().strict_matches(input) { + Ok(Value::Float(input.as_float().unwrap()).as_string()) + } else { + pluralized_decorator(decorator, token, input) + } + }, +}; + +pub const INT: DecoratorDefinition = DecoratorDefinition { + name: &["int", "integer"], + description: "Format a number as an integer", + argument: ExpectedTypes::IntOrFloat, + handler: |decorator, token, input| { + if decorator.arg().strict_matches(input) { + Ok(Value::Integer(input.as_int().unwrap()).as_string()) + } else { + pluralized_decorator(decorator, token, input) + } + }, +}; + +pub const BOOL: DecoratorDefinition = DecoratorDefinition { + name: &["bool", "boolean"], + description: "Format a number as a boolean", + argument: ExpectedTypes::Any, + handler: |_, _, input| Ok(Value::Boolean(input.as_bool()).as_string()), +}; + +pub const ARRAY: DecoratorDefinition = DecoratorDefinition { + name: &["array"], + description: "Format a number as an array", + argument: ExpectedTypes::Any, + handler: |_, _, input| Ok(Value::Array(input.as_array()).as_string()), +}; + +pub const OBJECT: DecoratorDefinition = DecoratorDefinition { + name: &["object"], + description: "Format a number as an object", + argument: ExpectedTypes::Any, + handler: |_, _, input| Ok(Value::Object(input.as_object()).as_string()), +}; + +#[cfg(test)] +mod test_builtin_functions { + use crate::Token; + + use super::*; + + #[test] + fn test_float() { + assert_eq!( + "8.0", + FLOAT.call(&Token::dummy(""), &Value::Integer(8)).unwrap() + ); + assert_eq!( + "81.0", + FLOAT.call(&Token::dummy(""), &Value::Float(81.0)).unwrap() + ); + assert_eq!( + "0.0", + FLOAT + .call(&Token::dummy(""), &Value::Float(0.0000000001)) + .unwrap() + ); + assert_eq!( + "0.081", + FLOAT.call(&Token::dummy(""), &Value::Float(0.081)).unwrap() + ); + } + + #[test] + fn test_int() { + assert_eq!( + "-8", + INT.call(&Token::dummy(""), &Value::Integer(-8)).unwrap() + ); + assert_eq!( + "81", + INT.call(&Token::dummy(""), &Value::Float(81.0)).unwrap() + ); + assert_eq!( + "0", + INT.call(&Token::dummy(""), &Value::Float(0.081)).unwrap() + ); + } + + #[test] + fn test_bool() { + assert_eq!( + "false", + BOOL.call(&Token::dummy(""), &Value::Integer(0)).unwrap() + ); + assert_eq!( + "true", + BOOL.call(&Token::dummy(""), &Value::Integer(81)).unwrap() + ); + assert_eq!( + "true", + BOOL.call(&Token::dummy(""), &Value::Float(0.081)).unwrap() + ); + } +} diff --git a/src/decorators/string.rs b/src/decorators/string.rs new file mode 100644 index 0000000..a82a644 --- /dev/null +++ b/src/decorators/string.rs @@ -0,0 +1,114 @@ +use crate::{DecoratorDefinition, Error, ExpectedTypes, Value}; + +use super::pluralized_decorator; + +pub const PERCENTAGE: DecoratorDefinition = DecoratorDefinition { + name: &["percentage", "percent"], + description: "Format a floating point number as a percentage", + argument: ExpectedTypes::IntOrFloat, + handler: |decorator, token, input| { + if decorator.arg().strict_matches(input) { + Ok(format!("{}%", input.as_float().unwrap() * 100.0)) + } else { + pluralized_decorator(decorator, token, input) + } + }, +}; + +pub const ORDINAL: DecoratorDefinition = DecoratorDefinition { + name: &["ordinal"], + description: "Format an integer as an ordinal (1st, 38th, etc)", + argument: ExpectedTypes::IntOrFloat, + handler: |decorator, token, input| { + if decorator.arg().strict_matches(input) { + let v = Value::Integer(input.as_int().unwrap()).as_string(); + let suffix = if v.ends_with('1') { + "st" + } else if v.ends_with('2') { + "nd" + } else if v.ends_with('3') { + "rd" + } else { + "th" + }; + Ok(format!("{}{}", v, suffix)) + } else { + pluralized_decorator(decorator, token, input) + } + }, +}; + +pub const ROMAN: DecoratorDefinition = DecoratorDefinition { + name: &["roman"], + description: "Format an integer as a roman numeral", + argument: ExpectedTypes::IntOrFloat, + handler: |decorator, token, input| { + if decorator.arg().strict_matches(input) { + let mut value = input.as_int().unwrap(); + if value > 3999 { + return Err(Error::Overflow(token.clone()).into()); + } + + let roman_numerals = vec![ + (1000, "M"), + (900, "CM"), + (500, "D"), + (400, "CD"), + (100, "C"), + (90, "XC"), + (50, "L"), + (40, "XL"), + (10, "X"), + (9, "IX"), + (5, "V"), + (4, "IV"), + (1, "I"), + ]; + let mut roman_numeral = String::new(); + for (n, r) in roman_numerals { + while value >= n { + roman_numeral.push_str(r); + value -= n; + } + } + Ok(roman_numeral) + } else { + pluralized_decorator(decorator, token, input) + } + }, +}; + +#[cfg(test)] +mod test_builtin_functions { + use crate::Token; + + use super::*; + + #[test] + fn test_ordinal() { + assert_eq!( + "32nd", + ORDINAL + .call(&Token::dummy(""), &Value::Integer(32)) + .unwrap() + ); + } + + #[test] + fn test_percentage() { + assert_eq!( + "32.5%", + PERCENTAGE + .call(&Token::dummy(""), &Value::Float(0.325)) + .unwrap() + ); + } + + #[test] + fn test_roman() { + assert_eq!( + "XXVI", + ROMAN.call(&Token::dummy(""), &Value::Integer(26)).unwrap() + ); + } +} diff --git a/src/errors.rs b/src/errors.rs index 2b50623..7647d73 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,198 +1,283 @@ -use crate::{Value, Token}; - -use std::error::Error; -use std::fmt::{self, Display, Formatter}; - -#[macro_use] -mod error_macro { - macro_rules! define_parser_error { - ($(($name:ident, $struct:ident, $docs:expr)),+) => { - - - #[derive(Debug)] - /// Error occuring during parsing - pub enum ParserError { - $( - #[doc = $docs] - $name($struct), - )+ - } - - impl Display for ParserError { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - $( - Self::$name(inner) => write!(f, "{}", inner), - )+ - } - } - } - - impl Error for ParserError {} - - $( - impl Error for $struct {} - impl From<$struct> for ParserError { - fn from(val: $struct) -> Self { - ParserError::$name(val) - } - } - )+ - }; - } -} +use crate::{ExpectedTypes, Token, Value}; +use thiserror::Error; -const MAX_DISPLAY_SRC: usize = 8; -/// Location and cause of the error -#[derive(Debug, Clone)] -pub struct ParserErrorSource { - src: Token -} -impl ParserErrorSource { - /// Create a new source for an error - pub fn new(src: &Token) -> Self { - Self { - src: src.clone() - } - } - - /// Return a reference to the cause of the error - pub fn token(&self) -> &Token { - &self.src - } - - - /// Return the location of the error - pub fn index(&self) -> usize { - self.src.index() - } -} -impl Display for ParserErrorSource { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - let mut token_text = self.src.text().split('\n').next().unwrap_or("").to_string(); - if token_text.len() > MAX_DISPLAY_SRC { - token_text = token_text[0..MAX_DISPLAY_SRC].to_string(); - } - write!(f, "at {} (col {})", token_text.trim(), self.src.index()) - } -} +const BUG_REPORT_URL : &str = "https://github.com/rscarson/lavendeux-parser/issues/new?assignees=&labels=&template=bug_report.md&title="; -/// Represents a type of value that was expected -#[derive(Debug, Clone)] -pub enum ExpectedTypes { - /// Integer value - Int, +/// Represents the errors that can occur during parsing +#[derive(Error, Debug)] +#[rustfmt::skip] +pub enum Error { + /// An error caused by a problem with the parser itself + #[error( + "internal parser issue at {0}.\nPlease report this problem at {}", + BUG_REPORT_URL + )] + Internal(Token), - /// Floating point value - Float, + /////////////////////////////////////////////////////////////////////////// + // Value Errors + // Mostly deals with variables, and value objects + /////////////////////////////////////////////////////////////////////////// - /// Any numeric value - IntOrFloat, - - /// String value - String, - - /// Boolean value - Boolean, - - /// Array value - Array, - - /// Object value - Object, - - /// Any type of value - Any -} + /// An error caused by attempting to overwrite a constant + #[error("could not overwrite constant value {name} at {token}")] + ConstantValue { + /// Name of the constant + name: String, + + /// token at which the error occured + token: Token + }, -impl ExpectedTypes { - /// Returns true if the given value matches expectations - pub fn matches(&self, value: &Value) -> bool { - if value.is_compound() { - true - } else { - self.strict_matches(value) - } - } - - /// Returns true if the given value matches expectations and count - pub fn strict_matches(&self, value: &Value) -> bool { - match self { - ExpectedTypes::Int => value.is_int(), - ExpectedTypes::Float => value.is_float(), - ExpectedTypes::IntOrFloat => value.is_numeric(), - - // Can be converted from any type - _ => true - } - } -} + /// An error caused by a calculation that resulted in an overflow + #[error("arithmetic overflow at {0}")] + Overflow(Token), -impl fmt::Display for ExpectedTypes { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - ExpectedTypes::Int => write!(f, "integer"), - ExpectedTypes::Float => write!(f, "float"), - ExpectedTypes::IntOrFloat => write!(f, "integer or float"), - ExpectedTypes::String => write!(f, "string"), - ExpectedTypes::Boolean => write!(f, "boolean"), - ExpectedTypes::Array => write!(f, "array"), - ExpectedTypes::Object => write!(f, "object"), - ExpectedTypes::Any => write!(f, "any"), - } - } -} + /// An error caused by a calculation that resulted in an underflow + #[error("arithmetic underflow at {0}")] + Underflow(Token), -mod external; pub use external::*; -mod functions; pub use functions::*; -mod arrays; pub use arrays::*; -mod values; pub use values::*; -mod syntax; pub use syntax::*; - -mod internal; pub use internal::*; - -define_parser_error!( - // Array errors - (ArrayEmpty, ArrayEmptyError, "An error caused by attempting to use an empty array"), - (ArrayIndex, ArrayIndexError, "An error caused by attempting to use an out of bounds index on an array"), - (ArrayLength, ArrayLengthError, "An error caused by attempting to use arrays of different lengths"), - - // Function errors - (AmbiguousFunction, AmbiguousFunctionError, "An error caused by attempting to use a function with ambiguous arguments"), - (DecoratorName, DecoratorNameError, "An error caused by calling a decorator that does not exist"), - (DecoratorArgType, DecoratorArgTypeError, "An error caused by calling a decorator using an argument of the wrong type"), - - (FunctionArgType, FunctionArgTypeError, "An error caused by calling a function using an argument of the wrong type"), - (FunctionNArgs, FunctionNArgsError, "An error caused by calling a function using the wrong number of arguments"), - (FunctionName, FunctionNameError, "An error caused by calling a function that does not exist"), - (FunctionOverflow, FunctionOverflowError, "An error caused by a function argument overflowing a pre-determined limit"), - (Stack, StackError, "An error caused by a recursive function going too deep"), - - // Value errors - (ConstantValue, ConstantValueError, "An error caused by attempting to overwrite a constant"), - (ObjectKey, ObjectKeyError, "An error caused by attempting to use an invalid object key"), - (Overflow, OverflowError, "An error caused by a calculation that resulted in an overflow"), - (ParseValue, ParseValueError, "An error caused by attempting to parse an value"), - (Parsing, ParsingError, "An error caused by attempting to parse an invalid string into a given format"), - (Range, RangeError, "An error caused by attempting use an out of range value"), - (Underflow, UnderflowError, "An error caused by a calculation that resulted in an underflow"), - (ValueType, ValueTypeError, "An error caused by attempting to use a value of the wrong type in a calculation"), - (VariableName, VariableNameError, "An error caused by attempting to use an unassigned variable"), - - // Downstream errors - (IO, IOError, "An error caused by filesystem issues"), - (Network, NetworkError, "An error caused by network issues"), - (Pest, PestError, "An error caused by a problem in parsing the syntax of an expression"), - (Script, ScriptError, "An error caused by an unknown exception in a javascript extension"), - - // Syntax errors - (UnexpectedDecorator, UnexpectedDecoratorError, "An error caused by using a decorator in the wrong place"), - (UnexpectedPostfix, UnexpectedPostfixError, "An error caused by using a postfix operator without an operand"), - (UnterminatedArray, UnterminatedArrayError, "An error caused by a missing square bracket"), - (UnterminatedLinebreak, UnterminatedLinebreakError, "An error caused by ending a script on a backslash"), - (UnterminatedLiteral, UnterminatedLiteralError, "An error caused by a missing quote"), - (UnterminatedObject, UnterminatedObjectError, "An error caused by a missing curly brace"), - (UnterminatedParen,UnterminatedParenError, "An error caused by a missing parentheses"), + /// An error caused by attempting to parse an value + #[error("{input} could not be parsed as {expected_type} at {token}")] + ValueParsing { + /// Value causing the error + input: String, + + /// Type that was requested + expected_type: ExpectedTypes, + + /// token at which the error occured + token: Token, + }, + + /// An error caused by attempting to parse an invalid string into a given format + #[error("string could not be parsed as {expected_format} at (Token)")] + StringFormat { + /// Expected format of the string + expected_format: String, + + /// token at which the error occured + token: Token, + }, + + /// An error caused by attempting use an out of range value + #[error("value {value} was out of range at {token}")] + Range { + /// Value causing the error + value: Value, + + /// token at which the error occured + token: Token + }, + + /// An error caused by attempting to use a value of the wrong type in a calculation + #[error("wrong type of value {value} expected {expected_type} at {token}")] + ValueType { + /// Value causing the error + value: Value, + + /// Type that was requested + expected_type: ExpectedTypes, + + /// token at which the error occured + token: Token, + }, + + /// An error caused by attempting to use an unassigned variable + #[error("undefined variable {name} at {token}")] + VariableName { + /// Name of the variable + name: String, + + /// token at which the error occured + token: Token + }, + + /////////////////////////////////////////////////////////////////////////// + // Syntax Errors + // Deals with issues during Pest tree parsing + /////////////////////////////////////////////////////////////////////////// + + /// An error caused by using a decorator in the wrong place + #[error("@{0} must be at the end of a statement")] + UnexpectedDecorator(Token), + + /// An error caused by using a postfix operator without an operand + #[error("missing operand before postfix operator at {0}")] + UnexpectedPostfix(Token), + + /// An error caused by a missing bracket + #[error("expected ']' at {0}")] + UnterminatedArray(Token), + + /// An error caused by a missing brace + #[error("expected '}}' at {0}")] + UnterminatedObject(Token), + + /// An error caused by ending a script on a backslash + #[error("missing linebreak after '\\' at {0}")] + UnterminatedLinebreak(Token), + + /// An error caused by a missing quote + #[error("expected ' or \" at {0}")] + UnterminatedLiteral(Token), + + /// An error caused by a missing parentheses + #[error("expected ')' at {0}")] + UnterminatedParen(Token), + + /////////////////////////////////////////////////////////////////////////// + // Function Errors + // Deals with issues during builtin, user, or extension function calls + /////////////////////////////////////////////////////////////////////////// + + /// An error caused by a recursive function going too deep + #[error("stack overflow at {0}")] + StackOverflow(Token), + + /// An error caused by attempting to use a function with ambiguous arguments + #[error("function parameters for {signature} are ambiguous at {token}")] + AmbiguousFunctionDefinition { + /// Signature of the function called + signature: String, + + /// token at which the error occured + token: Token + }, + + /// An error caused by calling a function with an argument of the wrong type + #[error("argument {arg} of {signature}, expected {expected_type} at {token}")] + FunctionArgumentType { + /// Argument number causing the issue (1-based) + arg: usize, + + /// Type that was requested + expected_type: ExpectedTypes, + + /// Signature of the function called + signature: String, + + /// token at which the error occured + token: Token, + }, + + /// An error caused by calling a function that does not exist + #[error("no such function {name} at {token}")] + FunctionName { + /// Name of the function + name: String, + + /// token at which the error occured + token: Token + }, + + /// An error caused by calling a function using the wrong number of arguments + #[error( + "{signature} expected {} arguments at {token}", + if min == max {format!("{}", min)} else {format!("{}-{}", min, max)} + )] + FunctionArguments { + /// Smallest number of arguments accepted by the function + min: usize, + + /// Largest number of arguments accepted by the function + max: usize, + + + /// Signature of the function called + signature: String, + + /// token at which the error occured + token: Token + }, + + /// An error caused by a function argument overflowing a pre-determined limit + #[error("argument {arg} of {signature} at {token}")] + FunctionArgumentOverflow { + /// Argument number causing the issue (1-based) + arg: usize, + + /// Signature of the function called + signature: String, + + /// token at which the error occured + token: Token + }, + + /// An error caused by calling a decorator with an argument of the wrong type + #[error("@{name} expected type {expected_type} at {token}")] + DecoratorArgumentType { + /// Type that was requested + expected_type: ExpectedTypes, + + /// Name of the decorator + name: String, + + /// token at which the error occured + token: Token, + }, + + /// An error caused by calling a decorator that does not exist + #[error("no such decorator {name} at {token}")] + DecoratorName { + /// Name of the decorator + name: String, + + /// token at which the error occured + token: Token + }, - (Internal, InternalError, "An error caused by a problem with the parser itself") -); \ No newline at end of file + /// An error caused by attempting to use an API without registering it + #[error("API {name} was not found. Add it with api_register(\"{name}\", base_url, [optional api key]) at {token}")] + UnknownApi { + /// Name of the API + name: String, + + /// token at which the error occured + token: Token + }, + + /////////////////////////////////////////////////////////////////////////// + // Array Errors + // Deals with issues indexing of arrays and objects + /////////////////////////////////////////////////////////////////////////// + + /// An error caused by attempting to use an invalid object or array key + #[error("undefined index {key} at {token}")] + Index { + /// Index that caused the error + key: Value, + + /// token at which the error occured + token: Token + }, + + /// An error caused by attempting to index on an empty array + #[error("could not index empty array at {0}")] + ArrayEmpty(Token), + + /// An error caused by attempting to operate on a pair of arrays of incompatible lengths + #[error("array lengths were incompatible at {0}")] + ArrayLengths(Token), + + /////////////////////////////////////////////////////////////////////////// + // External Errors + // Deals with issues inside dependencies + /////////////////////////////////////////////////////////////////////////// + + /// Error dealing with filesystem issues + #[error("{0} at {1}")] + Io(std::io::Error, Token), + + /// Error dealing with network issues from the reqwest crate + #[error("{0} at {1}")] + Network(reqwest::Error, Token), + + /// Error dealing with pest parsing problems + #[error("{0} at {1}")] + Pest(pest::error::Error, Token), + + /// Error dealing with JS execution issues + #[error("{0} at {1}")] + Javascript(js_playground::Error, Token), +} diff --git a/src/errors/arrays/array_empty.rs b/src/errors/arrays/array_empty.rs index 72d7c3e..f3caae1 100644 --- a/src/errors/arrays/array_empty.rs +++ b/src/errors/arrays/array_empty.rs @@ -1,26 +1,26 @@ +use crate::Error; use crate::Token; -use crate::errors::*; use std::fmt::{self, Display}; /// An error caused by attempting to use an empty array #[derive(Debug, Clone)] pub struct ArrayEmptyError { - src: ParserErrorSource + src: ErrorSource, } impl ArrayEmptyError { /// Create a new instance of this error - /// + /// /// # Arguments /// * `src` - Token causing the error pub fn new(src: &Token) -> Self { Self { - src: ParserErrorSource::new(src) + src: ErrorSource::new(src), } } /// Describes the location and text of the bad token - pub fn source(&self) -> &ParserErrorSource { + pub fn source(&self) -> &ErrorSource { &self.src } } @@ -30,4 +30,4 @@ impl Display for ArrayEmptyError { write!(f, "array is empty {}", self.src)?; fmt::Result::Ok(()) } -} \ No newline at end of file +} diff --git a/src/errors/arrays/array_index.rs b/src/errors/arrays/array_index.rs index 546d504..8ec86dd 100644 --- a/src/errors/arrays/array_index.rs +++ b/src/errors/arrays/array_index.rs @@ -1,5 +1,5 @@ +use crate::Error; use crate::Token; -use crate::errors::*; use std::fmt::{self, Display}; @@ -7,18 +7,18 @@ use std::fmt::{self, Display}; #[derive(Debug, Clone)] pub struct ArrayIndexError { cause: usize, - src: ParserErrorSource + src: ErrorSource, } impl ArrayIndexError { /// Create a new instance of this error - /// + /// /// # Arguments /// * `src` - Token causing the error /// * `cause` - Reason for the error pub fn new(src: &Token, cause: usize) -> Self { Self { cause, - src: ParserErrorSource::new(src) + src: ErrorSource::new(src), } } @@ -28,7 +28,7 @@ impl ArrayIndexError { } /// Describes the location and text of the bad token - pub fn source(&self) -> &ParserErrorSource { + pub fn source(&self) -> &ErrorSource { &self.src } } @@ -38,4 +38,4 @@ impl Display for ArrayIndexError { write!(f, "array index {} out of bounds {}", self.cause, self.src)?; fmt::Result::Ok(()) } -} \ No newline at end of file +} diff --git a/src/errors/arrays/array_length.rs b/src/errors/arrays/array_length.rs index 3906337..e24e6bf 100644 --- a/src/errors/arrays/array_length.rs +++ b/src/errors/arrays/array_length.rs @@ -1,26 +1,26 @@ +use crate::Error; use crate::Token; -use crate::errors::*; use std::fmt::{self, Display}; /// An error caused by attempting to use arrays of different lengths #[derive(Debug, Clone)] pub struct ArrayLengthError { - src: ParserErrorSource + src: ErrorSource, } impl ArrayLengthError { /// Create a new instance of this error - /// + /// /// # Arguments /// * `src` - Token causing the error pub fn new(src: &Token) -> Self { Self { - src: ParserErrorSource::new(src) + src: ErrorSource::new(src), } } /// Describes the location and text of the bad token - pub fn source(&self) -> &ParserErrorSource { + pub fn source(&self) -> &ErrorSource { &self.src } } @@ -30,4 +30,4 @@ impl Display for ArrayLengthError { write!(f, "array lengths incompatible {}", self.src)?; fmt::Result::Ok(()) } -} \ No newline at end of file +} diff --git a/src/errors/external/io.rs b/src/errors/external/io.rs index d3a2ac3..1ea0fab 100644 --- a/src/errors/external/io.rs +++ b/src/errors/external/io.rs @@ -1,5 +1,5 @@ +use crate::Error; use crate::Token; -use crate::errors::*; use std::fmt::{self, Display}; @@ -7,18 +7,18 @@ use std::fmt::{self, Display}; #[derive(Debug, Clone)] pub struct IOError { cause: String, - src: ParserErrorSource + src: ErrorSource, } impl IOError { /// Create a new instance of this error - /// + /// /// # Arguments /// * `src` - Token causing the error /// * `cause` - Reason for the error pub fn new(src: &Token, cause: &str) -> Self { Self { cause: cause.to_string(), - src: ParserErrorSource::new(src) + src: ErrorSource::new(src), } } @@ -28,12 +28,12 @@ impl IOError { } /// Describes the location and text of the bad token - pub fn source(&self) -> &ParserErrorSource { + pub fn source(&self) -> &ErrorSource { &self.src } /// Create a new instance of this error from an existing error - /// + /// /// # Arguments /// * `src` - Token causing the error /// * `error`- source error @@ -47,4 +47,4 @@ impl Display for IOError { write!(f, "IO error: {} {}", self.cause, self.src)?; fmt::Result::Ok(()) } -} \ No newline at end of file +} diff --git a/src/errors/external/network.rs b/src/errors/external/network.rs index 4d3c9a6..1d563e3 100644 --- a/src/errors/external/network.rs +++ b/src/errors/external/network.rs @@ -1,5 +1,5 @@ +use crate::Error; use crate::Token; -use crate::errors::*; use std::fmt::{self, Display}; @@ -7,18 +7,18 @@ use std::fmt::{self, Display}; #[derive(Debug, Clone)] pub struct NetworkError { cause: String, - src: ParserErrorSource + src: ErrorSource, } impl NetworkError { /// Create a new instance of this error - /// + /// /// # Arguments /// * `src` - Token causing the error /// * `cause` - Reason for the error pub fn new(src: &Token, cause: &str) -> Self { Self { cause: cause.to_string(), - src: ParserErrorSource::new(src) + src: ErrorSource::new(src), } } @@ -28,12 +28,12 @@ impl NetworkError { } /// Describes the location and text of the bad token - pub fn source(&self) -> &ParserErrorSource { + pub fn source(&self) -> &ErrorSource { &self.src } /// Create a new instance of this error from an existing error - /// + /// /// # Arguments /// * `src` - Token causing the error /// * `error`- source error @@ -47,4 +47,4 @@ impl Display for NetworkError { write!(f, "network error: {} {}", self.cause, self.src)?; fmt::Result::Ok(()) } -} \ No newline at end of file +} diff --git a/src/errors/external/pest.rs b/src/errors/external/pest.rs index 1924fb2..223c011 100644 --- a/src/errors/external/pest.rs +++ b/src/errors/external/pest.rs @@ -1,26 +1,26 @@ +use crate::Error; use crate::Token; -use crate::errors::*; use std::fmt::{self, Display}; /// An error caused by a problem in parsing the syntax of an expression #[derive(Debug, Clone)] pub struct PestError { - src: ParserErrorSource + src: ErrorSource, } impl PestError { /// Create a new instance of this error - /// + /// /// # Arguments /// * `src` - Token causing the error pub fn new(src: &Token) -> Self { Self { - src: ParserErrorSource::new(src) + src: ErrorSource::new(src), } } /// Describes the location and text of the bad token - pub fn source(&self) -> &ParserErrorSource { + pub fn source(&self) -> &ErrorSource { &self.src } } @@ -30,4 +30,4 @@ impl Display for PestError { write!(f, "invalid syntax {}", self.src)?; fmt::Result::Ok(()) } -} \ No newline at end of file +} diff --git a/src/errors/external/script.rs b/src/errors/external/script.rs index e7c9fb2..5760829 100644 --- a/src/errors/external/script.rs +++ b/src/errors/external/script.rs @@ -1,21 +1,18 @@ +use crate::Error; use crate::Token; -use crate::errors::*; use std::fmt::{self, Display}; -#[cfg(feature = "extensions")] -use js_sandbox::JsError; - /// An error caused by an unknown exception in a javascript extension #[derive(Debug, Clone)] pub struct ScriptError { filename: String, cause: String, - src: ParserErrorSource + src: ErrorSource, } impl ScriptError { /// Create a new instance of this error - /// + /// /// # Arguments /// * `src` - Token causing the error /// * `filename` - Reason for the error @@ -24,7 +21,7 @@ impl ScriptError { Self { filename: filename.to_string(), cause: cause.to_string(), - src: ParserErrorSource::new(src) + src: ErrorSource::new(src), } } @@ -39,30 +36,29 @@ impl ScriptError { } /// Describes the location and text of the bad token - pub fn source(&self) -> &ParserErrorSource { + pub fn source(&self) -> &ErrorSource { &self.src } /// Create a new instance of this error from an existing error - /// + /// /// # Arguments /// * `src` - Token causing the error /// * `name` - File or function name /// * `error`- source error #[cfg(feature = "extensions")] - pub fn from_jserror(src: &Token, name: &str, error: JsError) -> Self { - if matches!(error, JsError::Json(_)) { + pub fn from_jserror(src: &Token, name: &str, error: js_playground::Error) -> Self { + if matches!(error, js_playground::Error::JsonDecode(_)) { Self::new(src, name, &format!("{}: {}", name, &error.to_string())) } else { - Self::new(src, name, &error.to_string().replace("sandboxed.js", name)) + Self::new(src, name, &error.to_string()) } } } impl Display for ScriptError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let cause = self.cause.replace("sandboxed.js", &self.filename); - write!(f, "{} {}", cause, self.src)?; + write!(f, "{} {}", self.cause, self.src)?; fmt::Result::Ok(()) } -} \ No newline at end of file +} diff --git a/src/errors/functions/ambiguous_function.rs b/src/errors/functions/ambiguous_function.rs index c64f7b0..83e5769 100644 --- a/src/errors/functions/ambiguous_function.rs +++ b/src/errors/functions/ambiguous_function.rs @@ -1,5 +1,5 @@ +use crate::Error; use crate::Token; -use crate::errors::*; use std::fmt::{self, Display}; @@ -8,11 +8,11 @@ use std::fmt::{self, Display}; pub struct AmbiguousFunctionError { function: String, cause: String, - src: ParserErrorSource + src: ErrorSource, } impl AmbiguousFunctionError { /// Create a new instance of this error - /// + /// /// # Arguments /// * `src` - Token causing the error /// * `function` - name of the ambiguous function @@ -21,7 +21,7 @@ impl AmbiguousFunctionError { Self { function: function.to_string(), cause: cause.to_string(), - src: ParserErrorSource::new(src) + src: ErrorSource::new(src), } } @@ -36,14 +36,18 @@ impl AmbiguousFunctionError { } /// Describes the location and text of the bad token - pub fn source(&self) -> &ParserErrorSource { + pub fn source(&self) -> &ErrorSource { &self.src } } impl Display for AmbiguousFunctionError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "ambiguous arguments in {}(): {} {}", self.function, self.cause, self.src)?; + write!( + f, + "ambiguous arguments in {}(): {} {}", + self.function, self.cause, self.src + )?; fmt::Result::Ok(()) } -} \ No newline at end of file +} diff --git a/src/errors/functions/decorator_arg_type.rs b/src/errors/functions/decorator_arg_type.rs index c3cc4a3..6a480dc 100644 --- a/src/errors/functions/decorator_arg_type.rs +++ b/src/errors/functions/decorator_arg_type.rs @@ -1,31 +1,32 @@ +use crate::Error; +use crate::ExpectedTypes; use crate::Token; -use crate::errors::*; use std::fmt::{self, Display}; /// An error caused by calling a decorator using an argument of the wrong type #[derive(Debug, Clone)] pub struct DecoratorArgTypeError { - expected: ExpectedTypes, + expected: ExpectedTypes, signature: String, - src: ParserErrorSource + src: ErrorSource, } impl DecoratorArgTypeError { /// Create a new instance of this error - /// + /// /// # Arguments /// * `src` - Token causing the error /// * `signature` - decorator signature /// * `expected` - Expected type of value for the argument pub fn new(src: &Token, signature: &str, expected: ExpectedTypes) -> Self { Self { - expected, + expected, signature: signature.to_string(), - src: ParserErrorSource::new(src) + src: ErrorSource::new(src), } } - /// Decorator call signature + /// Decorator call signature pub fn signature(&self) -> &str { &self.signature } @@ -36,14 +37,18 @@ impl DecoratorArgTypeError { } /// Describes the location and text of the bad token - pub fn source(&self) -> &ParserErrorSource { + pub fn source(&self) -> &ErrorSource { &self.src } } impl Display for DecoratorArgTypeError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "invalid type for decorator {} (expected {}) {}", self.signature, self.expected, self.src)?; + write!( + f, + "invalid type for decorator {} (expected {}) {}", + self.signature, self.expected, self.src + )?; fmt::Result::Ok(()) } -} \ No newline at end of file +} diff --git a/src/errors/functions/decorator_name.rs b/src/errors/functions/decorator_name.rs index d3ba5d4..d24c77d 100644 --- a/src/errors/functions/decorator_name.rs +++ b/src/errors/functions/decorator_name.rs @@ -1,5 +1,5 @@ +use crate::Error; use crate::Token; -use crate::errors::*; use std::fmt::{self, Display}; @@ -7,18 +7,18 @@ use std::fmt::{self, Display}; #[derive(Debug, Clone)] pub struct DecoratorNameError { cause: String, - src: ParserErrorSource + src: ErrorSource, } impl DecoratorNameError { /// Create a new instance of this error - /// + /// /// # Arguments /// * `src` - Token causing the error /// * `cause` - Reason for the error pub fn new(src: &Token, cause: &str) -> Self { Self { cause: cause.to_string(), - src: ParserErrorSource::new(src) + src: ErrorSource::new(src), } } @@ -28,7 +28,7 @@ impl DecoratorNameError { } /// Describes the location and text of the bad token - pub fn source(&self) -> &ParserErrorSource { + pub fn source(&self) -> &ErrorSource { &self.src } } @@ -38,4 +38,4 @@ impl Display for DecoratorNameError { write!(f, "unrecognized decorator {} {}", self.cause, self.src)?; fmt::Result::Ok(()) } -} \ No newline at end of file +} diff --git a/src/errors/functions/function_arg_type.rs b/src/errors/functions/function_arg_type.rs index 4ab779b..53a062a 100644 --- a/src/errors/functions/function_arg_type.rs +++ b/src/errors/functions/function_arg_type.rs @@ -1,19 +1,20 @@ +use crate::Error; +use crate::ExpectedTypes; use crate::Token; -use crate::errors::*; use std::fmt::{self, Display}; /// An error caused by calling a function using an argument of the wrong type #[derive(Debug, Clone)] pub struct FunctionArgTypeError { - arg: usize, - expected: ExpectedTypes, + arg: usize, + expected: ExpectedTypes, signature: String, - src: ParserErrorSource + src: ErrorSource, } impl FunctionArgTypeError { /// Create a new instance of this error - /// + /// /// # Arguments /// * `src` - Token causing the error /// * `signature` - Function signature @@ -21,14 +22,14 @@ impl FunctionArgTypeError { /// * `expected` - Expected type of value for the argument pub fn new(src: &Token, signature: &str, arg: usize, expected: ExpectedTypes) -> Self { Self { - arg, - expected, + arg, + expected, signature: signature.to_string(), - src: ParserErrorSource::new(src) + src: ErrorSource::new(src), } } - /// Function call signature + /// Function call signature pub fn signature(&self) -> &str { &self.signature } @@ -44,14 +45,18 @@ impl FunctionArgTypeError { } /// Describes the location and text of the bad token - pub fn source(&self) -> &ParserErrorSource { + pub fn source(&self) -> &ErrorSource { &self.src } } impl Display for FunctionArgTypeError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}: invalid type for argument {} (expected {}) {}", self.signature, self.arg, self.expected, self.src)?; + write!( + f, + "{}: invalid type for argument {} (expected {}) {}", + self.signature, self.arg, self.expected, self.src + )?; fmt::Result::Ok(()) } -} \ No newline at end of file +} diff --git a/src/errors/functions/function_n_args.rs b/src/errors/functions/function_n_args.rs index 9d23ef2..04c7ae7 100644 --- a/src/errors/functions/function_n_args.rs +++ b/src/errors/functions/function_n_args.rs @@ -1,18 +1,19 @@ +use crate::Error; use crate::Token; -use crate::errors::*; use std::fmt::{self, Display}; /// An error caused by calling a function using the wrong number of arguments #[derive(Debug, Clone)] pub struct FunctionNArgsError { - min: usize, max: usize, + min: usize, + max: usize, signature: String, - src: ParserErrorSource + src: ErrorSource, } impl FunctionNArgsError { /// Create a new instance of this error - /// + /// /// # Arguments /// * `src` - Token causing the error /// * `signature` - Function signature @@ -20,37 +21,46 @@ impl FunctionNArgsError { /// * `max` - Maximum acceptable number of arguments pub fn new(src: &Token, signature: &str, min: usize, max: usize) -> Self { Self { - min, max, + min, + max, signature: signature.to_string(), - src: ParserErrorSource::new(src) + src: ErrorSource::new(src), } } - /// Function call signature + /// Function call signature pub fn signature(&self) -> &str { &self.signature } - /// Smallest allowed number of arguments + /// Smallest allowed number of arguments pub fn min(&self) -> usize { self.min } - /// Largest allowed number of arguments + /// Largest allowed number of arguments pub fn max(&self) -> usize { self.max } /// Describes the location and text of the bad token - pub fn source(&self) -> &ParserErrorSource { + pub fn source(&self) -> &ErrorSource { &self.src } } impl Display for FunctionNArgsError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let n_args = if self.min == self.max {format!("{}", self.min)} else {format!("{}-{}", self.min, self.max)}; - write!(f, "{}: expected {} args {}", self.signature, n_args, self.src)?; + let n_args = if self.min == self.max { + format!("{}", self.min) + } else { + format!("{}-{}", self.min, self.max) + }; + write!( + f, + "{}: expected {} args {}", + self.signature, n_args, self.src + )?; fmt::Result::Ok(()) } -} \ No newline at end of file +} diff --git a/src/errors/functions/function_name.rs b/src/errors/functions/function_name.rs index 953d9e8..64f9250 100644 --- a/src/errors/functions/function_name.rs +++ b/src/errors/functions/function_name.rs @@ -1,5 +1,5 @@ +use crate::Error; use crate::Token; -use crate::errors::*; use std::fmt::{self, Display}; @@ -7,18 +7,18 @@ use std::fmt::{self, Display}; #[derive(Debug, Clone)] pub struct FunctionNameError { cause: String, - src: ParserErrorSource + src: ErrorSource, } impl FunctionNameError { /// Create a new instance of this error - /// + /// /// # Arguments /// * `src` - Token causing the error /// * `cause` - Reason for the error pub fn new(src: &Token, cause: &str) -> Self { Self { cause: cause.to_string(), - src: ParserErrorSource::new(src) + src: ErrorSource::new(src), } } @@ -28,7 +28,7 @@ impl FunctionNameError { } /// Describes the location and text of the bad token - pub fn source(&self) -> &ParserErrorSource { + pub fn source(&self) -> &ErrorSource { &self.src } } @@ -38,4 +38,4 @@ impl Display for FunctionNameError { write!(f, "unrecognized function {}() {}", self.cause, self.src)?; fmt::Result::Ok(()) } -} \ No newline at end of file +} diff --git a/src/errors/functions/function_overflow.rs b/src/errors/functions/function_overflow.rs index 9f8d2d6..3e8d87c 100644 --- a/src/errors/functions/function_overflow.rs +++ b/src/errors/functions/function_overflow.rs @@ -1,18 +1,18 @@ +use crate::Error; use crate::Token; -use crate::errors::*; use std::fmt::{self, Display}; /// An error caused by a function argument overflowing a pre-determined limit #[derive(Debug, Clone)] pub struct FunctionOverflowError { - arg: usize, + arg: usize, signature: String, - src: ParserErrorSource + src: ErrorSource, } impl FunctionOverflowError { /// Create a new instance of this error - /// + /// /// # Arguments /// * `src` - Token causing the error /// * `signature` - Function signature @@ -21,11 +21,11 @@ impl FunctionOverflowError { Self { arg, signature: signature.to_string(), - src: ParserErrorSource::new(src) + src: ErrorSource::new(src), } } - /// Function call signature + /// Function call signature pub fn signature(&self) -> &str { &self.signature } @@ -36,14 +36,18 @@ impl FunctionOverflowError { } /// Describes the location and text of the bad token - pub fn source(&self) -> &ParserErrorSource { + pub fn source(&self) -> &ErrorSource { &self.src } } impl Display for FunctionOverflowError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}: overflow in argument {} {}", self.signature, self.arg, self.src)?; + write!( + f, + "{}: overflow in argument {} {}", + self.signature, self.arg, self.src + )?; fmt::Result::Ok(()) } -} \ No newline at end of file +} diff --git a/src/errors/functions/stack.rs b/src/errors/functions/stack.rs index ac57cee..d8aae03 100644 --- a/src/errors/functions/stack.rs +++ b/src/errors/functions/stack.rs @@ -1,26 +1,26 @@ +use crate::Error; use crate::Token; -use crate::errors::*; use std::fmt::{self, Display}; /// An error caused by a recursive function going too deep #[derive(Debug, Clone)] pub struct StackError { - src: ParserErrorSource + src: ErrorSource, } impl StackError { /// Create a new instance of this error - /// + /// /// # Arguments /// * `src` - Token causing the error pub fn new(src: &Token) -> Self { Self { - src: ParserErrorSource::new(src) + src: ErrorSource::new(src), } } /// Describes the location and text of the bad token - pub fn source(&self) -> &ParserErrorSource { + pub fn source(&self) -> &ErrorSource { &self.src } } @@ -30,4 +30,4 @@ impl Display for StackError { write!(f, "recursive function went too deep {}", self.src)?; fmt::Result::Ok(()) } -} \ No newline at end of file +} diff --git a/src/errors/internal.rs b/src/errors/internal.rs index c7e12a3..017d426 100644 --- a/src/errors/internal.rs +++ b/src/errors/internal.rs @@ -1,5 +1,5 @@ +use super::ErrorSource; use crate::Token; -use super::ParserErrorSource; use std::fmt::{self, Display}; @@ -8,28 +8,32 @@ const BUG_REPORT_URL : &str = "https://github.com/rscarson/lavendeux-parser/issu /// An error caused by a problem with the parser itself #[derive(Debug, Clone)] pub struct InternalError { - src: ParserErrorSource + src: ErrorSource, } impl InternalError { /// Create a new instance of this error - /// + /// /// # Arguments /// * `src` - Token causing the error pub fn new(src: &Token) -> Self { Self { - src: ParserErrorSource::new(src) + src: ErrorSource::new(src), } } /// Describes the location and text of the bad token - pub fn source(&self) -> &ParserErrorSource { + pub fn source(&self) -> &ErrorSource { &self.src } } impl Display for InternalError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "internal parser issue {}.\nPlease report this problem at {}", self.src, BUG_REPORT_URL)?; + write!( + f, + "internal parser issue {}.\nPlease report this problem at {}", + self.src, BUG_REPORT_URL + )?; fmt::Result::Ok(()) } -} \ No newline at end of file +} diff --git a/src/errors/syntax/unexpected_decorator.rs b/src/errors/syntax/unexpected_decorator.rs index 3b88982..add9308 100644 --- a/src/errors/syntax/unexpected_decorator.rs +++ b/src/errors/syntax/unexpected_decorator.rs @@ -1,33 +1,37 @@ +use crate::Error; use crate::Token; -use crate::errors::*; use std::fmt::{self, Display}; /// An error caused by using a decorator in the wrong place #[derive(Debug, Clone)] pub struct UnexpectedDecoratorError { - src: ParserErrorSource + src: ErrorSource, } impl UnexpectedDecoratorError { /// Create a new instance of this error - /// + /// /// # Arguments /// * `src` - Token causing the error pub fn new(src: &Token) -> Self { Self { - src: ParserErrorSource::new(src) + src: ErrorSource::new(src), } } /// Describes the location and text of the bad token - pub fn source(&self) -> &ParserErrorSource { + pub fn source(&self) -> &ErrorSource { &self.src } } impl Display for UnexpectedDecoratorError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "@decorators must be at the end of a statement {}", self.src)?; + write!( + f, + "@decorators must be at the end of a statement {}", + self.src + )?; fmt::Result::Ok(()) } -} \ No newline at end of file +} diff --git a/src/errors/syntax/unexpected_postfix.rs b/src/errors/syntax/unexpected_postfix.rs index 5540641..bbe45c8 100644 --- a/src/errors/syntax/unexpected_postfix.rs +++ b/src/errors/syntax/unexpected_postfix.rs @@ -1,26 +1,26 @@ +use crate::Error; use crate::Token; -use crate::errors::*; use std::fmt::{self, Display}; /// An error caused by using a postfix operator without an operand #[derive(Debug, Clone)] pub struct UnexpectedPostfixError { - src: ParserErrorSource + src: ErrorSource, } impl UnexpectedPostfixError { /// Create a new instance of this error - /// + /// /// # Arguments /// * `src` - Token causing the error pub fn new(src: &Token) -> Self { Self { - src: ParserErrorSource::new(src) + src: ErrorSource::new(src), } } /// Describes the location and text of the bad token - pub fn source(&self) -> &ParserErrorSource { + pub fn source(&self) -> &ErrorSource { &self.src } } @@ -30,4 +30,4 @@ impl Display for UnexpectedPostfixError { write!(f, "missing operand before factorial operator {}", self.src)?; fmt::Result::Ok(()) } -} \ No newline at end of file +} diff --git a/src/errors/syntax/unterminated_array.rs b/src/errors/syntax/unterminated_array.rs index 200007a..4062741 100644 --- a/src/errors/syntax/unterminated_array.rs +++ b/src/errors/syntax/unterminated_array.rs @@ -1,26 +1,26 @@ +use crate::Error; use crate::Token; -use crate::errors::*; use std::fmt::{self, Display}; /// An error caused by using a postfix operator without an operand #[derive(Debug, Clone)] pub struct UnterminatedArrayError { - src: ParserErrorSource + src: ErrorSource, } impl UnterminatedArrayError { /// Create a new instance of this error - /// + /// /// # Arguments /// * `src` - Token causing the error pub fn new(src: &Token) -> Self { Self { - src: ParserErrorSource::new(src) + src: ErrorSource::new(src), } } /// Describes the location and text of the bad token - pub fn source(&self) -> &ParserErrorSource { + pub fn source(&self) -> &ErrorSource { &self.src } } @@ -30,4 +30,4 @@ impl Display for UnterminatedArrayError { write!(f, "missing ']' {}", self.src)?; fmt::Result::Ok(()) } -} \ No newline at end of file +} diff --git a/src/errors/syntax/unterminated_linebreak.rs b/src/errors/syntax/unterminated_linebreak.rs index 5cd7874..e7d78fb 100644 --- a/src/errors/syntax/unterminated_linebreak.rs +++ b/src/errors/syntax/unterminated_linebreak.rs @@ -1,26 +1,26 @@ +use crate::Error; use crate::Token; -use crate::errors::*; use std::fmt::{self, Display}; /// An error caused by ending a script on a backslash #[derive(Debug, Clone)] pub struct UnterminatedLinebreakError { - src: ParserErrorSource + src: ErrorSource, } impl UnterminatedLinebreakError { /// Create a new instance of this error - /// + /// /// # Arguments /// * `src` - Token causing the error pub fn new(src: &Token) -> Self { Self { - src: ParserErrorSource::new(src) + src: ErrorSource::new(src), } } /// Describes the location and text of the bad token - pub fn source(&self) -> &ParserErrorSource { + pub fn source(&self) -> &ErrorSource { &self.src } } @@ -30,4 +30,4 @@ impl Display for UnterminatedLinebreakError { write!(f, "missing newline after '\\' {}", self.src)?; fmt::Result::Ok(()) } -} \ No newline at end of file +} diff --git a/src/errors/syntax/unterminated_literal.rs b/src/errors/syntax/unterminated_literal.rs index 9e5cc77..89bc18a 100644 --- a/src/errors/syntax/unterminated_literal.rs +++ b/src/errors/syntax/unterminated_literal.rs @@ -1,26 +1,26 @@ +use crate::Error; use crate::Token; -use crate::errors::*; use std::fmt::{self, Display}; /// An error caused by a missing quote #[derive(Debug, Clone)] pub struct UnterminatedLiteralError { - src: ParserErrorSource + src: ErrorSource, } impl UnterminatedLiteralError { /// Create a new instance of this error - /// + /// /// # Arguments /// * `src` - Token causing the error pub fn new(src: &Token) -> Self { Self { - src: ParserErrorSource::new(src) + src: ErrorSource::new(src), } } /// Describes the location and text of the bad token - pub fn source(&self) -> &ParserErrorSource { + pub fn source(&self) -> &ErrorSource { &self.src } } @@ -30,4 +30,4 @@ impl Display for UnterminatedLiteralError { write!(f, "unterminated string literal {}", self.src)?; fmt::Result::Ok(()) } -} \ No newline at end of file +} diff --git a/src/errors/syntax/unterminated_object.rs b/src/errors/syntax/unterminated_object.rs index 6330812..4400dad 100644 --- a/src/errors/syntax/unterminated_object.rs +++ b/src/errors/syntax/unterminated_object.rs @@ -1,26 +1,26 @@ +use crate::Error; use crate::Token; -use crate::errors::*; use std::fmt::{self, Display}; /// An error caused by a missing curly brace #[derive(Debug, Clone)] pub struct UnterminatedObjectError { - src: ParserErrorSource + src: ErrorSource, } impl UnterminatedObjectError { /// Create a new instance of this error - /// + /// /// # Arguments /// * `src` - Token causing the error pub fn new(src: &Token) -> Self { Self { - src: ParserErrorSource::new(src) + src: ErrorSource::new(src), } } /// Describes the location and text of the bad token - pub fn source(&self) -> &ParserErrorSource { + pub fn source(&self) -> &ErrorSource { &self.src } } @@ -30,4 +30,4 @@ impl Display for UnterminatedObjectError { write!(f, "missing '}}' {}", self.src)?; fmt::Result::Ok(()) } -} \ No newline at end of file +} diff --git a/src/errors/syntax/unterminated_paren.rs b/src/errors/syntax/unterminated_paren.rs index f2e0e43..687190a 100644 --- a/src/errors/syntax/unterminated_paren.rs +++ b/src/errors/syntax/unterminated_paren.rs @@ -1,26 +1,26 @@ +use crate::Error; use crate::Token; -use crate::errors::*; use std::fmt::{self, Display}; /// An error caused by a missing parentheses #[derive(Debug, Clone)] pub struct UnterminatedParenError { - src: ParserErrorSource + src: ErrorSource, } impl UnterminatedParenError { /// Create a new instance of this error - /// + /// /// # Arguments /// * `src` - Token causing the error pub fn new(src: &Token) -> Self { Self { - src: ParserErrorSource::new(src) + src: ErrorSource::new(src), } } /// Describes the location and text of the bad token - pub fn source(&self) -> &ParserErrorSource { + pub fn source(&self) -> &ErrorSource { &self.src } } @@ -30,4 +30,4 @@ impl Display for UnterminatedParenError { write!(f, "missing ')' {}", self.src)?; fmt::Result::Ok(()) } -} \ No newline at end of file +} diff --git a/src/errors/values/constant_value.rs b/src/errors/values/constant_value.rs index bd2930c..6a720e0 100644 --- a/src/errors/values/constant_value.rs +++ b/src/errors/values/constant_value.rs @@ -1,5 +1,5 @@ +use crate::Error; use crate::Token; -use crate::errors::*; use std::fmt::{self, Display}; @@ -7,18 +7,18 @@ use std::fmt::{self, Display}; #[derive(Debug, Clone)] pub struct ConstantValueError { cause: String, - src: ParserErrorSource + src: ErrorSource, } impl ConstantValueError { /// Create a new instance of this error - /// + /// /// # Arguments /// * `src` - Token causing the error /// * `cause` - Reason for the error pub fn new(src: &Token, cause: &str) -> Self { Self { cause: cause.to_string(), - src: ParserErrorSource::new(src) + src: ErrorSource::new(src), } } @@ -28,14 +28,18 @@ impl ConstantValueError { } /// Describes the location and text of the bad token - pub fn source(&self) -> &ParserErrorSource { + pub fn source(&self) -> &ErrorSource { &self.src } } impl Display for ConstantValueError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "could not overwrite constant value {} {}", self.cause, self.src)?; + write!( + f, + "could not overwrite constant value {} {}", + self.cause, self.src + )?; fmt::Result::Ok(()) } -} \ No newline at end of file +} diff --git a/src/errors/values/object_key.rs b/src/errors/values/object_key.rs index 50414f3..9671778 100644 --- a/src/errors/values/object_key.rs +++ b/src/errors/values/object_key.rs @@ -1,5 +1,5 @@ +use crate::Error; use crate::Token; -use crate::errors::*; use std::fmt::{self, Display}; @@ -7,18 +7,18 @@ use std::fmt::{self, Display}; #[derive(Debug, Clone)] pub struct ObjectKeyError { cause: String, - src: ParserErrorSource + src: ErrorSource, } impl ObjectKeyError { /// Create a new instance of this error - /// + /// /// # Arguments /// * `src` - Token causing the error /// * `cause` - Reason for the error pub fn new(src: &Token, cause: &str) -> Self { Self { cause: cause.to_string(), - src: ParserErrorSource::new(src) + src: ErrorSource::new(src), } } @@ -28,7 +28,7 @@ impl ObjectKeyError { } /// Describes the location and text of the bad token - pub fn source(&self) -> &ParserErrorSource { + pub fn source(&self) -> &ErrorSource { &self.src } } @@ -38,4 +38,4 @@ impl Display for ObjectKeyError { write!(f, "object key {} not found {}", self.cause, self.src)?; fmt::Result::Ok(()) } -} \ No newline at end of file +} diff --git a/src/errors/values/overflow.rs b/src/errors/values/overflow.rs index e884866..b3baec7 100644 --- a/src/errors/values/overflow.rs +++ b/src/errors/values/overflow.rs @@ -1,26 +1,26 @@ +use crate::Error; use crate::Token; -use crate::errors::*; use std::fmt::{self, Display}; /// An error caused by a calculation that resulted in an overflow #[derive(Debug, Clone)] pub struct OverflowError { - src: ParserErrorSource + src: ErrorSource, } impl OverflowError { /// Create a new instance of this error - /// + /// /// # Arguments /// * `src` - Token causing the error pub fn new(src: &Token) -> Self { Self { - src: ParserErrorSource::new(src) + src: ErrorSource::new(src), } } /// Describes the location and text of the bad token - pub fn source(&self) -> &ParserErrorSource { + pub fn source(&self) -> &ErrorSource { &self.src } } @@ -30,4 +30,4 @@ impl Display for OverflowError { write!(f, "arithmetic overflow {}", self.src)?; fmt::Result::Ok(()) } -} \ No newline at end of file +} diff --git a/src/errors/values/parse_value.rs b/src/errors/values/parse_value.rs index c8928c7..b7e1d3b 100644 --- a/src/errors/values/parse_value.rs +++ b/src/errors/values/parse_value.rs @@ -1,5 +1,6 @@ +use crate::Error; +use crate::ExpectedTypes; use crate::Token; -use crate::errors::*; use std::fmt::{self, Display}; @@ -8,11 +9,11 @@ use std::fmt::{self, Display}; pub struct ParseValueError { cause: String, variant: ExpectedTypes, - src: ParserErrorSource + src: ErrorSource, } impl ParseValueError { /// Create a new instance of this error - /// + /// /// # Arguments /// * `src` - Token causing the error /// * `cause` - Reason for the error @@ -21,7 +22,7 @@ impl ParseValueError { Self { cause: cause.to_string(), variant, - src: ParserErrorSource::new(src) + src: ErrorSource::new(src), } } @@ -36,7 +37,7 @@ impl ParseValueError { } /// Describes the location and text of the bad token - pub fn source(&self) -> &ParserErrorSource { + pub fn source(&self) -> &ErrorSource { &self.src } } @@ -44,8 +45,16 @@ impl ParseValueError { impl Display for ParseValueError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let variant = format!("{}", self.variant); - let suffix = if ['a','e','i','o','u'].contains(&variant.chars().next().unwrap()) {"n"} else {" "}; - write!(f, "could not parse {} as a{suffix} {} {}", self.cause, self.variant, self.src)?; + let suffix = if ['a', 'e', 'i', 'o', 'u'].contains(&variant.chars().next().unwrap()) { + "n" + } else { + " " + }; + write!( + f, + "could not parse {} as a{suffix} {} {}", + self.cause, self.variant, self.src + )?; fmt::Result::Ok(()) } -} \ No newline at end of file +} diff --git a/src/errors/values/parsing.rs b/src/errors/values/parsing.rs index b08cc07..0f01727 100644 --- a/src/errors/values/parsing.rs +++ b/src/errors/values/parsing.rs @@ -1,5 +1,5 @@ +use crate::Error; use crate::Token; -use crate::errors::*; use std::fmt::{self, Display}; @@ -8,11 +8,11 @@ use std::fmt::{self, Display}; pub struct ParsingError { format: String, cause: String, - src: ParserErrorSource + src: ErrorSource, } impl ParsingError { /// Create a new instance of this error - /// + /// /// # Arguments /// * `src` - Token causing the error /// * `format` - Type of formatting @@ -21,7 +21,7 @@ impl ParsingError { Self { format: format.to_string(), cause: cause.to_string(), - src: ParserErrorSource::new(src) + src: ErrorSource::new(src), } } @@ -36,14 +36,18 @@ impl ParsingError { } /// Describes the location and text of the bad token - pub fn source(&self) -> &ParserErrorSource { + pub fn source(&self) -> &ErrorSource { &self.src } } impl Display for ParsingError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{} parsing error: {} {}", self.format, self.cause, self.src)?; + write!( + f, + "{} parsing error: {} {}", + self.format, self.cause, self.src + )?; fmt::Result::Ok(()) } -} \ No newline at end of file +} diff --git a/src/errors/values/range.rs b/src/errors/values/range.rs index 5f4e973..300e506 100644 --- a/src/errors/values/range.rs +++ b/src/errors/values/range.rs @@ -1,5 +1,5 @@ -use crate::{Value, Token}; -use crate::errors::*; +use crate::Error; +use crate::{Token, Value}; use std::fmt::{self, Display}; @@ -7,18 +7,18 @@ use std::fmt::{self, Display}; #[derive(Debug, Clone)] pub struct RangeError { cause: Value, - src: ParserErrorSource + src: ErrorSource, } impl RangeError { /// Create a new instance of this error - /// + /// /// # Arguments /// * `src` - Token causing the error /// * `cause` - Reason for the error pub fn new(src: &Token, cause: &Value) -> Self { Self { cause: cause.clone(), - src: ParserErrorSource::new(src) + src: ErrorSource::new(src), } } @@ -28,7 +28,7 @@ impl RangeError { } /// Describes the location and text of the bad token - pub fn source(&self) -> &ParserErrorSource { + pub fn source(&self) -> &ErrorSource { &self.src } } @@ -38,4 +38,4 @@ impl Display for RangeError { write!(f, "value out of range: {} {}", self.cause, self.src)?; fmt::Result::Ok(()) } -} \ No newline at end of file +} diff --git a/src/errors/values/underflow.rs b/src/errors/values/underflow.rs index fc3d2f3..ce09513 100644 --- a/src/errors/values/underflow.rs +++ b/src/errors/values/underflow.rs @@ -1,26 +1,26 @@ +use crate::Error; use crate::Token; -use crate::errors::*; use std::fmt::{self, Display}; /// An error caused by a calculation that resulted in an underflow #[derive(Debug, Clone)] pub struct UnderflowError { - src: ParserErrorSource + src: ErrorSource, } impl UnderflowError { /// Create a new instance of this error - /// + /// /// # Arguments /// * `src` - Token causing the error pub fn new(src: &Token) -> Self { Self { - src: ParserErrorSource::new(src) + src: ErrorSource::new(src), } } /// Describes the location and text of the bad token - pub fn source(&self) -> &ParserErrorSource { + pub fn source(&self) -> &ErrorSource { &self.src } } @@ -30,4 +30,4 @@ impl Display for UnderflowError { write!(f, "arithmetic underflow {}", self.src)?; fmt::Result::Ok(()) } -} \ No newline at end of file +} diff --git a/src/errors/values/value_type.rs b/src/errors/values/value_type.rs index f40aa2f..0859e49 100644 --- a/src/errors/values/value_type.rs +++ b/src/errors/values/value_type.rs @@ -1,5 +1,6 @@ +use crate::Error; +use crate::ExpectedTypes; use crate::Token; -use crate::errors::*; use std::fmt::{self, Display}; @@ -7,18 +8,18 @@ use std::fmt::{self, Display}; #[derive(Debug, Clone)] pub struct ValueTypeError { expected: ExpectedTypes, - src: ParserErrorSource + src: ErrorSource, } impl ValueTypeError { /// Create a new instance of this error - /// + /// /// # Arguments /// * `src` - Token causing the error /// * `expected` - The type of value required pub fn new(src: &Token, expected: ExpectedTypes) -> Self { Self { expected, - src: ParserErrorSource::new(src) + src: ErrorSource::new(src), } } @@ -28,14 +29,18 @@ impl ValueTypeError { } /// Describes the location and text of the bad token - pub fn source(&self) -> &ParserErrorSource { + pub fn source(&self) -> &ErrorSource { &self.src } } impl Display for ValueTypeError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "invalid type for value, expected {} {}", self.expected, self.src)?; + write!( + f, + "invalid type for value, expected {} {}", + self.expected, self.src + )?; fmt::Result::Ok(()) } -} \ No newline at end of file +} diff --git a/src/errors/values/variable_name.rs b/src/errors/values/variable_name.rs index d909f87..2e9fbad 100644 --- a/src/errors/values/variable_name.rs +++ b/src/errors/values/variable_name.rs @@ -1,5 +1,5 @@ +use crate::Error; use crate::Token; -use crate::errors::*; use std::fmt::{self, Display}; @@ -7,18 +7,18 @@ use std::fmt::{self, Display}; #[derive(Debug, Clone)] pub struct VariableNameError { cause: String, - src: ParserErrorSource + src: ErrorSource, } impl VariableNameError { /// Create a new instance of this error - /// + /// /// # Arguments /// * `src` - Token causing the error /// * `cause` - Reason for the error pub fn new(src: &Token, cause: &str) -> Self { Self { cause: cause.to_string(), - src: ParserErrorSource::new(src) + src: ErrorSource::new(src), } } @@ -28,7 +28,7 @@ impl VariableNameError { } /// Describes the location and text of the bad token - pub fn source(&self) -> &ParserErrorSource { + pub fn source(&self) -> &ErrorSource { &self.src } } @@ -38,4 +38,4 @@ impl Display for VariableNameError { write!(f, "unrecognized variable {} {}", self.cause, self.src)?; fmt::Result::Ok(()) } -} \ No newline at end of file +} diff --git a/src/expected_types.rs b/src/expected_types.rs new file mode 100644 index 0000000..f635c7b --- /dev/null +++ b/src/expected_types.rs @@ -0,0 +1,69 @@ +use std::fmt; + +use crate::Value; + +/// Represents a type of value that was expected +#[derive(Debug, Copy, Clone)] +pub enum ExpectedTypes { + /// Integer value + Int, + + /// Floating point value + Float, + + /// Any numeric value + IntOrFloat, + + /// String value + String, + + /// Boolean value + Boolean, + + /// Array value + Array, + + /// Object value + Object, + + /// Any type of value + Any, +} + +impl ExpectedTypes { + /// Returns true if the given value matches expectations + pub fn matches(&self, value: &Value) -> bool { + if value.is_compound() { + true + } else { + self.strict_matches(value) + } + } + + /// Returns true if the given value matches expectations and count + pub fn strict_matches(&self, value: &Value) -> bool { + match self { + ExpectedTypes::Int => value.is_int(), + ExpectedTypes::Float => value.is_float(), + ExpectedTypes::IntOrFloat => value.is_numeric(), + + // Can be converted from any type + _ => true, + } + } +} + +impl fmt::Display for ExpectedTypes { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + ExpectedTypes::Int => write!(f, "integer"), + ExpectedTypes::Float => write!(f, "float"), + ExpectedTypes::IntOrFloat => write!(f, "integer or float"), + ExpectedTypes::String => write!(f, "string"), + ExpectedTypes::Boolean => write!(f, "boolean"), + ExpectedTypes::Array => write!(f, "array"), + ExpectedTypes::Object => write!(f, "object"), + ExpectedTypes::Any => write!(f, "any"), + } + } +} diff --git a/src/extensions.rs b/src/extensions.rs deleted file mode 100644 index bc88825..0000000 --- a/src/extensions.rs +++ /dev/null @@ -1,448 +0,0 @@ -use crate::{Value, errors::*, Token}; - -use js_sandbox::{Script, JsError}; -use serde::{Deserialize, Serialize}; -use core::time::Duration; -use std::error::Error; -use std::collections::HashMap; -use std::fs; - -const SCRIPT_TIMEOUT : u64 = 1000; - -/// Holds a set of registered extensions -#[derive(Deserialize, Serialize, Clone)] -pub struct ExtensionTable(HashMap); -impl ExtensionTable { - /// Create a new empty table - pub fn new() -> Self { - Self(HashMap::new()) - } - - /// Add an extension - /// - /// # Arguments - /// * `filename` - File name - /// * `extension` - Extension to add - pub fn add(&mut self, filename: &str, extension: Extension) { - self.0.insert(filename.to_string(), extension); - } - - /// Load an extension from a filename - /// - /// # Arguments - /// * `filename` - File name - pub fn load(&mut self, filename: &str) -> Result { - let e = Extension::new(filename)?; - self.0.insert(filename.to_string(), e.clone()); - Ok(e) - } - - /// Attempt to load all extensions in a directory - pub fn load_all(&mut self, path: &str) -> Vec>> { - let e = Extension::load_all(path); - self.0.clear(); - for extension in e.iter().flatten() { - self.0.insert(extension.filename().to_string(), extension.clone()); - } - e - } - - /// Delete an extension - pub fn remove(&mut self, filename: &str) { - self.0.remove(filename); - } - - /// Returns the full list of extensions available - pub fn all(&mut self) -> Vec<&mut Extension> { - let mut a = Vec::from_iter(self.0.values_mut()); - a.sort_by(|f1, f2|f1.name().cmp(f2.name())); - a - } - - /// Determine if a function exists in the extension - /// - /// # Arguments - /// * `name` - Function name - pub fn has_function(&mut self, name: &str) -> bool { - for extension in self.all() { - if extension.has_function(name) { - return true; - } - } - false - } - - /// Try to call a function in the loaded extensions - pub fn call_function(&mut self, name: &str, token: &Token, args: &[Value], variables: &mut HashMap) -> Result { - for extension in self.all() { - if extension.has_function(name) { - return extension.call_function(name, token, args, variables); - } - } - Err(FunctionNameError::new(token, name).into()) - } - - /// Determine if a decorator exists in the extension - /// - /// # Arguments - /// * `name` - Decorator name - pub fn has_decorator(&mut self, name: &str) -> bool { - for extension in self.all() { - if extension.has_decorator(name) { - return true; - } - } - false - } - - /// Try to call a decorator in the loaded extensions - pub fn call_decorator(&mut self, name: &str, token: &Token, variables: &mut HashMap) -> Result { - for extension in self.all() { - if extension.has_decorator(name) { - return extension.call_decorator(name, token, variables); - } - } - Err(FunctionNameError::new(token, &format!("@{}", name)).into()) - } -} -impl Default for ExtensionTable { - fn default() -> Self { - Self::new() - } -} - -fn default_name() -> String { "Unnamed Extension".to_string() } -fn default_author() -> String { "Anonymous".to_string() } -fn default_version() -> String { "0.0.0".to_string() } - -/// Represents a single loaded extension. It describes the functions and decorators it adds, -/// as well as metadata about the extension and it's author. -/// -/// Add this to a ParserState to use it in expressions, or call the extension directly with -/// call_function / call_decorator -#[derive(Deserialize, Serialize, Clone, Debug, Eq, PartialEq)] -pub struct Extension { - #[serde(default)] - filename: String, - - #[serde(default = "default_name")] - name: String, - - #[serde(default = "default_author")] - author: String, - - #[serde(default = "default_version")] - version: String, - - #[serde(default)] - contents: String, - - #[serde(default)] - functions: HashMap, - - #[serde(default)] - decorators: HashMap -} - -impl std::fmt::Display for Extension { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{} v{}, by {}", self.name, self.version, self.author) - } -} - -unsafe impl Send for Extension {} -impl Extension { - /// Load an extension from a filename - /// - /// # Arguments - /// * `filename` - Source filename - pub fn new(filename: &str) -> Result { - match fs::read_to_string(filename) { - Ok(s) => { - match script_from_string(filename, &s) { - Ok(v) => Ok(v), - Err(e) => Err(std::io::Error::new( - std::io::ErrorKind::InvalidData, - e.to_string().replace("sandboxed.js", filename) - )) - } - }, - Err(e) => Err(e) - } - } - - /// Create a new dummy extension that cannot be called or used - /// - /// # Arguments - /// * `name` - Extension name - /// * `author` - Extension author - /// * `author` - Extension author - /// * `version` - Extension version - /// * `functions` - Extension functions - /// * `decorators` - Extension decorators - pub fn new_stub(name: Option<&str>, author: Option<&str>, version: Option<&str>, functions: Vec, decorators: Vec) -> Self { - let mut stub = Self { - name: name.unwrap_or(&default_name()).to_string(), - author: author.unwrap_or(&default_author()).to_string(), - version: version.unwrap_or(&default_version()).to_string(), - contents: "".to_string(), - filename: "".to_string(), - functions: HashMap::new(), - decorators: HashMap::new() - }; - - for f in functions { stub.functions.insert(f.clone(), f); } - for d in decorators { stub.decorators.insert(d.clone(), d); } - - stub - } - - /// Attempt to load all extensions in a directory - pub fn load_all(directory: &str) -> Vec>> { - let mut extensions : Vec>> = Vec::new(); - - match fs::read_dir(directory) { - Ok(entries) => { - for file in entries.flatten() { - if let Some(filename) = file.path().to_str() { - if !filename.ends_with("js") { continue; } - match Extension::new(filename) { - Ok(extension) => extensions.push(Ok(extension)), - Err(e) => {extensions.push(Err(Box::new(e)))} - } - } - } - }, - Err(e) => { - extensions.push(Err(Box::new(e))); - } - } - - extensions - } - - /// Determine if a function exists in the extension - /// - /// # Arguments - /// * `name` - Function name - pub fn has_function(&self, name: &str) -> bool { - self.functions.contains_key(name) - } - - /// Load the script from string - pub fn load_script(&self) -> Result { - match Script::from_string(&self.contents) { - Ok(s) => Ok(s), - Err(e) => Err(e) - } - } - - /// Call a function from the extension - /// - /// # Arguments - /// * `name` - Function name - /// * `args` - Values to pass in - pub fn call_function(&mut self, name: &str, token: &Token, args: &[Value], variables: &mut HashMap) -> Result { - match self.load_script() { - Ok(mut script) => { - // Inject parser state - self.call_js_function(&mut script, "setState", token, (&variables,))?; - - // Call function - let e: ParserError = FunctionNameError::new(token, name).into(); - let fname = self.functions.get(name).ok_or(e)?; - let result: Value = self.call_js_function(&mut script, fname, token, (&args.to_vec(),))?; - - // Pull out modified state - let state : HashMap = self.call_js_function(&mut script, "getState", token, (&variables,))?; - variables.clear(); - for k in state.keys() { - variables.insert(k.to_string(), state.get(k).unwrap().clone()); - } - - Ok(result) - }, - Err(e) => Err(ScriptError::from_jserror(token, self.filename(), e).into()) - } - } - - /// Determine if a decorator exists in the extension - /// - /// # Arguments - /// * `name` - Decorator name - pub fn has_decorator(&self, name: &str) -> bool { - self.decorators.contains_key(name) - } - - /// Call a decorator from the extension - /// - /// # Arguments - /// * `name` - Decorator name - /// * `arg` - Value to pass in - pub fn call_decorator(&mut self, name: &str, token: &Token, variables: &mut HashMap) -> Result { - match self.load_script() { - Ok(mut script) => { - // Inject parser state - self.call_js_function(&mut script, "setState", token, (&variables,))?; - - // Call decorator - let e: ParserError = DecoratorNameError::new(token, name).into(); - let fname = self.decorators.get(name).ok_or(e)?; - let result: String = self.call_js_function(&mut script, fname, token, (token.value(),))?; - - // Pull out modified state - let state : HashMap = self.call_js_function(&mut script, "getState", token, (&variables,))?; - variables.clear(); - for k in state.keys() { - variables.insert(k.to_string(), state.get(k).unwrap().clone()); - } - - Ok(result) - }, - Err(e) => Err(ScriptError::from_jserror(token, self.filename(), e).into()) - } - } - - /// Returns the file from which an extension was loaded - pub fn filename(&self) -> &str { - &self.filename - } - - /// Returns the name of the extension - pub fn name(&self) -> &str { - &self.name - } - - /// Returns the name of the extension's author - pub fn author(&self) -> &str { - &self.author - } - - /// Returns the version of the extension - pub fn version(&self) -> &str { - &self.version - } - - /// Return the list of all functions in the extension - pub fn functions(&self) -> Vec { - let mut a : Vec = self.functions.keys().cloned().collect(); - a.sort(); - a - } - - /// Return the list of all decorators in the extension - pub fn decorators(&self) -> Vec { - let mut a : Vec = self.decorators.keys().cloned().collect(); - a.sort(); - a - } - - fn call_js_function(&self, script: &mut Script, function: &str, token: &Token, args: A) -> Result - where T: serde::de::DeserializeOwned, A: js_sandbox::CallArgs { - match script.call::(function, args) { - Ok(r) => Ok(r), - Err(e) => Err(ScriptError::from_jserror(token, &format!("{}:{}", self.filename(), function), e).into()) - } - } -} - -/// Load a script from a string -/// -/// # Arguments -/// * `code` - JS source as string -fn script_from_string(filename: &str, code: &str) -> Result { - match Script::from_string(code) { - Ok(script) => { - let mut e : Extension = script.with_timeout(Duration::from_millis(SCRIPT_TIMEOUT)) - .call("extension", ())?; - e.contents = code.to_string(); - e.filename = filename.to_string(); - - // Append state information - e.contents = format!("{}\n\n{}", - " - let lavendeux_state = {}; - const setState = (s) => {lavendeux_state = s}; - const getState = () => lavendeux_state;", - e.contents - ); - - Ok(e) - }, - Err(e) => Err(e) - } -} - -#[cfg(test)] -mod test_extensions { - use super::*; - - #[test] - fn test_new() { - let e = Extension::new("example_extensions/colour_utils.js").unwrap(); - assert_eq!("HTML Colour Utilities", e.name); - } - - #[test] - fn test_to_string() { - let e = Extension::new("example_extensions/colour_utils.js").unwrap(); - assert_eq!("HTML Colour Utilities v0.2.0, by @rscarson", e.to_string()); - } - - #[test] - fn test_has_function() { - let e = Extension::new("example_extensions/colour_utils.js").unwrap(); - assert_eq!(true, e.has_function("complement")); - assert_eq!(false, e.has_function("foobar")); - } - - #[test] - fn test_call_function() { - let mut e = Extension::new("example_extensions/colour_utils.js").unwrap(); - assert_eq!(Value::Integer(0x00FFFF), e.call_function("complement", &Token::dummy(""), &[Value::Integer(0xFFAA00)], &mut HashMap::new()).unwrap()); - assert_eq!(Value::Integer(0xFFF), e.call_function("color", &Token::dummy(""), &[Value::String("white".to_string())], &mut HashMap::new()).unwrap()); - } - - #[test] - fn test_maintains_state() { - let mut e = Extension::new("example_extensions/stateful_functions.js").unwrap(); - let mut state: HashMap = HashMap::new(); - state.insert("foo".to_string(), Value::String("bar".to_string())); - assert_eq!(Value::Integer(0xFFAA00), e.call_function("set", &Token::dummy(""), &[Value::String("test".to_string()), Value::Integer(0xFFAA00)], &mut state).unwrap()); - assert_eq!(true, state.contains_key("test") && state.get("test").unwrap().as_int().unwrap() == 0xFFAA00); - } - - #[test] - fn test_can_fail() { - let mut e = Extension::new("example_extensions/colour_utils.js").unwrap(); - assert_eq!(true, matches!(e.call_function("complement", &Token::dummy(""), &[], &mut HashMap::new()), Err(_))); - } - - #[test] - fn test_has_decorator() { - let e = Extension::new("example_extensions/colour_utils.js").unwrap(); - assert_eq!(true, e.has_decorator("color")); - assert_eq!(false, e.has_decorator("foobar")); - } - - #[test] - fn test_call_decorator() { - let mut e = Extension::new("example_extensions/colour_utils.js").unwrap(); - let mut state: HashMap = HashMap::new(); - let mut token = Token::dummy(""); - token.set_value(Value::Integer(0xFF)); - assert_eq!("#ff0000", e.call_decorator("color", &token, &mut state).unwrap()); - } - - #[test] - fn test_load_all() { - let e = Extension::load_all("example_extensions"); - assert_eq!(true, e.len() > 0); - } - - #[test] - fn test_color() { - let mut e = Extension::new("example_extensions/colour_utils.js").unwrap(); - assert_eq!(Value::Integer(0x00FFFF), e.call_function("complement", &Token::dummy(""), &[Value::Integer(0xFFAA00)], &mut HashMap::new()).unwrap()); - } -} \ No newline at end of file diff --git a/src/extensions/extension.rs b/src/extensions/extension.rs new file mode 100644 index 0000000..d153d8b --- /dev/null +++ b/src/extensions/extension.rs @@ -0,0 +1,366 @@ +use crate::{Token, Value}; + +use js_playground::Module; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +use super::{function::ExtensionFunction, runtime::ExtensionsRuntime}; + +fn default_name() -> String { + "Unnamed Extension".to_string() +} +fn default_author() -> String { + "Anonymous".to_string() +} +fn default_version() -> String { + "0.0.0".to_string() +} + +/// Represents a single loaded extension. It describes the functions and decorators it adds, +/// as well as metadata about the extension and it's author. +/// +/// Add this to a ParserState to use it in expressions, or call the extension directly with +/// call_function / call_decorator +#[derive(Deserialize, Serialize, Clone, Debug, Eq, PartialEq)] +pub struct Extension { + #[serde(default)] + /// Associated code / filename for the extension + pub module: Module, + + #[serde(default = "default_name")] + /// Name of this extension + pub name: String, + + #[serde(default = "default_author")] + /// Author of this extension + pub author: String, + + #[serde(default = "default_version")] + /// Version of the extension + pub version: String, + + #[serde(default)] + /// Defines all the functions provided by this extension + pub function_definitions: Option>, + + #[serde(default)] + /// Defines all the decorators provided by this extension + pub decorator_definitions: Option>, + + #[serde(default)] + /// Legacy extension support + pub functions: Option>, + + #[serde(default)] + /// Legacy extension support + pub decorators: Option>, +} + +impl std::fmt::Display for Extension { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{} v{}, by {}", self.name, self.version, self.author) + } +} + +impl Extension { + /// Create a new extension object by loading it from a JS module + pub fn new(path: &str) -> Result { + ExtensionsRuntime::load_extension(path) + } + + /// Determine if a function exists in the extension + /// + /// # Arguments + /// * `name` - Function name + pub fn has_function(&self, name: &str) -> bool { + if let Some(functions) = &self.function_definitions { + functions.contains_key(name) + } else if let Some(functions) = &self.functions { + // Legacy function support + functions.contains_key(name) + } else { + false + } + } + + /// Call a function from the extension + /// + /// # Arguments + /// * `name` - Function name + /// * `args` - Values to pass in + pub fn call_function( + &mut self, + name: &str, + args: &[Value], + variables: &mut HashMap, + ) -> Result { + if let Some(functions) = &self.function_definitions { + let function_properties = functions + .get(name) + .ok_or(js_playground::Error::ValueNotFound(name.to_string()))?; + function_properties.call(&self.module, args, variables) + } else if let Some(functions) = &self.functions { + // Legacy function support + let function_name = functions + .get(name) + .ok_or(js_playground::Error::ValueNotFound(name.to_string()))?; + ExtensionFunction::call_legacy(function_name, &self.module, args) + } else { + Err(js_playground::Error::JsonDecode( + "invalid extension definition".to_string(), + )) + } + } + + /// Determine if a decorator exists in the extension + /// + /// # Arguments + /// * `name` - Decorator name + pub fn has_decorator(&self, name: &str) -> bool { + if let Some(decorators) = &self.function_definitions { + decorators.contains_key(name) + } else if let Some(decorators) = &self.decorators { + // Legacy function support + decorators.contains_key(name) + } else { + false + } + } + + /// Call a decorator from the extension + /// + /// # Arguments + /// * `name` - Decorator name + /// * `arg` - Value to pass in + pub fn call_decorator( + &mut self, + name: &str, + token: &Token, + variables: &mut HashMap, + ) -> Result { + if let Some(decorator) = &self.decorator_definitions { + let function_properties = decorator + .get(name) + .ok_or(js_playground::Error::ValueNotFound(name.to_string()))?; + Ok(function_properties + .call(&self.module, &[token.value()], variables)? + .to_string()) + } else if let Some(decorator) = &self.decorators { + // Legacy function support + let function_name = decorator + .get(name) + .ok_or(js_playground::Error::ValueNotFound(name.to_string()))?; + ExtensionFunction::call_legacy_decorator(function_name, &self.module, token.value()) + } else { + Err(js_playground::Error::JsonDecode( + "invalid extension definition".to_string(), + )) + } + } + + /// Returns the file from which an extension was loaded + pub fn filename(&self) -> &str { + self.module.filename() + } + + /// Returns the name of the extension + pub fn name(&self) -> &str { + &self.name + } + + /// Returns the name of the extension's author + pub fn author(&self) -> &str { + &self.author + } + + /// Returns the version of the extension + pub fn version(&self) -> &str { + &self.version + } + + /// Return the list of all functions in the extension + pub fn functions(&self) -> Vec { + let mut function_keys = if let Some(functions) = &self.function_definitions { + functions.keys().cloned().collect() + } else if let Some(functions) = &self.functions { + functions.keys().cloned().collect() + } else { + vec![] + }; + + function_keys.sort(); + function_keys + } + + /// Return the list of all functions, with complete signatures + pub fn function_signatures(&self) -> Vec { + let mut function_keys = if let Some(functions) = &self.function_definitions { + functions.values().map(|k| k.signature()).collect() + } else if let Some(functions) = &self.functions { + functions.keys().map(|k| format!("{}()", k)).collect() + } else { + vec![] + }; + + function_keys.sort(); + function_keys + } + + /// Return the list of all decorators in the extension + pub fn decorators(&self) -> Vec { + let mut decorator_keys = if let Some(decorators) = &self.decorator_definitions { + decorators.keys().cloned().collect() + } else if let Some(decorators) = &self.decorators { + decorators.keys().cloned().collect() + } else { + vec![] + }; + + decorator_keys.sort(); + decorator_keys + } + + /// Return the list of all decorators, with complete signatures + pub fn decorator_signatures(&self) -> Vec { + let mut decorator_keys = if let Some(decorators) = &self.decorator_definitions { + decorators.values().map(|k| k.signature()).collect() + } else if let Some(decorators) = &self.decorators { + decorators.keys().map(|k| format!("@{}", k)).collect() + } else { + vec![] + }; + + decorator_keys.sort(); + decorator_keys + } +} + +#[cfg(test)] +mod test_extensions { + use super::*; + + #[test] + fn test_new() { + let e = Extension::new("example_extensions/colour_utils.js").unwrap(); + assert_eq!("HTML Colour Utilities", e.name); + } + + #[test] + fn test_to_string() { + let e = Extension::new("example_extensions/colour_utils.js").unwrap(); + assert_eq!("HTML Colour Utilities v0.2.0, by @rscarson", e.to_string()); + } + + #[test] + fn test_has_function() { + let e = Extension::new("example_extensions/colour_utils.js").unwrap(); + assert_eq!(true, e.has_function("complement")); + assert_eq!(false, e.has_function("foobar")); + } + + #[test] + fn test_call_simple() { + let mut e = Extension::new("example_extensions/simple.js").unwrap(); + assert_eq!( + Value::Float(3.0), + e.call_function( + "add", + &[Value::Integer(1), Value::Integer(2)], + &mut HashMap::new() + ) + .unwrap() + ); + } + + #[test] + fn test_call_function() { + let mut e = Extension::new("example_extensions/colour_utils.js").unwrap(); + assert_eq!( + Value::Integer(0x00FFFF), + e.call_function( + "complement", + &[Value::Integer(0xFFAA00)], + &mut HashMap::new() + ) + .unwrap() + ); + assert_eq!( + Value::Integer(0xFFF), + e.call_function( + "color", + &[Value::String("white".to_string())], + &mut HashMap::new() + ) + .unwrap() + ); + } + + #[test] + fn test_maintains_state() { + let mut e = Extension::new("example_extensions/stateful_functions.js").unwrap(); + let mut state: HashMap = HashMap::new(); + state.insert("foo".to_string(), Value::String("bar".to_string())); + assert_eq!( + Value::Integer(0xFFAA00), + e.call_function( + "put", + &[Value::String("test".to_string()), Value::Integer(0xFFAA00)], + &mut state + ) + .unwrap() + ); + assert_eq!(Some(&Value::Integer(0xFFAA00)), state.get("test")); + } + + #[test] + fn test_can_fail() { + let mut e = Extension::new("example_extensions/colour_utils.js").unwrap(); + assert_eq!( + true, + matches!( + e.call_function("complement", &[], &mut HashMap::new()), + Err(_) + ) + ); + } + + #[test] + fn test_has_decorator() { + let e = Extension::new("example_extensions/colour_utils.js").unwrap(); + assert_eq!(true, e.has_decorator("color")); + assert_eq!(false, e.has_decorator("foobar")); + } + + #[test] + fn test_call_decorator() { + let mut e = Extension::new("example_extensions/colour_utils.js").unwrap(); + let mut state: HashMap = HashMap::new(); + let mut token = Token::dummy(""); + token.set_value(Value::Integer(0xFF)); + assert_eq!( + "#ff0000", + e.call_decorator("color", &token, &mut state).unwrap() + ); + } + /* + #[test] + fn test_load_all() { + let mut table = ExtensionTable::new(); + let e = table.load_all("example_extensions"); + assert_eq!(true, e.len() > 0); + } + */ + #[test] + fn test_color() { + let mut e = Extension::new("example_extensions/colour_utils.js").unwrap(); + assert_eq!( + Value::Integer(0x00FFFF), + e.call_function( + "complement", + &[Value::Integer(0xFFAA00)], + &mut HashMap::new() + ) + .unwrap() + ); + } +} diff --git a/src/extensions/function.rs b/src/extensions/function.rs new file mode 100644 index 0000000..4737919 --- /dev/null +++ b/src/extensions/function.rs @@ -0,0 +1,125 @@ +use crate::Value; + +use js_playground::{json_args, Module}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +use super::runtime::ExtensionsRuntime; + +#[derive(Deserialize, Serialize, Clone, Debug, Eq, PartialEq)] +pub struct ExtensionFunction { + pub returns: String, + pub argument_types: Vec, + pub fname: String, + pub ftype: String, +} + +impl ExtensionFunction { + fn decorator_signature(&self) -> String { + format!( + "[{}] @{}", + self.argument_types + .get(0) + .or(Some(&"Any".to_string().to_lowercase())) + .unwrap(), + self.fname + ) + } + + fn function_signature(&self) -> String { + format!( + "{}({}) -> {}", + self.fname, + self.argument_types + .iter() + .map(|a| format!("[{}]", a.to_lowercase())) + .collect::>() + .join(", "), + self.returns.to_lowercase() + ) + } + + pub fn signature(&self) -> String { + if self.ftype == "decorator" { + self.decorator_signature() + } else { + self.function_signature() + } + } + + pub fn call_legacy( + name: &str, + module: &Module, + args: &[Value], + ) -> Result { + ExtensionsRuntime::with(|runtime| match runtime.load_module(module) { + Ok(module_context) => { + let mut _args = serde_json::to_value(args)?; + runtime.call_function::(&module_context, &name, &[_args]) + } + Err(e) => Err(e), + }) + } + + pub fn call_legacy_decorator( + name: &str, + module: &Module, + arg: Value, + ) -> Result { + ExtensionsRuntime::with(|runtime| match runtime.load_module(module) { + Ok(module_context) => { + let mut _arg = serde_json::to_value(arg.clone())?; + runtime.call_function::(&module_context, &name, &[_arg]) + } + Err(e) => Err(e), + }) + } + + pub fn call( + &self, + module: &Module, + args: &[Value], + variables: &mut HashMap, + ) -> Result { + ExtensionsRuntime::with(|runtime| { + match runtime.load_module(module) { + Ok(module_context) => { + // Inject parser state + let json_variables = serde_json::to_value(variables.clone())?; + runtime.call_function( + &module_context, + "setLavendeuxState", + json_args!(json_variables), + )?; + + // Decode arguments + let mut _args: Vec = vec![serde_json::to_value(self)?]; + for arg in args { + _args.push(serde_json::to_value(arg)?); + } + + // Call the function + let result: Value = runtime.call_function( + &module_context, + "callLavendeuxFunction", + _args.as_slice(), + )?; + + // Pull out modified state + let state: HashMap = runtime.call_function( + &module_context, + "getLavendeuxState", + json_args!(), + )?; + variables.clear(); + for k in state.keys() { + variables.insert(k.to_string(), state.get(k).unwrap().clone()); + } + + Ok(result) + } + Err(e) => Err(e), + } + }) + } +} diff --git a/src/extensions/js/extension.js b/src/extensions/js/extension.js new file mode 100644 index 0000000..359b8b5 --- /dev/null +++ b/src/extensions/js/extension.js @@ -0,0 +1,48 @@ +import { LavendeuxFunction } from 'ext:lavendeux/function.js'; +import { Types } from 'ext:lavendeux/value.js'; + +export class LavendeuxExtension { + constructor(properties) { + if (false && !['name', 'author', 'version'].every(k => { + properties.hasOwnProperty(k) + })) { + throw new Error("Properties given are missing one of ['name', 'author', 'version']"); + } + + this.properties = properties; + this.functions = {}; + this.decorators = {}; + } + + addFunction(name, callback, returns = 'Any') { + this.functions[name] = new LavendeuxFunction(name, 'function', callback, returns); + return this.functions[name]; + } + + addDecorator(name, callback, accepts = 'Any') { + this.decorators[name] = new LavendeuxFunction(name, 'decorator', callback, Types.String) + .requireArgument(accepts); + } + + export() { + let properties = { + 'function_definitions': {}, + 'decorator_definitions': {} + }; + Object.assign(properties, this.properties); + + for (const name in this.functions) { + properties.function_definitions[name] = this.functions[name].properties; + } + + for (const name in this.decorators) { + properties.decorator_definitions[name] = this.decorators[name].properties; + } + + return properties; + } + + name() { return this.properties.name; } + author() { return this.properties.author; } + version() { return this.properties.version; } +} \ No newline at end of file diff --git a/src/extensions/js/function.js b/src/extensions/js/function.js new file mode 100644 index 0000000..17506b6 --- /dev/null +++ b/src/extensions/js/function.js @@ -0,0 +1,65 @@ +import {LavendeuxValue, Types} from 'ext:lavendeux/value.js'; + +export class LavendeuxFunction { + constructor(name, type, callback, returns) { + this.callback = callback; + this.properties = { + 'fname': name, + 'ftype': type, + 'returns': returns, + 'argument_types': [] + }; + } + + requireArgument(type = 'Any') { + this.properties.argument_types.push(type); + return this; + } + + static unwrapLavendeuxFunctionArguments(expectedArgumentTypes, args) { + let types = args.map(a => LavendeuxValue.typeOf(a)); + if (expectedArgumentTypes.length > args.length) { + throw new Error(`function expected ${expectedArgumentTypes.length} parameters, but only received ${args.length}`); + } + for (const expectedTypeIndex in expectedArgumentTypes) { + let expectedType = expectedArgumentTypes[expectedTypeIndex]; + let actualType = types[expectedTypeIndex]; + + // No cooersion needed - the function does not care about type + if (expectedType == Types.Any) continue; + + // This case is not valid as only numeric types can be cooerced to to numeric + if ( + (expectedType == Types.Numeric && ![Types.Integer, Types.Float].includes(actualType)) || + ([Types.Integer, Types.Float].includes(expectedType) && expectedType != actualType) + ) { + throw new Error(`Argument ${expectedTypeIndex+1}: expected ${expectedType}, but received ${actualType}`); + } + } + + // In all other cases we can use type cooersion + return args.map((a,i) => LavendeuxValue.unwrap(a, expectedArgumentTypes[i])); + } + + static call(functionProperties, ...args) { + let state = getLavendeuxState(); + for (const key of Object.keys(state)) { + state[key] = LavendeuxValue.unwrap(state[key]); + } + + let js_args = LavendeuxFunction.unwrapLavendeuxFunctionArguments(functionProperties.argument_types, args); + let callback = lavendeux.retrieveFunction(functionProperties.fname, functionProperties.ftype); + + let value = LavendeuxValue.wrap( + callback(...js_args, state), + functionProperties.returns + ); + + for (const key of Object.keys(state)) { + state[key] = LavendeuxValue.wrap(state[key]); + } + setLavendeuxState(state); + + return value; + } +} \ No newline at end of file diff --git a/src/extensions/js/lavendeux.js b/src/extensions/js/lavendeux.js new file mode 100644 index 0000000..e51a5a8 --- /dev/null +++ b/src/extensions/js/lavendeux.js @@ -0,0 +1,58 @@ +import { LavendeuxFunction } from 'ext:lavendeux/function.js'; +import { LavendeuxExtension } from 'ext:lavendeux/extension.js'; +import { applyToGlobal, nonEnumerable } from 'ext:js_playground/js_playground.js'; + +class Lavendeux { + constructor() { + this.state = {}; + this.functionCache = { + 'function': {}, + 'decorator': {} + } + } + + storeFunction(name, type, callback) { + this.functionCache[type][name] = callback; + } + + retrieveFunction(name, type) { + return this.functionCache[type][name]; + } + + setState(s) { + this.state = s; + } + + getState() { + return this.state; + } + + extend(properties) { + return new LavendeuxExtension(properties); + } + + register(extension) { + globalThis._registered_lavendeux_extension = extension.export(); + let functions = Object.values(extension.functions); + for (const entry of functions) { + lavendeux.storeFunction(entry.properties.fname, entry.properties.ftype, entry.callback); + } + js_playground.register_entrypoint(() => globalThis._registered_lavendeux_extension); + } +} + +applyToGlobal({ + lavendeux: nonEnumerable( + new Lavendeux(), + ), + + setLavendeuxState: nonEnumerable( + (s) => globalThis.lavendeux.setState(s) + ), + getLavendeuxState: nonEnumerable( + () => globalThis.lavendeux.getState() + ), + callLavendeuxFunction: nonEnumerable( + (p, ...a) => LavendeuxFunction.call(p, ...a) + ), +}); \ No newline at end of file diff --git a/src/extensions/js/value.js b/src/extensions/js/value.js new file mode 100644 index 0000000..f356326 --- /dev/null +++ b/src/extensions/js/value.js @@ -0,0 +1,117 @@ +/** + * Valid types for function/decorator arguments + */ +export const Types = { + Float:"Float", Integer:"Integer", Numeric:"Numeric", + + // These can be converted from any type + String:"String", Boolean:"Boolean", Array:"Array", Object:"Object", + Any:"" +} + +/** + * A value for use with Lavendeux + */ +export class LavendeuxValue { + /** + * Determine the type of an incoming value + * @param {Object} wrappedValue + * @returns Type of the value given + */ + static typeOf(wrappedValue) { + let inType = Object.keys(wrappedValue); + if (inType.length) return inType[0]; + throw new Error('Received an invalid value from Lavendeux'); + } + + /** + * Cooerce a value to a given type + * Should mimic Lavendeux's type cooersion + * @param {Any} value + * @param {String} targetType Type to wrap as + * @returns New value + */ + static cooerce(value, targetType) { + switch (targetType) { + case 'Integer': return Math.floor( Number(value) ); + case 'Numeric': + case 'Float': + return Number(value); + case 'Boolean': return !!value; + case 'String': + if (typeof value === 'object') { + return JSON.stringify(value); + } else { + return `${value}`; + } + case 'Array': + if (Array.isArray(value)) { + return value; + } else if (typeof value === 'object') { + return Object.values(value); + } else { + return [value]; + } + case 'Object': + if (typeof value === 'object') { + return Object.assign({}, value); + } else { + return {0: value}; + } + default: return value; + } + } + + /** + * Return a raw value + * @param {Object} wrappedValue + * @returns value + */ + static unwrap(wrappedValue, targetType=Types.Any) { + let type = this.typeOf(wrappedValue); + let value = Object.values(wrappedValue)[0]; + switch (type) { + case 'Object': + value = value.map(([k,v]) => [ + this.unwrap(k, Types.String), + this.unwrap(v) + ]); + value = Object.fromEntries(value); + break; + case 'Array': + value = value.map(e => this.unwrap(e)); + break; + } + + return LavendeuxValue.cooerce(value, targetType); + } + + /** + * Wrap a value for returning to Lavendeux + * @param {Any} value + * @param {String} targetType Type to wrap as + * @returns Wrapped value + */ + static wrap(value, targetType=Types.Any) { + value = this.cooerce(value, targetType); + + if (Array.isArray(value)) { + return {'Array': value.map(e => this.wrap(e))} + } else if (typeof value === 'object') { + let result = []; + Object.keys(value).forEach(k => { + result.push([ + this.wrap(k), + this.wrap(value[k]) + ]) + }); + return {'Object': result}; + } else if (typeof value === 'string' || value instanceof String) { + return {'String': value}; + } else if (Number.isInteger(value)) { + return {'Integer': value}; + } else if (Number(value) === value) { + return {'Float': value}; + } else return {'Boolean': value == true}; + } +} diff --git a/src/extensions/mod.rs b/src/extensions/mod.rs new file mode 100644 index 0000000..63c77a4 --- /dev/null +++ b/src/extensions/mod.rs @@ -0,0 +1,7 @@ +mod extension; +mod function; +mod runtime; +mod table; + +pub use extension::Extension; +pub use table::ExtensionTable; diff --git a/src/extensions/runtime.rs b/src/extensions/runtime.rs new file mode 100644 index 0000000..3e4a567 --- /dev/null +++ b/src/extensions/runtime.rs @@ -0,0 +1,103 @@ +use core::time::Duration; +use js_playground::deno_core::extension; +use js_playground::{json_args, FunctionArguments, Module, ModuleHandle, Runtime, RuntimeOptions}; +use once_cell::sync::OnceCell; +use std::cell::RefCell; + +use super::extension::Extension; + +// Create a thread-local version of the runtime +// This should allow the following to be enforced: +// - Runtime is not sent between threads +// - Runtime is only initialized once +// - Runtime is never accessed concurrently +thread_local! { + static RUNTIME_CELL: OnceCell> = OnceCell::new(); +} + +extension!( + lavendeux, + esm_entry_point = "ext:lavendeux/lavendeux.js", + esm = [ + dir "src/extensions/js", "extension.js", "function.js", "value.js", "lavendeux.js", + ], +); + +const SCRIPT_TIMEOUT: u64 = 1000; +pub struct ExtensionsRuntime(Runtime); +impl ExtensionsRuntime { + fn new() -> Self { + Self( + Runtime::new(RuntimeOptions { + timeout: Duration::from_millis(SCRIPT_TIMEOUT), + default_entrypoint: Some("extension".to_string()), + extensions: vec![lavendeux::init_ops_and_esm()], + }) + .expect("could not create a JS runtime for extensions"), + ) + } + + /// Perform an operation on the runtime instance + /// Will return T if we can get access to the runtime + /// or panic went wrong + pub fn with T>(mut callback: F) -> T { + RUNTIME_CELL.with(|once_lock| { + let rt_mut = once_lock.get_or_init(|| RefCell::new(ExtensionsRuntime::new())); + let mut runtime = rt_mut.borrow_mut(); + runtime.reset(); + callback(&mut runtime) + }) + } + + pub fn reset(&mut self) { + self.0.reset() + } + + pub fn load_module(&mut self, module: &Module) -> Result { + self.0.load_module(module) + } + + pub fn call_function( + &mut self, + context: &ModuleHandle, + function: &str, + args: &FunctionArguments, + ) -> Result + where + T: serde::de::DeserializeOwned, + { + self.0.call_function(&context, function, args) + } + + pub fn load_extension(path: &str) -> Result { + let module = Module::load(path)?; + ExtensionsRuntime::with(|runtime| runtime.get_extension_from_module(&module)) + } + + pub fn load_extensions(dir: &str) -> Vec> { + match Module::load_dir(dir) { + Ok(modules) => { + let mut results: Vec> = Vec::new(); + for module in modules { + let extension = ExtensionsRuntime::with(|runtime| { + runtime.get_extension_from_module(&module) + }); + results.push(extension); + } + + results + } + Err(e) => vec![Err(e.into())], + } + } + + fn get_extension_from_module( + &mut self, + module: &Module, + ) -> Result { + let context = self.0.load_module(module)?; + let mut extension: Extension = self.0.call_entrypoint(&context, json_args!())?; + extension.module = module.clone(); + Ok(extension) + } +} diff --git a/src/extensions/table.rs b/src/extensions/table.rs new file mode 100644 index 0000000..d2445e0 --- /dev/null +++ b/src/extensions/table.rs @@ -0,0 +1,133 @@ +use crate::{Error, Token, Value}; + +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +use crate::extensions::extension::Extension; +use crate::extensions::runtime::ExtensionsRuntime; + +/// Holds a set of registered extensions +#[derive(Deserialize, Serialize, Clone)] +pub struct ExtensionTable(HashMap); +impl ExtensionTable { + /// Create a new empty table + pub fn new() -> Self { + Self(HashMap::new()) + } + + /// Add an extension + /// + /// # Arguments + /// * `filename` - File name + /// * `extension` - Extension to add + pub fn add(&mut self, filename: &str, extension: Extension) { + self.0.insert(filename.to_string(), extension); + } + + /// Load an extension from a filename + /// + /// # Arguments + /// * `filename` - File name + pub fn load(&mut self, filename: &str) -> Result { + let e = ExtensionsRuntime::load_extension(filename)?; + self.0.insert(filename.to_string(), e.clone()); + Ok(e) + } + + /// Attempt to load all extensions in a directory + pub fn load_all(&mut self, path: &str) -> Vec> { + let e = ExtensionsRuntime::load_extensions(path); + self.0.clear(); + for extension in e.iter().flatten() { + self.0 + .insert(extension.filename().to_string(), extension.clone()); + } + e + } + + /// Delete an extension + pub fn remove(&mut self, filename: &str) { + self.0.remove(filename); + } + + /// Returns the full list of extensions available + pub fn all(&mut self) -> Vec<&mut Extension> { + let mut a = Vec::from_iter(self.0.values_mut()); + a.sort_by(|f1, f2| f1.name().cmp(f2.name())); + a + } + + /// Determine if a function exists in the extension + /// + /// # Arguments + /// * `name` - Function name + pub fn has_function(&mut self, name: &str) -> bool { + for extension in self.all() { + if extension.has_function(name) { + return true; + } + } + false + } + + /// Try to call a function in the loaded extensions + pub fn call_function( + &mut self, + name: &str, + token: &Token, + args: &[Value], + variables: &mut HashMap, + ) -> Result { + for extension in self.all() { + if extension.has_function(name) { + return match extension.call_function(name, args, variables) { + Ok(value) => Ok(value), + Err(e) => Err(Error::Javascript(e, token.clone())), + }; + } + } + Err(Error::FunctionName { + name: name.to_string(), + token: token.clone(), + }) + } + + /// Determine if a decorator exists in the extension + /// + /// # Arguments + /// * `name` - Decorator name + pub fn has_decorator(&mut self, name: &str) -> bool { + for extension in self.all() { + if extension.has_decorator(name) { + return true; + } + } + false + } + + /// Try to call a decorator in the loaded extensions + pub fn call_decorator( + &mut self, + name: &str, + token: &Token, + variables: &mut HashMap, + ) -> Result { + for extension in self.all() { + if extension.has_decorator(name) { + return match extension.call_decorator(name, token, variables) { + Ok(value) => Ok(value), + Err(e) => Err(Error::Javascript(e, token.clone())), + }; + } + } + Err(Error::DecoratorName { + name: format!("@{}", name), + token: token.clone(), + }) + } +} +impl Default for ExtensionTable { + fn default() -> Self { + Self::new() + } +} diff --git a/src/functions/builtins/api.rs b/src/functions/builtins/api.rs index 53bae22..d869f18 100644 --- a/src/functions/builtins/api.rs +++ b/src/functions/builtins/api.rs @@ -1,10 +1,10 @@ //! Builtin functions for API manipulation -use crate::{network::ApiInstance, Value}; use super::*; +use crate::{network::ApiInstance, ExpectedTypes, Value}; use std::collections::HashMap; -const LIST : FunctionDefinition = FunctionDefinition { +const LIST: FunctionDefinition = FunctionDefinition { name: "api_list", category: Some("network"), description: "List all registered APIs", @@ -12,27 +12,31 @@ const LIST : FunctionDefinition = FunctionDefinition { handler: |_function, _token, state, _args| { let mut keys = state.apis.keys().collect::>(); keys.sort(); - - let definitions = keys.iter().map(|k| format!("{}: {}", k, state.apis.get(*k).unwrap())); + + let definitions = keys + .iter() + .map(|k| format!("{}: {}", k, state.apis.get(*k).unwrap())); let t = definitions.collect::>().join("\n"); - + Ok(Value::String(t)) - } + }, }; -const REGISTER : FunctionDefinition = FunctionDefinition { +const REGISTER: FunctionDefinition = FunctionDefinition { name: "api_register", category: Some("network"), description: "Register a new API for quick usage", - arguments: || vec![ - FunctionArgument::new_required("name", ExpectedTypes::String), - FunctionArgument::new_required("base_url", ExpectedTypes::String), - FunctionArgument::new_optional("api_key", ExpectedTypes::String), - ], + arguments: || { + vec![ + FunctionArgument::new_required("name", ExpectedTypes::String), + FunctionArgument::new_required("base_url", ExpectedTypes::String), + FunctionArgument::new_optional("api_key", ExpectedTypes::String), + ] + }, handler: |_function, token, state, args| { let name = args.get("name").required().as_string(); let base_url = args.get("base_url").required().as_string(); - + let mut instance = ApiInstance::new(base_url); if let Some(s) = args.get("api_key").optional() { instance.set_key(s.as_string()); @@ -42,54 +46,63 @@ const REGISTER : FunctionDefinition = FunctionDefinition { let list = LIST.call(token, state, &[]).unwrap().as_string(); Ok(Value::String(list)) - } + }, }; -const DELETE : FunctionDefinition = FunctionDefinition { +const DELETE: FunctionDefinition = FunctionDefinition { name: "api_delete", category: Some("network"), description: "Remove a registered API from the list", - arguments: || vec![ - FunctionArgument::new_required("name", ExpectedTypes::String) - ], + arguments: || { + vec![FunctionArgument::new_required( + "name", + ExpectedTypes::String, + )] + }, handler: |_function, token, state, args| { let name = args.get("name").required().as_string(); state.apis.remove(&name); - + let list = LIST.call(token, state, &[]).unwrap().as_string(); Ok(Value::String(list)) - } + }, }; -const CALL : FunctionDefinition = FunctionDefinition { +const CALL: FunctionDefinition = FunctionDefinition { name: "api", category: Some("network"), description: "Make a call to a registered API", - arguments: || vec![ - FunctionArgument::new_required("name", ExpectedTypes::String), - FunctionArgument::new_optional("endpoint", ExpectedTypes::String) - ], + arguments: || { + vec![ + FunctionArgument::new_required("name", ExpectedTypes::String), + FunctionArgument::new_optional("endpoint", ExpectedTypes::String), + ] + }, handler: |_function, token, state, args| { let api_name = args.get("name").required().as_string(); - let endpoint = args.get("endpoint").optional_or(Value::String("".to_string())).as_string(); + let endpoint = args + .get("endpoint") + .optional_or(Value::String("".to_string())) + .as_string(); match state.apis.get(&api_name) { Some(api) => { - match api.request(&endpoint, None, HashMap::from([("Accept".to_string(),"text/plain".to_string())])) { - Ok(result) => { - Ok(Value::String(result.as_string())) - }, - Err(e) => { - Err(NetworkError::from_reqwesterror(token, e).into()) - } + match api.request( + &endpoint, + None, + HashMap::from([("Accept".to_string(), "text/plain".to_string())]), + ) { + Ok(result) => Ok(Value::String(result.as_string())), + Err(e) => Err(Error::Network(e, token.clone())), } - }, - - None => { - Err(IOError::new(token, "API {} was not found. Add it with api_register(name, base_url, [optional api key])").into()) } + + None => Err(Error::UnknownApi { + name: api_name, + token: token.clone(), + }), } - } + }, }; /// Register api functions @@ -104,12 +117,17 @@ pub fn register_functions(table: &mut FunctionTable) { mod test_builtin_functions { use super::*; - fn hardy_net_test(test: fn() -> Result) -> Value { - let results = [ - test(), test(), test(), test(), test() - ]; + fn hardy_net_test(test: fn() -> Result) -> Value { + let results = [test(), test(), test(), test(), test()]; assert_eq!(true, results.iter().filter(|r| r.is_ok()).count() > 0); - return results.iter().filter(|r| r.is_ok()).next().unwrap().as_ref().unwrap().clone() + return results + .iter() + .filter(|r| r.is_ok()) + .next() + .unwrap() + .as_ref() + .unwrap() + .clone(); } #[test] @@ -120,13 +138,20 @@ mod test_builtin_functions { assert_eq!(false, state.apis.contains_key(&name)); - assert_eq!(true, REGISTER.call(&Token::dummy(""), &mut state, &[ - Value::String(name.clone()), - Value::String(url) - ]).unwrap().as_string().contains(&name)); + assert_eq!( + true, + REGISTER + .call( + &Token::dummy(""), + &mut state, + &[Value::String(name.clone()), Value::String(url)] + ) + .unwrap() + .as_string() + .contains(&name) + ); assert_eq!(true, state.apis.contains_key(&name)); - } #[test] @@ -136,12 +161,20 @@ mod test_builtin_functions { assert_eq!(true, state.apis.contains_key(&name)); - assert_eq!(false, DELETE.call(&Token::dummy(""), &mut state, &[ - Value::String(name.clone()) - ]).unwrap().as_string().contains(&name)); + assert_eq!( + false, + DELETE + .call( + &Token::dummy(""), + &mut state, + &[Value::String(name.clone())] + ) + .unwrap() + .as_string() + .contains(&name) + ); assert_eq!(false, state.apis.contains_key(&name)); - } #[test] @@ -149,21 +182,33 @@ mod test_builtin_functions { let mut state = ParserState::new(); let name = "dictionary".to_string(); - assert_eq!(true, LIST.call(&Token::dummy(""), &mut state, &[ - ]).unwrap().as_string().contains(&name)); - + assert_eq!( + true, + LIST.call(&Token::dummy(""), &mut state, &[]) + .unwrap() + .as_string() + .contains(&name) + ); } #[test] - fn test_call() { - assert_eq!(true, hardy_net_test(|| { - let mut state = ParserState::new(); - let name = "dictionary".to_string(); - return CALL.call(&Token::dummy(""), &mut state, &[ - Value::String(name.clone()), - Value::String("en/fart".to_string()) - ]); - }).as_string().contains("the anus")); - + fn test_call() { + assert_eq!( + true, + hardy_net_test(|| { + let mut state = ParserState::new(); + let name = "dictionary".to_string(); + return CALL.call( + &Token::dummy(""), + &mut state, + &[ + Value::String(name.clone()), + Value::String("en/fart".to_string()), + ], + ); + }) + .as_string() + .contains("the anus") + ); } -} \ No newline at end of file +} diff --git a/src/functions/builtins/array.rs b/src/functions/builtins/array.rs index ba560b8..8f595b4 100644 --- a/src/functions/builtins/array.rs +++ b/src/functions/builtins/array.rs @@ -1,219 +1,259 @@ //! Builtin functions for array manipulation use super::*; -use crate::value::{Value, IntegerType, ArrayType}; +use crate::{ + value::{ArrayType, IntegerType, Value}, + ExpectedTypes, +}; -const LEN : FunctionDefinition = FunctionDefinition { +const LEN: FunctionDefinition = FunctionDefinition { name: "len", category: Some("arrays"), description: "Returns the length of the given array or object", - arguments: || vec![ - FunctionArgument::new_required("input", ExpectedTypes::Array) - ], + arguments: || { + vec![FunctionArgument::new_required( + "input", + ExpectedTypes::Array, + )] + }, handler: |_function, _token, _state, args| { - Ok(Value::Integer( - match args.get("input").required() { - Value::Object(v) => v.keys().len() as IntegerType, - _ => args.get("input").required().as_array().len() as IntegerType - } - )) - } + Ok(Value::Integer(match args.get("input").required() { + Value::Object(v) => v.keys().len() as IntegerType, + _ => args.get("input").required().as_array().len() as IntegerType, + })) + }, }; -const IS_EMPTY : FunctionDefinition = FunctionDefinition { +const IS_EMPTY: FunctionDefinition = FunctionDefinition { name: "is_empty", category: Some("arrays"), description: "Returns true if the given array or object is empty", - arguments: || vec![ - FunctionArgument::new_required("input", ExpectedTypes::Array) - ], + arguments: || { + vec![FunctionArgument::new_required( + "input", + ExpectedTypes::Array, + )] + }, handler: |_function, _token, _state, args| { - Ok(Value::Boolean( - match args.get("input").required() { - Value::Object(v) => v.is_empty(), - _ => args.get("input").required().as_array().is_empty() - } - )) - } + Ok(Value::Boolean(match args.get("input").required() { + Value::Object(v) => v.is_empty(), + _ => args.get("input").required().as_array().is_empty(), + })) + }, }; -const POP : FunctionDefinition = FunctionDefinition { +const POP: FunctionDefinition = FunctionDefinition { name: "pop", category: Some("arrays"), description: "Remove the last element from an array", - arguments: || vec![ - FunctionArgument::new_required("array", ExpectedTypes::Array) - ], + arguments: || { + vec![FunctionArgument::new_required( + "array", + ExpectedTypes::Array, + )] + }, handler: |_function, token, _state, args| { let mut e = args.get("array").required().as_array(); if e.is_empty() { - Err(ArrayEmptyError::new(token).into()) + Err(Error::ArrayEmpty(token.clone())) } else { e.pop(); Ok(Value::Array(e)) } - } + }, }; -const PUSH : FunctionDefinition = FunctionDefinition { +const PUSH: FunctionDefinition = FunctionDefinition { name: "push", category: Some("arrays"), description: "Add an element to the end of an array", - arguments: || vec![ - FunctionArgument::new_required("array", ExpectedTypes::Array), - FunctionArgument::new_required("element", ExpectedTypes::Any) - ], + arguments: || { + vec![ + FunctionArgument::new_required("array", ExpectedTypes::Array), + FunctionArgument::new_required("element", ExpectedTypes::Any), + ] + }, handler: |_function, _token, _state, args| { let mut e = args.get("array").required().as_array(); e.push(args.get("element").required()); Ok(Value::Array(e)) - } + }, }; -const DEQUEUE : FunctionDefinition = FunctionDefinition { +const DEQUEUE: FunctionDefinition = FunctionDefinition { name: "dequeue", category: Some("arrays"), description: "Remove the first element from an array", - arguments: || vec![ - FunctionArgument::new_required("array", ExpectedTypes::Array) - ], + arguments: || { + vec![FunctionArgument::new_required( + "array", + ExpectedTypes::Array, + )] + }, handler: |_function, token, _state, args| { let mut e = args.get("array").required().as_array(); if e.is_empty() { - Err(ArrayEmptyError::new(token).into()) + Err(Error::ArrayEmpty(token.clone())) } else { e.remove(0); Ok(Value::Array(e)) } - } + }, }; -const ENQUEUE : FunctionDefinition = FunctionDefinition { +const ENQUEUE: FunctionDefinition = FunctionDefinition { name: "enqueue", category: Some("arrays"), description: "Add an element to the end of an array", - arguments: || vec![ - FunctionArgument::new_required("array", ExpectedTypes::Array), - FunctionArgument::new_required("element", ExpectedTypes::Any) - ], + arguments: || { + vec![ + FunctionArgument::new_required("array", ExpectedTypes::Array), + FunctionArgument::new_required("element", ExpectedTypes::Any), + ] + }, handler: |_function, _token, _state, args| { let mut e = args.get("array").required().as_array(); e.push(args.get("element").required()); Ok(Value::Array(e)) - } + }, }; -const REMOVE : FunctionDefinition = FunctionDefinition { +const REMOVE: FunctionDefinition = FunctionDefinition { name: "remove", category: Some("arrays"), description: "Removes an element from an array", - arguments: || vec![ - FunctionArgument::new_required("input", ExpectedTypes::Array), - FunctionArgument::new_required("index", ExpectedTypes::Int) - ], + arguments: || { + vec![ + FunctionArgument::new_required("input", ExpectedTypes::Array), + FunctionArgument::new_required("index", ExpectedTypes::Int), + ] + }, handler: |_function, token, _state, args| { - match args.get("input").required() { + let input = args.get("input").required(); + let index = args.get("index").required(); + + match input { Value::Object(mut v) => { - v.remove(&args.get("index").required()); - Ok(args.get("input").required()) - }, + v.remove(&index); + Ok(Value::from(v)) + } _ => { - let mut a = args.get("input").required().as_array(); - let idx = args.get("index").required().as_int().unwrap(); + let mut a = input.as_array(); + let idx = index.as_int().unwrap(); if idx < 0 || idx >= a.len() as IntegerType { - Err(ArrayIndexError::new(token, idx as usize).into()) + Err(Error::Index { + key: index, + token: token.clone(), + }) } else { a.remove(idx as usize); Ok(Value::Array(a)) } } } - } + }, }; -const ELEMENT : FunctionDefinition = FunctionDefinition { +const ELEMENT: FunctionDefinition = FunctionDefinition { name: "element", category: Some("arrays"), description: "Return an element from a location in an array or object", - arguments: || vec![ - FunctionArgument::new_required("input", ExpectedTypes::Array), - FunctionArgument::new_required("index", ExpectedTypes::Int) - ], + arguments: || { + vec![ + FunctionArgument::new_required("input", ExpectedTypes::Array), + FunctionArgument::new_required("index", ExpectedTypes::Int), + ] + }, handler: |_function, token, _state, args| { - match args.get("input").required() { - Value::Object(v) => { - match v.get(&args.get("index").required()) { - None => Err(ObjectKeyError::new(token, &args.get("index").required().as_string()).into()), - Some(v) => Ok(v.clone()) - } + let input = args.get("input").required(); + let index = args.get("index").required(); + + match input { + Value::Object(v) => match v.get(&index) { + None => Err(Error::Index { + key: index, + token: token.clone(), + }), + Some(v) => Ok(v.clone()), }, _ => { - let a = args.get("input").required().as_array(); - let idx = args.get("index").required().as_int().unwrap(); + let a = input.as_array(); + let idx = index.as_int().unwrap(); if idx < 0 || idx > a.len() as IntegerType { - Err(ArrayIndexError::new(token, idx as usize).into()) + Err(Error::Index { + key: index, + token: token.clone(), + }) } else { Ok(a[idx as usize].clone()) } } } - } + }, }; -const MERGE : FunctionDefinition = FunctionDefinition { +const MERGE: FunctionDefinition = FunctionDefinition { name: "merge", category: Some("arrays"), description: "Merge all given arrays or objects", - arguments: || vec![ - FunctionArgument::new("target", ExpectedTypes::Any, false), - FunctionArgument::new_plural("inputs", ExpectedTypes::Any, false) - ], - handler: |_function, _token, _state, args| { - match args.get("target").required() { - Value::Object(mut v) => { - for arg in args.get("inputs").plural() { - v.extend(arg.as_object()); - } - Ok(Value::Object(v)) - }, + arguments: || { + vec![ + FunctionArgument::new("target", ExpectedTypes::Any, false), + FunctionArgument::new_plural("inputs", ExpectedTypes::Any, false), + ] + }, + handler: |_function, _token, _state, args| match args.get("target").required() { + Value::Object(mut v) => { + for arg in args.get("inputs").plural() { + v.extend(arg.as_object()); + } + Ok(Value::Object(v)) + } - _ => { - let mut result : ArrayType = args.get("target").required().as_array(); - for arg in args.get("inputs").plural() { - result.append(&mut arg.as_array()); - } - Ok(Value::Array(result)) + _ => { + let mut result: ArrayType = args.get("target").required().as_array(); + for arg in args.get("inputs").plural() { + result.append(&mut arg.as_array()); } + Ok(Value::Array(result)) } - } + }, }; -const KEYS : FunctionDefinition = FunctionDefinition { +const KEYS: FunctionDefinition = FunctionDefinition { name: "keys", category: Some("arrays"), description: "Get a list of keys in the object or array", - arguments: || vec![ - FunctionArgument::new("input", ExpectedTypes::Any, false) - ], + arguments: || vec![FunctionArgument::new("input", ExpectedTypes::Any, false)], handler: |_function, _token, _state, args| { - let mut a = args.get("input").required().as_object().keys().cloned().collect::(); + let mut a = args + .get("input") + .required() + .as_object() + .keys() + .cloned() + .collect::(); a.sort(); Ok(Value::Array(a)) - } + }, }; -const VALUES : FunctionDefinition = FunctionDefinition { +const VALUES: FunctionDefinition = FunctionDefinition { name: "values", category: Some("arrays"), description: "Get a list of values in the object or array", - arguments: || vec![ - FunctionArgument::new("input", ExpectedTypes::Any, false) - ], + arguments: || vec![FunctionArgument::new("input", ExpectedTypes::Any, false)], handler: |_function, _token, _state, args| { - let mut a = args.get("input").required().as_object().values().cloned().collect::(); + let mut a = args + .get("input") + .required() + .as_object() + .values() + .cloned() + .collect::(); a.sort(); Ok(Value::Array(a)) - } + }, }; /// Register array functions @@ -241,162 +281,243 @@ mod test_builtin_functions { fn test_len() { let mut state = ParserState::new(); - assert_eq!(Value::Integer(1), LEN.call(&Token::dummy(""), &mut state, &[ - Value::Array(vec![ - Value::Integer(5), - ]) - ]).unwrap()); - assert_eq!(Value::Integer(3), LEN.call(&Token::dummy(""), &mut state, &[ - Value::Array(vec![ - Value::Integer(5), - Value::Float(2.0), - Value::String("test".to_string()) - ]) - ]).unwrap()); + assert_eq!( + Value::Integer(1), + LEN.call( + &Token::dummy(""), + &mut state, + &[Value::Array(vec![Value::Integer(5),])] + ) + .unwrap() + ); + assert_eq!( + Value::Integer(3), + LEN.call( + &Token::dummy(""), + &mut state, + &[Value::Array(vec![ + Value::Integer(5), + Value::Float(2.0), + Value::String("test".to_string()) + ])] + ) + .unwrap() + ); } #[test] fn test_is_empty() { let mut state = ParserState::new(); - assert_eq!(Value::Boolean(false), IS_EMPTY.call(&Token::dummy(""), &mut state, &[ - Value::Array(vec![ - Value::Integer(5), - ]) - ]).unwrap()); - assert_eq!(Value::Boolean(true), IS_EMPTY.call(&Token::dummy(""), &mut state, &[ - Value::Array(vec![ - ]) - ]).unwrap()); + assert_eq!( + Value::Boolean(false), + IS_EMPTY + .call( + &Token::dummy(""), + &mut state, + &[Value::Array(vec![Value::Integer(5),])] + ) + .unwrap() + ); + assert_eq!( + Value::Boolean(true), + IS_EMPTY + .call(&Token::dummy(""), &mut state, &[Value::Array(vec![])]) + .unwrap() + ); } #[test] fn test_pop() { let mut state = ParserState::new(); - assert_eq!(Value::Array(vec![ - Value::Integer(5) - ]), POP.call(&Token::dummy(""), &mut state, &[ - Value::Array(vec![ - Value::Integer(5), Value::Integer(3), - ]) - ]).unwrap()); + assert_eq!( + Value::Array(vec![Value::Integer(5)]), + POP.call( + &Token::dummy(""), + &mut state, + &[Value::Array(vec![Value::Integer(5), Value::Integer(3),])] + ) + .unwrap() + ); } #[test] fn test_push() { let mut state = ParserState::new(); - assert_eq!(Value::Array(vec![ - Value::Integer(5), Value::Integer(3), - ]), PUSH.call(&Token::dummy(""), &mut state, &[ - Value::Array(vec![ - Value::Integer(5), - ]), Value::Integer(3) - ]).unwrap()); + assert_eq!( + Value::Array(vec![Value::Integer(5), Value::Integer(3),]), + PUSH.call( + &Token::dummy(""), + &mut state, + &[Value::Array(vec![Value::Integer(5),]), Value::Integer(3)] + ) + .unwrap() + ); } #[test] fn test_dequeue() { let mut state = ParserState::new(); - assert_eq!(Value::Array(vec![ - Value::Integer(3), - ]), DEQUEUE.call(&Token::dummy(""), &mut state, &[ - Value::Array(vec![ - Value::Integer(5), Value::Integer(3), - ]) - ]).unwrap()); + assert_eq!( + Value::Array(vec![Value::Integer(3),]), + DEQUEUE + .call( + &Token::dummy(""), + &mut state, + &[Value::Array(vec![Value::Integer(5), Value::Integer(3),])] + ) + .unwrap() + ); } #[test] fn test_enqueue() { let mut state = ParserState::new(); - assert_eq!(Value::Array(vec![ - Value::Integer(5), Value::Integer(3), - ]), ENQUEUE.call(&Token::dummy(""), &mut state, &[ - Value::Array(vec![ - Value::Integer(5), - ]), Value::Integer(3) - ]).unwrap()); + assert_eq!( + Value::Array(vec![Value::Integer(5), Value::Integer(3),]), + ENQUEUE + .call( + &Token::dummy(""), + &mut state, + &[Value::Array(vec![Value::Integer(5),]), Value::Integer(3)] + ) + .unwrap() + ); } #[test] fn test_remove() { let mut state = ParserState::new(); - assert_eq!(Value::Array(vec![Value::Integer(3)]), REMOVE.call(&Token::dummy(""), &mut state, &[ - Value::Array(vec![ - Value::Integer(5), Value::Integer(3), - ]), - Value::Integer(0) - ]).unwrap()); - assert_eq!(Value::Array(vec![Value::Integer(5)]), REMOVE.call(&Token::dummy(""), &mut state, &[ - Value::Array(vec![ - Value::Integer(5), Value::Integer(3), - ]), - Value::Integer(1) - ]).unwrap()); - assert_eq!(true, REMOVE.call(&Token::dummy(""), &mut state, &[ - Value::Array(vec![ - Value::Integer(5), Value::Integer(3), - ]), - Value::Integer(2) - ]).is_err()); + assert_eq!( + Value::Array(vec![Value::Integer(3)]), + REMOVE + .call( + &Token::dummy(""), + &mut state, + &[ + Value::Array(vec![Value::Integer(5), Value::Integer(3),]), + Value::Integer(0) + ] + ) + .unwrap() + ); + assert_eq!( + Value::Array(vec![Value::Integer(5)]), + REMOVE + .call( + &Token::dummy(""), + &mut state, + &[ + Value::Array(vec![Value::Integer(5), Value::Integer(3),]), + Value::Integer(1) + ] + ) + .unwrap() + ); + assert_eq!( + true, + REMOVE + .call( + &Token::dummy(""), + &mut state, + &[ + Value::Array(vec![Value::Integer(5), Value::Integer(3),]), + Value::Integer(2) + ] + ) + .is_err() + ); } #[test] fn test_element() { let mut state = ParserState::new(); - assert_eq!(Value::Integer(3), ELEMENT.call(&Token::dummy(""), &mut state, &[ - Value::Array(vec![ - Value::Integer(5), - Value::Integer(3), - ]), - Value::Integer(1) - ]).unwrap()); + assert_eq!( + Value::Integer(3), + ELEMENT + .call( + &Token::dummy(""), + &mut state, + &[ + Value::Array(vec![Value::Integer(5), Value::Integer(3),]), + Value::Integer(1) + ] + ) + .unwrap() + ); } #[test] fn test_merge() { let mut state = ParserState::new(); - assert_eq!(Value::Array( - vec![Value::Integer(1), Value::Integer(2), Value::Integer(3), Value::Integer(4)]), - MERGE.call(&Token::dummy(""), &mut state, &[ - Value::Array(vec![Value::Integer(1)]), - Value::Array(vec![Value::Integer(2), Value::Integer(3)]), + assert_eq!( + Value::Array(vec![ + Value::Integer(1), + Value::Integer(2), + Value::Integer(3), Value::Integer(4) - ]).unwrap()); + ]), + MERGE + .call( + &Token::dummy(""), + &mut state, + &[ + Value::Array(vec![Value::Integer(1)]), + Value::Array(vec![Value::Integer(2), Value::Integer(3)]), + Value::Integer(4) + ] + ) + .unwrap() + ); } #[test] fn test_keys() { let mut state = ParserState::new(); - assert_eq!(Value::Array( - vec![Value::Integer(1), Value::String("2".to_string())]), - KEYS.call(&Token::dummy(""), &mut state, &[ - Value::Object(HashMap::from([ + assert_eq!( + Value::Array(vec![Value::Integer(1), Value::String("2".to_string())]), + KEYS.call( + &Token::dummy(""), + &mut state, + &[Value::Object(HashMap::from([ (Value::Integer(1), Value::Integer(3)), - (Value::String("2".to_string()), Value::String("4".to_string())), - ])) - ]).unwrap()); + ( + Value::String("2".to_string()), + Value::String("4".to_string()) + ), + ]))] + ) + .unwrap() + ); } #[test] fn test_values() { let mut state = ParserState::new(); - assert_eq!(Value::Array( - vec![Value::Integer(3), Value::String("4".to_string())]), - VALUES.call(&Token::dummy(""), &mut state, &[ - Value::Object(HashMap::from([ - (Value::Integer(1), Value::Integer(3)), - (Value::String("2".to_string()), Value::String("4".to_string())), - ])) - ]).unwrap()); + assert_eq!( + Value::Array(vec![Value::Integer(3), Value::String("4".to_string())]), + VALUES + .call( + &Token::dummy(""), + &mut state, + &[Value::Object(HashMap::from([ + (Value::Integer(1), Value::Integer(3)), + ( + Value::String("2".to_string()), + Value::String("4".to_string()) + ), + ]))] + ) + .unwrap() + ); } -} \ No newline at end of file +} diff --git a/src/functions/builtins/crypto.rs b/src/functions/builtins/crypto.rs index 06dbc0e..f4485cc 100644 --- a/src/functions/builtins/crypto.rs +++ b/src/functions/builtins/crypto.rs @@ -1,19 +1,17 @@ //! Builtin cryptographic functions use super::*; - +use crate::{define_function, ExpectedTypes}; use rand::prelude::*; #[cfg(feature = "crypto-functions")] -const SHA256 : FunctionDefinition = FunctionDefinition { - name: "sha256", - category: Some("cryptography"), - description: "Returns the SHA256 hash of a given string", - arguments: || vec![ - FunctionArgument::new_plural("input", ExpectedTypes::Any, false) - ], - handler: |_function, _token, _state, args| { - use sha2::{Sha256, Digest}; +define_function!( + name = sha256, + category = "cryptography", + description = "Returns the SHA256 hash of a given string", + arguments = [function_arg!("plural", "input":Any)], + handler = |function, token, state, args| { + use sha2::{Digest, Sha256}; let input = args.get("input").required().as_string(); let mut hasher = Sha256::new(); @@ -22,18 +20,22 @@ const SHA256 : FunctionDefinition = FunctionDefinition { let s = format!("{:X}", hasher.finalize()); Ok(Value::String(s)) } -}; +); #[cfg(feature = "crypto-functions")] -const MD5 : FunctionDefinition = FunctionDefinition { +const MD5: FunctionDefinition = FunctionDefinition { name: "md5", category: Some("cryptography"), description: "Returns the MD5 hash of a given string", - arguments: || vec![ - FunctionArgument::new_plural("input", ExpectedTypes::Any, false) - ], + arguments: || { + vec![FunctionArgument::new_plural( + "input", + ExpectedTypes::Any, + false, + )] + }, handler: |_function, _token, _state, args| { - use md5::{Md5, Digest}; + use md5::{Digest, Md5}; let input = args.get("input").required().as_string(); let mut hasher = Md5::new(); @@ -41,21 +43,25 @@ const MD5 : FunctionDefinition = FunctionDefinition { let s = format!("{:X}", hasher.finalize()); Ok(Value::String(s)) - } + }, }; -const CHOOSE : FunctionDefinition = FunctionDefinition { +const CHOOSE: FunctionDefinition = FunctionDefinition { name: "choose", category: Some("cryptography"), description: "Returns any one of the provided arguments at random", - arguments: || vec![ - FunctionArgument::new_plural("option", ExpectedTypes::Any, false) - ], + arguments: || { + vec![FunctionArgument::new_plural( + "option", + ExpectedTypes::Any, + false, + )] + }, handler: |_function, _token, _state, args| { let mut rng = rand::thread_rng(); let arg = rng.gen_range(0..args.len()); Ok(args[arg].clone()) - } + }, }; const RAND : FunctionDefinition = FunctionDefinition { @@ -85,7 +91,7 @@ const RAND : FunctionDefinition = FunctionDefinition { /// Register developper functions pub fn register_functions(table: &mut FunctionTable) { #[cfg(feature = "crypto-functions")] - table.register(SHA256); + table.register(sha256); #[cfg(feature = "crypto-functions")] table.register(MD5); @@ -97,42 +103,64 @@ pub fn register_functions(table: &mut FunctionTable) { #[cfg(test)] mod test_builtin_table { use super::*; - + #[cfg(feature = "crypto-functions")] #[test] fn test_sha256() { let mut state = ParserState::new(); - let result = SHA256.call(&Token::dummy(""), &mut state, &[ - Value::String("foobar".to_string()) - ]).unwrap().as_string(); - - assert_eq!("C3AB8FF13720E8AD9047DD39466B3C8974E592C2FA383D4A3960714CAEF0C4F2".to_string(), result); + let result = sha256 + .call( + &Token::dummy(""), + &mut state, + &[Value::String("foobar".to_string())], + ) + .unwrap() + .as_string(); + + assert_eq!( + "C3AB8FF13720E8AD9047DD39466B3C8974E592C2FA383D4A3960714CAEF0C4F2".to_string(), + result + ); } - + #[cfg(feature = "crypto-functions")] #[test] fn test_md5() { let mut state = ParserState::new(); - let result = MD5.call(&Token::dummy(""), &mut state, &[ - Value::String("foobar".to_string()) - ]).unwrap().as_string(); + let result = MD5 + .call( + &Token::dummy(""), + &mut state, + &[Value::String("foobar".to_string())], + ) + .unwrap() + .as_string(); assert_eq!("3858F62230AC3C915F300C664312C63F".to_string(), result); } - + #[test] fn test_choose() { let mut state = ParserState::new(); let mut result; for _ in 0..30 { - result = CHOOSE.call(&Token::dummy(""), &mut state, &[Value::String("test".to_string()), Value::Integer(5)]).unwrap(); - assert_eq!(true, result.is_string() || result == Value::Integer(5).is_int()); + result = CHOOSE + .call( + &Token::dummy(""), + &mut state, + &[Value::String("test".to_string()), Value::Integer(5)], + ) + .unwrap(); + assert_eq!( + true, + result.is_string() || result == Value::Integer(5).is_int() + ); } } - + #[test] fn test_rand() { let mut state = ParserState::new(); @@ -141,17 +169,34 @@ mod test_builtin_table { for _ in 0..30 { result = RAND.call(&Token::dummy(""), &mut state, &[]).unwrap(); - assert_eq!(true, result.as_float().unwrap() >= 0.0 && result.as_float().unwrap() <= 1.0); + assert_eq!( + true, + result.as_float().unwrap() >= 0.0 && result.as_float().unwrap() <= 1.0 + ); } for _ in 0..30 { - result = RAND.call(&Token::dummy(""), &mut state, &[Value::Integer(5)]).unwrap(); - assert_eq!(true, result.as_int().unwrap() >= 0 && result.as_int().unwrap() <= 5); + result = RAND + .call(&Token::dummy(""), &mut state, &[Value::Integer(5)]) + .unwrap(); + assert_eq!( + true, + result.as_int().unwrap() >= 0 && result.as_int().unwrap() <= 5 + ); } for _ in 0..30 { - result = RAND.call(&Token::dummy(""), &mut state, &[Value::Integer(5), Value::Integer(10)]).unwrap(); - assert_eq!(true, result.as_int().unwrap() >= 5 && result.as_int().unwrap() <= 10); + result = RAND + .call( + &Token::dummy(""), + &mut state, + &[Value::Integer(5), Value::Integer(10)], + ) + .unwrap(); + assert_eq!( + true, + result.as_int().unwrap() >= 5 && result.as_int().unwrap() <= 10 + ); } } } diff --git a/src/functions/builtins/dev.rs b/src/functions/builtins/dev.rs index 2816f3b..6b02408 100644 --- a/src/functions/builtins/dev.rs +++ b/src/functions/builtins/dev.rs @@ -1,41 +1,42 @@ //! Builtin functions that don't fit nicely into other categories use super::*; -use crate::value::{Value, IntegerType}; +use crate::value::{IntegerType, Value}; +use crate::ExpectedTypes; -use std::time::{SystemTime, UNIX_EPOCH}; use std::fs::File; use std::io::{BufRead, BufReader}; +use std::time::{SystemTime, UNIX_EPOCH}; -#[cfg(feature = "encoding-functions")] -use base64::{Engine as _, engine::general_purpose}; - -const TIME : FunctionDefinition = FunctionDefinition { +const TIME: FunctionDefinition = FunctionDefinition { name: "time", category: None, description: "Returns a unix timestamp for the current system time", arguments: Vec::new, - handler: |_function, _token, _state, _args| { - match SystemTime::now().duration_since(UNIX_EPOCH) { - Ok(n) => Ok(Value::Integer(n.as_secs() as IntegerType)), - Err(_) => Ok(Value::Integer(0)) - } - } + handler: |_function, _token, _state, _args| match SystemTime::now().duration_since(UNIX_EPOCH) { + Ok(n) => Ok(Value::Integer(n.as_secs() as IntegerType)), + Err(_) => Ok(Value::Integer(0)), + }, }; const DEFAULT_TAIL_LINES: IntegerType = 1; -const TAIL : FunctionDefinition = FunctionDefinition { +const TAIL: FunctionDefinition = FunctionDefinition { name: "tail", category: None, description: "Returns the last [lines] lines from a given file", - arguments: || vec![ - FunctionArgument::new_required("filename", ExpectedTypes::String), - FunctionArgument::new_optional("lines", ExpectedTypes::Int), - ], + arguments: || { + vec![ + FunctionArgument::new_required("filename", ExpectedTypes::String), + FunctionArgument::new_optional("lines", ExpectedTypes::Int), + ] + }, handler: |_function, token, _state, args| { - let mut lines : Vec = Vec::new(); - let n_lines: IntegerType = args.get("lines").optional_or(Value::Integer(DEFAULT_TAIL_LINES)) - .as_int().unwrap_or(DEFAULT_TAIL_LINES); + let mut lines: Vec = Vec::new(); + let n_lines: IntegerType = args + .get("lines") + .optional_or(Value::Integer(DEFAULT_TAIL_LINES)) + .as_int() + .unwrap_or(DEFAULT_TAIL_LINES); match File::open(args.get("filename").required().as_string()) { Ok(f) => { @@ -46,104 +47,130 @@ const TAIL : FunctionDefinition = FunctionDefinition { if lines.len() as IntegerType > n_lines { lines.remove(0); } - }, - Err(e) => return Err(IOError::new(token, &e.to_string()).into()) + } + Err(e) => return Err(Error::Io(e, token.clone())), } } - }, - Err(e) => return Err(IOError::new(token, &e.to_string()).into()) + } + Err(e) => return Err(Error::Io(e, token.clone())), } Ok(Value::String(lines.join("\n"))) - } + }, }; -const PRETTYJSON : FunctionDefinition = FunctionDefinition { +const PRETTYJSON: FunctionDefinition = FunctionDefinition { name: "prettyjson", category: None, description: "Beautify a JSON input string", - arguments: || vec![ - FunctionArgument::new_required("input", ExpectedTypes::String) - ], + arguments: || { + vec![FunctionArgument::new_required( + "input", + ExpectedTypes::String, + )] + }, handler: |_function, token, _state, args| { let input = args.get("input").required().as_string(); - match serde_json::from_str::(&input) { - Ok(json) => match serde_json::to_string_pretty(&json) { - Ok(output) => Ok(Value::String(output)), - Err(e) => Err(ParsingError::new(token, "JSON", &e.to_string()).into()) - }, - Err(e) => Err(ParsingError::new(token, "JSON", &e.to_string()).into()) + if let Ok(json) = serde_json::from_str::(&input) { + if let Ok(output) = serde_json::to_string_pretty(&json) { + return Ok(Value::String(output)); + } } - } + + Err(Error::StringFormat { + expected_format: "url".to_string(), + token: token.clone(), + }) + }, }; #[cfg(feature = "encoding-functions")] -const URLENCODE : FunctionDefinition = FunctionDefinition { +const URLENCODE: FunctionDefinition = FunctionDefinition { name: "urlencode", category: None, description: "Escape characters in a string for use in a URL", - arguments: || vec![ - FunctionArgument::new_required("input", ExpectedTypes::String) - ], + arguments: || { + vec![FunctionArgument::new_required( + "input", + ExpectedTypes::String, + )] + }, handler: |_function, _token, _state, args| { let input = args.get("input").required().as_string(); Ok(Value::String(urlencoding::encode(&input).into_owned())) - } + }, }; #[cfg(feature = "encoding-functions")] -const URLDECODE : FunctionDefinition = FunctionDefinition { +const URLDECODE: FunctionDefinition = FunctionDefinition { name: "urldecode", category: None, description: "Decode urlencoded character escape sequences in a string", - arguments: || vec![ - FunctionArgument::new_required("input", ExpectedTypes::String) - ], + arguments: || { + vec![FunctionArgument::new_required( + "input", + ExpectedTypes::String, + )] + }, handler: |_function, token, _state, args| { let input = args.get("input").required().as_string(); match urlencoding::decode(&input) { Ok(s) => Ok(Value::String(s.into_owned())), - Err(e) => Err(ParsingError::new(token, "url", &e.to_string()).into()) + Err(_) => Err(Error::StringFormat { + expected_format: "url".to_string(), + token: token.clone(), + }), } - } + }, }; #[cfg(feature = "encoding-functions")] -const BASE64ENCODE : FunctionDefinition = FunctionDefinition { +const BASE64ENCODE: FunctionDefinition = FunctionDefinition { name: "atob", category: None, description: "Convert a string into a base64 encoded string", - arguments: || vec![ - FunctionArgument::new_required("input", ExpectedTypes::String) - ], + arguments: || { + vec![FunctionArgument::new_required( + "input", + ExpectedTypes::String, + )] + }, handler: |_function, _token, _state, args| { + use base64::{engine::general_purpose, Engine as _}; + let input = args.get("input").required().as_string(); let mut buf = String::new(); general_purpose::STANDARD.encode_string(&input, &mut buf); Ok(Value::String(buf)) - } + }, }; #[cfg(feature = "encoding-functions")] -const BASE64DECODE : FunctionDefinition = FunctionDefinition { +const BASE64DECODE: FunctionDefinition = FunctionDefinition { name: "btoa", category: None, description: "Convert a base64 encoded string to an ascii encoded string", - arguments: || vec![ - FunctionArgument::new_required("input", ExpectedTypes::String) - ], + arguments: || { + vec![FunctionArgument::new_required( + "input", + ExpectedTypes::String, + )] + }, handler: |_function, token, _state, args| { + use base64::{engine::general_purpose, Engine as _}; + let input = args.get("input").required().as_string(); - match general_purpose::STANDARD.decode(input) { - Ok(bytes) => { - match std::str::from_utf8(&bytes) { - Ok(s) => Ok(Value::String(s.to_string())), - Err(e) => Err(ParsingError::new(token, "base64", &e.to_string()).into()) - } - }, - Err(e) => Err(ParsingError::new(token, "base64", &e.to_string()).into()) + if let Ok(bytes) = general_purpose::STANDARD.decode(input) { + if let Ok(s) = std::str::from_utf8(&bytes) { + return Ok(Value::String(s.to_string())); + } } - } + + Err(Error::StringFormat { + expected_format: "base64".to_string(), + token: token.clone(), + }) + }, }; /// Register developper functions @@ -151,16 +178,16 @@ pub fn register_functions(table: &mut FunctionTable) { table.register(TIME); table.register(TAIL); table.register(PRETTYJSON); - + #[cfg(feature = "encoding-functions")] table.register(URLDECODE); - + #[cfg(feature = "encoding-functions")] table.register(URLENCODE); - + #[cfg(feature = "encoding-functions")] table.register(BASE64DECODE); - + #[cfg(feature = "encoding-functions")] table.register(BASE64ENCODE); } @@ -168,8 +195,8 @@ pub fn register_functions(table: &mut FunctionTable) { #[cfg(test)] mod test_builtin_table { use super::*; - const WAS_NOW : IntegerType = 1647531435; - + const WAS_NOW: IntegerType = 1647531435; + #[test] fn test_time() { let mut state = ParserState::new(); @@ -177,44 +204,82 @@ mod test_builtin_table { let result = TIME.call(&Token::dummy(""), &mut state, &[]).unwrap(); assert_eq!(true, result.as_int().unwrap() > WAS_NOW); } - + #[test] fn test_tail() { let mut state = ParserState::new(); - let result = TAIL.call(&Token::dummy(""), &mut state, &[Value::String("README.md".to_string()), Value::Integer(5)]).unwrap(); + let result = TAIL + .call( + &Token::dummy(""), + &mut state, + &[Value::String("README.md".to_string()), Value::Integer(5)], + ) + .unwrap(); assert_eq!(4, result.as_string().matches("\n").count()); } - + #[test] fn test_prettyjson() { let mut state = ParserState::new(); - let result = PRETTYJSON.call(&Token::dummy(""), &mut state, &[Value::String("{\"test\":[1,2,3,[1,{\"2\": 3}]]}".to_string())]).unwrap(); + let result = PRETTYJSON + .call( + &Token::dummy(""), + &mut state, + &[Value::String( + "{\"test\":[1,2,3,[1,{\"2\": 3}]]}".to_string(), + )], + ) + .unwrap(); assert_eq!("{\n \"test\": [\n 1,\n 2,\n 3,\n [\n 1,\n {\n \"2\": 3\n }\n ]\n ]\n}", result.as_string()); } - + #[cfg(feature = "encoding-functions")] #[test] fn test_urlencode_decode() { let mut state = ParserState::new(); - let result = URLENCODE.call(&Token::dummy(""), &mut state, &[Value::String("TES % T =".to_string())]).unwrap(); + let result = URLENCODE + .call( + &Token::dummy(""), + &mut state, + &[Value::String("TES % T =".to_string())], + ) + .unwrap(); assert_eq!("TES%20%25%20T%20%3D", result.as_string()); - let result = URLDECODE.call(&Token::dummy(""), &mut state, &[Value::String("TES%20%25%20T%20%3D".to_string())]).unwrap(); + let result = URLDECODE + .call( + &Token::dummy(""), + &mut state, + &[Value::String("TES%20%25%20T%20%3D".to_string())], + ) + .unwrap(); assert_eq!("TES % T =", result.as_string()); } - + #[cfg(feature = "encoding-functions")] #[test] fn test_base64encode_decode() { let mut state = ParserState::new(); - let result = BASE64ENCODE.call(&Token::dummy(""), &mut state, &[Value::String("TES % T =".to_string())]).unwrap(); + let result = BASE64ENCODE + .call( + &Token::dummy(""), + &mut state, + &[Value::String("TES % T =".to_string())], + ) + .unwrap(); assert_eq!("VEVTICUgVCA9", result.as_string()); - let result = BASE64DECODE.call(&Token::dummy(""), &mut state, &[Value::String("VEVTICUgVCA9".to_string())]).unwrap(); + let result = BASE64DECODE + .call( + &Token::dummy(""), + &mut state, + &[Value::String("VEVTICUgVCA9".to_string())], + ) + .unwrap(); assert_eq!("TES % T =", result.as_string()); } } diff --git a/src/functions/builtins/math.rs b/src/functions/builtins/math.rs index 7ce1443..6b05216 100644 --- a/src/functions/builtins/math.rs +++ b/src/functions/builtins/math.rs @@ -1,143 +1,195 @@ //! Builtin functions for advanced mathematics use super::*; -use crate::value::{Value, IntegerType}; +use crate::value::{IntegerType, Value}; +use crate::ExpectedTypes; -const BOOL : FunctionDefinition = FunctionDefinition { +const BOOL: FunctionDefinition = FunctionDefinition { name: "bool", category: Some("math"), description: "Returns a value as a boolean", - arguments: || vec![ - FunctionArgument::new_required("n", ExpectedTypes::Any), - ], + arguments: || vec![FunctionArgument::new_required("n", ExpectedTypes::Any)], handler: |_function, _token, _state, args| { Ok(Value::Boolean(args.get("n").required().as_bool())) - } + }, }; -const ARRAY : FunctionDefinition = FunctionDefinition { +const ARRAY: FunctionDefinition = FunctionDefinition { name: "array", category: Some("math"), description: "Returns a value as an array", - arguments: || vec![ - FunctionArgument::new_required("n", ExpectedTypes::Any), - ], + arguments: || vec![FunctionArgument::new_required("n", ExpectedTypes::Any)], handler: |_function, _token, _state, args| { Ok(Value::Array(args.get("n").required().as_array())) - } + }, }; -const INT : FunctionDefinition = FunctionDefinition { +const INT: FunctionDefinition = FunctionDefinition { name: "int", category: Some("math"), description: "Returns a value as an integer", - arguments: || vec![ - FunctionArgument::new_required("n", ExpectedTypes::IntOrFloat), - ], + arguments: || { + vec![FunctionArgument::new_required( + "n", + ExpectedTypes::IntOrFloat, + )] + }, handler: |_function, _token, _state, args| { Ok(Value::Integer(args.get("n").required().as_int().unwrap())) - } + }, }; -const FLOAT : FunctionDefinition = FunctionDefinition { +const FLOAT: FunctionDefinition = FunctionDefinition { name: "float", category: Some("math"), description: "Returns a value as a float", - arguments: || vec![ - FunctionArgument::new_required("n", ExpectedTypes::IntOrFloat), - ], + arguments: || { + vec![FunctionArgument::new_required( + "n", + ExpectedTypes::IntOrFloat, + )] + }, handler: |_function, _token, _state, args| { Ok(Value::Float(args.get("n").required().as_float().unwrap())) - } + }, }; -const MIN : FunctionDefinition = FunctionDefinition { +const MIN: FunctionDefinition = FunctionDefinition { name: "min", category: Some("math"), description: "Returns the smallest numeric value from the supplied arguments", - arguments: || vec![ - FunctionArgument::new_plural("n", ExpectedTypes::IntOrFloat, false), - ], + arguments: || { + vec![FunctionArgument::new_plural( + "n", + ExpectedTypes::IntOrFloat, + false, + )] + }, handler: |_function, _token, _state, args| { - let mut valid_args = args.iter().filter(|a|!a.as_float().unwrap().is_nan()).cloned().collect::>(); - valid_args.sort_by(|a,b| a.as_float().unwrap().partial_cmp(&b.as_float().unwrap()).unwrap()); + let mut valid_args = args + .iter() + .filter(|a| !a.as_float().unwrap().is_nan()) + .cloned() + .collect::>(); + valid_args.sort_by(|a, b| { + a.as_float() + .unwrap() + .partial_cmp(&b.as_float().unwrap()) + .unwrap() + }); if valid_args.is_empty() { Ok(args.get("n").plural().first().cloned().unwrap()) } else { Ok(valid_args[0].clone()) } - } + }, }; -const MAX : FunctionDefinition = FunctionDefinition { +const MAX: FunctionDefinition = FunctionDefinition { name: "max", category: Some("math"), description: "Returns the largest numeric value from the supplied arguments", - arguments: || vec![ - FunctionArgument::new_plural("n", ExpectedTypes::IntOrFloat, false), - ], + arguments: || { + vec![FunctionArgument::new_plural( + "n", + ExpectedTypes::IntOrFloat, + false, + )] + }, handler: |_function, _token, _state, args| { - let mut valid_args = args.iter().filter(|a|!a.as_float().unwrap().is_nan()).cloned().collect::>(); - valid_args.sort_by(|a,b| b.as_float().unwrap().partial_cmp(&a.as_float().unwrap()).unwrap()); + let mut valid_args = args + .iter() + .filter(|a| !a.as_float().unwrap().is_nan()) + .cloned() + .collect::>(); + valid_args.sort_by(|a, b| { + b.as_float() + .unwrap() + .partial_cmp(&a.as_float().unwrap()) + .unwrap() + }); if valid_args.is_empty() { Ok(args.get("n").plural().first().cloned().unwrap()) } else { Ok(valid_args[0].clone()) } - } + }, }; -const CEIL : FunctionDefinition = FunctionDefinition { +const CEIL: FunctionDefinition = FunctionDefinition { name: "ceil", category: Some("math"), description: "Returns the nearest whole integer larger than n", - arguments: || vec![ - FunctionArgument::new_required("n", ExpectedTypes::IntOrFloat), - ], + arguments: || { + vec![FunctionArgument::new_required( + "n", + ExpectedTypes::IntOrFloat, + )] + }, handler: |_function, _token, _state, args| { - Ok(Value::Integer(args.get("n").required().as_float().unwrap().ceil() as IntegerType)) - } + Ok(Value::Integer( + args.get("n").required().as_float().unwrap().ceil() as IntegerType, + )) + }, }; -const FLOOR : FunctionDefinition = FunctionDefinition { +const FLOOR: FunctionDefinition = FunctionDefinition { name: "floor", category: Some("math"), description: "Returns the nearest whole integer smaller than n", - arguments: || vec![ - FunctionArgument::new_required("n", ExpectedTypes::IntOrFloat), - ], + arguments: || { + vec![FunctionArgument::new_required( + "n", + ExpectedTypes::IntOrFloat, + )] + }, handler: |_function, _token, _state, args| { - Ok(Value::Integer(args.get("n").required().as_float().unwrap().floor() as IntegerType)) - } + Ok(Value::Integer( + args.get("n").required().as_float().unwrap().floor() as IntegerType, + )) + }, }; -const ROUND : FunctionDefinition = FunctionDefinition { +const ROUND: FunctionDefinition = FunctionDefinition { name: "round", category: Some("math"), description: "Returns n, rounded to [precision] decimal places", - arguments: || vec![ - FunctionArgument::new_required("n", ExpectedTypes::IntOrFloat), - FunctionArgument::new_optional("precision", ExpectedTypes::Int), - ], - handler: |_function, token, _state, args| { - let precision = args.get("precision").optional_or(Value::Integer(0)).as_int().unwrap_or(0); - if precision > u32::MAX as IntegerType { - return Err(FunctionOverflowError::new(token, "round(n, precision=0)", 2).into()); + arguments: || { + vec![ + FunctionArgument::new_required("n", ExpectedTypes::IntOrFloat), + FunctionArgument::new_optional("precision", ExpectedTypes::Int), + ] + }, + handler: |function, token, _state, args| { + let precision = args + .get("precision") + .optional_or(Value::Integer(0)) + .as_int() + .unwrap_or(0); + if precision > u32::MAX as IntegerType { + return Err(Error::FunctionArgumentOverflow { + arg: 2, + signature: function.signature(), + token: token.clone(), + }); } - + let multiplier = f64::powi(10.0, precision as i32); let n = args.get("n").required().as_float().unwrap(); Ok(Value::Float((n * multiplier).round() / multiplier)) - } + }, }; -const ABS : FunctionDefinition = FunctionDefinition { +const ABS: FunctionDefinition = FunctionDefinition { name: "abs", category: Some("math"), description: "Returns the absolute value of n", - arguments: || vec![ - FunctionArgument::new_required("n", ExpectedTypes::IntOrFloat) - ], + arguments: || { + vec![FunctionArgument::new_required( + "n", + ExpectedTypes::IntOrFloat, + )] + }, handler: |_function, _token, _state, args| { let n = args.get("n").required(); if n.is_int() { @@ -145,71 +197,98 @@ const ABS : FunctionDefinition = FunctionDefinition { } else { Ok(Value::Float(n.as_float().unwrap().abs())) } - } + }, }; -const LOG10 : FunctionDefinition = FunctionDefinition { +const LOG10: FunctionDefinition = FunctionDefinition { name: "log10", category: Some("math"), description: "Returns the base 10 log of n", - arguments: || vec![ - FunctionArgument::new_required("n", ExpectedTypes::IntOrFloat), - ], + arguments: || { + vec![FunctionArgument::new_required( + "n", + ExpectedTypes::IntOrFloat, + )] + }, handler: |_function, _token, _state, args| { - Ok(Value::Float(args.get("n").required().as_float().unwrap().log10())) - } + Ok(Value::Float( + args.get("n").required().as_float().unwrap().log10(), + )) + }, }; -const LN : FunctionDefinition = FunctionDefinition { +const LN: FunctionDefinition = FunctionDefinition { name: "ln", category: Some("math"), description: "Returns the natural log of n", - arguments: || vec![ - FunctionArgument::new_required("n", ExpectedTypes::IntOrFloat), - ], + arguments: || { + vec![FunctionArgument::new_required( + "n", + ExpectedTypes::IntOrFloat, + )] + }, handler: |_function, _token, _state, args| { - Ok(Value::Float(args.get("n").required().as_float().unwrap().ln())) - } + Ok(Value::Float( + args.get("n").required().as_float().unwrap().ln(), + )) + }, }; -const LOG : FunctionDefinition = FunctionDefinition { +const LOG: FunctionDefinition = FunctionDefinition { name: "log", category: Some("math"), description: "Returns the logarithm of n in any base", - arguments: || vec![ - FunctionArgument::new_required("n", ExpectedTypes::IntOrFloat), - FunctionArgument::new_required("base", ExpectedTypes::IntOrFloat), - ], + arguments: || { + vec![ + FunctionArgument::new_required("n", ExpectedTypes::IntOrFloat), + FunctionArgument::new_required("base", ExpectedTypes::IntOrFloat), + ] + }, handler: |_function, _token, _state, args| { let base = args.get("base").required().as_float().unwrap(); - Ok(Value::Float(args.get("n").required().as_float().unwrap().log(base))) - } + Ok(Value::Float( + args.get("n").required().as_float().unwrap().log(base), + )) + }, }; -const SQRT : FunctionDefinition = FunctionDefinition { +const SQRT: FunctionDefinition = FunctionDefinition { name: "sqrt", category: Some("math"), description: "Returns the square root of n", - arguments: || vec![ - FunctionArgument::new_required("n", ExpectedTypes::IntOrFloat), - ], + arguments: || { + vec![FunctionArgument::new_required( + "n", + ExpectedTypes::IntOrFloat, + )] + }, handler: |_function, _token, _state, args| { - Ok(Value::Float(args.get("n").required().as_float().unwrap().sqrt())) - } + Ok(Value::Float( + args.get("n").required().as_float().unwrap().sqrt(), + )) + }, }; -const ROOT : FunctionDefinition = FunctionDefinition { +const ROOT: FunctionDefinition = FunctionDefinition { name: "root", category: Some("math"), description: "Returns a root of n of any base", - arguments: || vec![ - FunctionArgument::new_required("n", ExpectedTypes::IntOrFloat), - FunctionArgument::new_required("base", ExpectedTypes::IntOrFloat), - ], + arguments: || { + vec![ + FunctionArgument::new_required("n", ExpectedTypes::IntOrFloat), + FunctionArgument::new_required("base", ExpectedTypes::IntOrFloat), + ] + }, handler: |_function, _token, _state, args| { let base = args.get("base").required().as_float().unwrap(); - Ok(Value::Float(args.get("n").required().as_float().unwrap().powf(1.0 / base))) - } + Ok(Value::Float( + args.get("n") + .required() + .as_float() + .unwrap() + .powf(1.0 / base), + )) + }, }; /// Register string functions @@ -227,122 +306,234 @@ pub fn register_functions(table: &mut FunctionTable) { table.register(FLOOR); table.register(ROUND); table.register(ABS); - + // Roots and logs table.register(LOG10); table.register(LN); table.register(LOG); table.register(SQRT); table.register(ROOT); - } #[cfg(test)] mod test_builtin_functions { - use crate::value::{FloatType}; use super::*; - + use crate::value::FloatType; + #[test] fn test_min() { let mut state = ParserState::new(); - assert_eq!(Value::Integer(3), MIN.call(&Token::dummy(""), &mut state, &[ - Value::Float(3.5), + assert_eq!( Value::Integer(3), - Value::Integer(7), - Value::Float(FloatType::NAN) - ]).unwrap()); - assert_eq!(Value::Float(3.1), MIN.call(&Token::dummy(""), &mut state, &[ - Value::Float(3.5), + MIN.call( + &Token::dummy(""), + &mut state, + &[ + Value::Float(3.5), + Value::Integer(3), + Value::Integer(7), + Value::Float(FloatType::NAN) + ] + ) + .unwrap() + ); + assert_eq!( Value::Float(3.1), - Value::Integer(7), - Value::Float(FloatType::NAN) - ]).unwrap()); + MIN.call( + &Token::dummy(""), + &mut state, + &[ + Value::Float(3.5), + Value::Float(3.1), + Value::Integer(7), + Value::Float(FloatType::NAN) + ] + ) + .unwrap() + ); } - + #[test] fn test_max() { let mut state = ParserState::new(); - assert_eq!(Value::Integer(7), MAX.call(&Token::dummy(""), &mut state, &[ - Value::Float(3.5), - Value::Integer(3), + assert_eq!( Value::Integer(7), - Value::Float(FloatType::NAN) - ]).unwrap()); - assert_eq!(Value::Float(7.1), MAX.call(&Token::dummy(""), &mut state, &[ - Value::Float(3.5), - Value::Integer(3), + MAX.call( + &Token::dummy(""), + &mut state, + &[ + Value::Float(3.5), + Value::Integer(3), + Value::Integer(7), + Value::Float(FloatType::NAN) + ] + ) + .unwrap() + ); + assert_eq!( Value::Float(7.1), - Value::Float(FloatType::NAN) - ]).unwrap()); + MAX.call( + &Token::dummy(""), + &mut state, + &[ + Value::Float(3.5), + Value::Integer(3), + Value::Float(7.1), + Value::Float(FloatType::NAN) + ] + ) + .unwrap() + ); } - + #[test] fn test_ceil() { let mut state = ParserState::new(); - assert_eq!(Value::Integer(4), CEIL.call(&Token::dummy(""), &mut state, &[Value::Float(3.5)]).unwrap()); - assert_eq!(Value::Integer(4), CEIL.call(&Token::dummy(""), &mut state, &[Value::Integer(4)]).unwrap()); + assert_eq!( + Value::Integer(4), + CEIL.call(&Token::dummy(""), &mut state, &[Value::Float(3.5)]) + .unwrap() + ); + assert_eq!( + Value::Integer(4), + CEIL.call(&Token::dummy(""), &mut state, &[Value::Integer(4)]) + .unwrap() + ); } - + #[test] fn test_floor() { let mut state = ParserState::new(); - assert_eq!(Value::Integer(3), FLOOR.call(&Token::dummy(""), &mut state, &[Value::Float(3.5)]).unwrap()); - assert_eq!(Value::Integer(4), FLOOR.call(&Token::dummy(""), &mut state, &[Value::Integer(4)]).unwrap()); + assert_eq!( + Value::Integer(3), + FLOOR + .call(&Token::dummy(""), &mut state, &[Value::Float(3.5)]) + .unwrap() + ); + assert_eq!( + Value::Integer(4), + FLOOR + .call(&Token::dummy(""), &mut state, &[Value::Integer(4)]) + .unwrap() + ); } - + #[test] fn test_round() { let mut state = ParserState::new(); - assert_eq!(Value::Float(3.56), ROUND.call(&Token::dummy(""), &mut state, &[Value::Float(3.555), Value::Integer(2)]).unwrap()); - assert_eq!(Value::Float(4.0), ROUND.call(&Token::dummy(""), &mut state, &[Value::Integer(4), Value::Integer(2)]).unwrap()); + assert_eq!( + Value::Float(3.56), + ROUND + .call( + &Token::dummy(""), + &mut state, + &[Value::Float(3.555), Value::Integer(2)] + ) + .unwrap() + ); + assert_eq!( + Value::Float(4.0), + ROUND + .call( + &Token::dummy(""), + &mut state, + &[Value::Integer(4), Value::Integer(2)] + ) + .unwrap() + ); } - + #[test] fn test_abs() { let mut state = ParserState::new(); - assert_eq!(Value::Integer(3), ABS.call(&Token::dummy(""), &mut state, &[Value::Integer(3)]).unwrap()); - assert_eq!(Value::Integer(3), ABS.call(&Token::dummy(""), &mut state, &[Value::Integer(-3)]).unwrap()); - assert_eq!(Value::Float(4.0), ABS.call(&Token::dummy(""), &mut state, &[Value::Float(-4.0)]).unwrap()); + assert_eq!( + Value::Integer(3), + ABS.call(&Token::dummy(""), &mut state, &[Value::Integer(3)]) + .unwrap() + ); + assert_eq!( + Value::Integer(3), + ABS.call(&Token::dummy(""), &mut state, &[Value::Integer(-3)]) + .unwrap() + ); + assert_eq!( + Value::Float(4.0), + ABS.call(&Token::dummy(""), &mut state, &[Value::Float(-4.0)]) + .unwrap() + ); } - + #[test] fn test_ln() { let mut state = ParserState::new(); - assert_eq!(Value::Float(1.0), LN.call(&Token::dummy(""), &mut state, &[Value::Float(std::f64::consts::E)]).unwrap()); + assert_eq!( + Value::Float(1.0), + LN.call( + &Token::dummy(""), + &mut state, + &[Value::Float(std::f64::consts::E)] + ) + .unwrap() + ); } - + #[test] fn test_log10() { let mut state = ParserState::new(); - assert_eq!(Value::Float(2.0), LOG10.call(&Token::dummy(""), &mut state, &[Value::Float(100.0)]).unwrap()); + assert_eq!( + Value::Float(2.0), + LOG10 + .call(&Token::dummy(""), &mut state, &[Value::Float(100.0)]) + .unwrap() + ); } - + #[test] fn test_log() { let mut state = ParserState::new(); - assert_eq!(Value::Float(2.0), LOG.call(&Token::dummy(""), &mut state, &[Value::Float(100.0), Value::Integer(10)]).unwrap()); + assert_eq!( + Value::Float(2.0), + LOG.call( + &Token::dummy(""), + &mut state, + &[Value::Float(100.0), Value::Integer(10)] + ) + .unwrap() + ); } - + #[test] fn test_sqrt() { let mut state = ParserState::new(); - assert_eq!(Value::Float(3.0), SQRT.call(&Token::dummy(""), &mut state, &[Value::Float(9.0)]).unwrap()); + assert_eq!( + Value::Float(3.0), + SQRT.call(&Token::dummy(""), &mut state, &[Value::Float(9.0)]) + .unwrap() + ); } - + #[test] fn test_root() { let mut state = ParserState::new(); - assert_eq!(Value::Float(3.0), ROOT.call(&Token::dummy(""), &mut state, &[Value::Float(27.0), Value::Integer(3)]).unwrap()); + assert_eq!( + Value::Float(3.0), + ROOT.call( + &Token::dummy(""), + &mut state, + &[Value::Float(27.0), Value::Integer(3)] + ) + .unwrap() + ); } -} \ No newline at end of file +} diff --git a/src/functions/builtins/network.rs b/src/functions/builtins/network.rs index dfa92e9..8b5f56a 100644 --- a/src/functions/builtins/network.rs +++ b/src/functions/builtins/network.rs @@ -1,75 +1,86 @@ //! Builtin functions for network OPs use super::*; -use crate::{network::*, value::ObjectType}; +use crate::{network::*, value::ObjectType, ExpectedTypes}; use std::collections::HashMap; -const RESOLVE : FunctionDefinition = FunctionDefinition { +const RESOLVE: FunctionDefinition = FunctionDefinition { name: "resolve", category: Some("network"), description: "Returns the IP address associated to a given hostname", - arguments: || vec![ - FunctionArgument::new_required("hostname", ExpectedTypes::String), - ], + arguments: || { + vec![FunctionArgument::new_required( + "hostname", + ExpectedTypes::String, + )] + }, handler: |_function, token, _state, args| { let hostname = args.get("hostname").required().as_string(); match resolve(&hostname) { Ok(v) => Ok(v), - Err(e) => Err(IOError::new(token, &e.to_string()).into()) + Err(e) => Err(Error::Io(e, token.clone())), } - } + }, }; -const GET : FunctionDefinition = FunctionDefinition { +const GET: FunctionDefinition = FunctionDefinition { name: "get", category: Some("network"), description: "Return the resulting text-format body of an HTTP GET call", - arguments: || vec![ - FunctionArgument::new_required("url", ExpectedTypes::String), - FunctionArgument::new_optional("headers", ExpectedTypes::Object) - ], + arguments: || { + vec![ + FunctionArgument::new_required("url", ExpectedTypes::String), + FunctionArgument::new_optional("headers", ExpectedTypes::Object), + ] + }, handler: |_function, token, _state, args| { let url = args.get("url").required().as_string(); let arg_headers = match args.get("headers").optional() { Some(v) => v.as_object(), - None => ObjectType::new() + None => ObjectType::new(), }; let headers = HashMap::from_iter( - arg_headers.iter().map(|(k,v)| (k.to_string(), v.to_string())) + arg_headers + .iter() + .map(|(k, v)| (k.to_string(), v.to_string())), ); match request(&url, None, headers) { Ok(v) => Ok(v), - Err(e) => Err(NetworkError::from_reqwesterror(token, e).into()) + Err(e) => Err(Error::Network(e, token.clone())), } - } + }, }; -const POST : FunctionDefinition = FunctionDefinition { +const POST: FunctionDefinition = FunctionDefinition { name: "post", category: Some("network"), description: "Return the resulting text-format body of an HTTP POST call", - arguments: || vec![ - FunctionArgument::new_required("url", ExpectedTypes::String), - FunctionArgument::new_required("body", ExpectedTypes::String), - FunctionArgument::new_optional("headers", ExpectedTypes::Object) - ], + arguments: || { + vec![ + FunctionArgument::new_required("url", ExpectedTypes::String), + FunctionArgument::new_required("body", ExpectedTypes::String), + FunctionArgument::new_optional("headers", ExpectedTypes::Object), + ] + }, handler: |_function, token, _state, args| { let url = args.get("url").required().as_string(); let body = args.get("body").required().as_string(); let arg_headers = match args.get("headers").optional() { Some(v) => v.as_object(), - None => ObjectType::new() + None => ObjectType::new(), }; let headers = HashMap::from_iter( - arg_headers.iter().map(|(k,v)| (k.to_string(), v.to_string())) + arg_headers + .iter() + .map(|(k, v)| (k.to_string(), v.to_string())), ); match request(&url, Some(body), headers) { Ok(v) => Ok(v), - Err(e) => Err(NetworkError::from_reqwesterror(token, e).into()) + Err(e) => Err(Error::Network(e, token.clone())), } - } + }, }; /// Register network functions @@ -83,36 +94,73 @@ pub fn register_functions(table: &mut FunctionTable) { mod test_builtin_table { use super::*; - fn hardy_net_test(test: fn() -> Result) -> Value { - let results = [ - test(), test(), test(), test(), test() - ]; + fn hardy_net_test(test: fn() -> Result) -> Value { + let results = [test(), test(), test(), test(), test()]; assert_eq!(true, results.iter().filter(|r| r.is_ok()).count() > 0); - return results.iter().filter(|r| r.is_ok()).next().unwrap().as_ref().unwrap().clone() + return results + .iter() + .filter(|r| r.is_ok()) + .next() + .unwrap() + .as_ref() + .unwrap() + .clone(); } - + #[test] fn test_get() { - assert_eq!(true, hardy_net_test(|| { - let mut state = ParserState::new(); - return GET.call(&Token::dummy(""), &mut state, &[Value::String("https://google.com".to_string()), Value::String("authorization=5".to_string())]); - }).as_string().to_lowercase().starts_with("= s.len() as IntegerType || start < 0 { - return Err(FunctionOverflowError::new(token, &function.signature(), 2).into()); + return Err(Error::FunctionArgumentOverflow { + arg: 2, + signature: function.signature(), + token: token.clone() + }); } else if length < 0 || length > (s.len() - start as usize) as IntegerType { - return Err(FunctionOverflowError::new(token, &function.signature(), 3).into()); + return Err(Error::FunctionArgumentOverflow { + arg: 3, + signature: function.signature(), + token: token.clone() + }); } Ok(Value::String(s.chars().skip(start as usize).take(length as usize).collect())) @@ -132,8 +141,8 @@ const REGEX : FunctionDefinition = FunctionDefinition { }; let re = Regex::new(&pattern); - if let Err(e) = re { - return Err(ParsingError::new(token, "regex", &e.to_string()).into()); + if let Err(_) = re { + return Err(Error::StringFormat { expected_format: "regex".to_string(), token: token.clone() }); } if let Some(caps) = re.unwrap().captures(&subject) { diff --git a/src/functions/builtins/system.rs b/src/functions/builtins/system.rs index ee31552..0e808c2 100644 --- a/src/functions/builtins/system.rs +++ b/src/functions/builtins/system.rs @@ -1,14 +1,18 @@ //! Builtin functions for lower level ops -use crate::{Token, help::Help}; use super::*; -const HELP : FunctionDefinition = FunctionDefinition { +use crate::{help::Help, ExpectedTypes, Token}; + +const HELP: FunctionDefinition = FunctionDefinition { name: "help", category: None, description: "Display a help message", - arguments: || vec![ - FunctionArgument::new_optional("function_name", ExpectedTypes::String), - ], + arguments: || { + vec![FunctionArgument::new_optional( + "function_name", + ExpectedTypes::String, + )] + }, handler: |_function, token, state, args| { match args.get("function_name").optional() { Some(f) => { @@ -18,21 +22,24 @@ const HELP : FunctionDefinition = FunctionDefinition { if let Some(f) = state.functions.get(&target) { return Ok(Value::String(f.help())); } - + // Extension functions #[cfg(feature = "extensions")] if state.extensions.has_function(&target) { let signature = format!("{}(...)", target); return Ok(Value::String(signature)); } - + // User-defined functions if let Some(f) = state.user_functions.get(&target) { return Ok(Value::String(f.signature())); } - - Err(FunctionNameError::new(token, &target).into()) - }, + + Err(Error::FunctionName { + name: target, + token: token.clone(), + }) + } None => { let mut help = Help::new(); @@ -46,13 +53,13 @@ const HELP : FunctionDefinition = FunctionDefinition { .add_entry(" sum(a) = element(a, 0) + ( len(a)>1 ? sum(dequeue(a)) : 0 )") .add_entry("Performs arithmetic between arrays, and scalars: ") .add_entry(" [10, 12] + 2 * [1.2, 1.3]"); - + help.add_block("Operators") .add_entry(" Bitwise: AND (0xF & 0xA), OR (0xA | 0xF), XOR (0xA ^ 0xF), NOT (~0xA), SHIFT (0xF >> 1, 0xA << 1)") .add_entry(" Boolean: AND (true && false), OR (true || false), CMP (1 < 2, 4 >= 5), EQ (1 == 1, 2 != 5)") .add_entry("Arithmetic: Add/Sub (+, -), Mul/Div (*, /), Exponentiation (**), Modulo (%), Implied Mul ((5)(5), 5x)") .add_entry(" Unary: Factorial (5!!), Negation (-1, -(1+1))"); - + help.add_block("Data Types") .add_entry(" String: Text delimited by 'quotes' or \"double-quotes\"") .add_entry(" Boolean: A truth value (true or false)") @@ -68,44 +75,48 @@ const HELP : FunctionDefinition = FunctionDefinition { Ok(Value::String(help.to_string())) } } - } + }, }; -const RUN : FunctionDefinition = FunctionDefinition { +const RUN: FunctionDefinition = FunctionDefinition { name: "run", category: None, description: "Run a string as an expression", - arguments: || vec![ - FunctionArgument::new_required("expression", ExpectedTypes::String), - ], + arguments: || { + vec![FunctionArgument::new_required( + "expression", + ExpectedTypes::String, + )] + }, handler: |_function, _token, state, args| { let expression = args.get("expression").required().as_string(); match Token::new(&expression, state) { Ok(t) => Ok(t.value()), - Err(e) => Err(e) + Err(e) => Err(e), } - } + }, }; -const CALL : FunctionDefinition = FunctionDefinition { +const CALL: FunctionDefinition = FunctionDefinition { name: "call", category: None, description: "Run the contents of a file as a script", - arguments: || vec![ - FunctionArgument::new_required("filename", ExpectedTypes::String), - ], + arguments: || { + vec![FunctionArgument::new_required( + "filename", + ExpectedTypes::String, + )] + }, handler: |_function, token, state, args| { let filename = args.get("filename").required().as_string(); match std::fs::read_to_string(filename) { - Ok(script) => { - match Token::new(&script, state) { - Ok(t) => Ok(t.value()), - Err(e) => Err(e) - } + Ok(script) => match Token::new(&script, state) { + Ok(t) => Ok(t.value()), + Err(e) => Err(e), }, - Err(e) => Err(IOError::new(token, &e.to_string()).into()) + Err(e) => Err(Error::Io(e, token.clone())), } - } + }, }; /// Register api functions @@ -121,26 +132,34 @@ mod test_token { #[cfg(feature = "extensions")] use crate::Extension; - - use std::path::PathBuf; + + use std::{collections::HashMap, path::PathBuf}; #[test] fn test_call() { let mut state = ParserState::new(); - + // Get test script location let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR")); d.push("example_scripts"); d.push("populate_state.lav"); let path = d.display().to_string().replace("\\", "\\\\"); - assert_eq!(true, CALL.call(&Token::dummy(""), &mut state, &[ - Value::String("not a real path.oops".to_string()) - ]).is_err()); + assert_eq!( + true, + CALL.call( + &Token::dummy(""), + &mut state, + &[Value::String("not a real path.oops".to_string())] + ) + .is_err() + ); - assert_eq!(false, CALL.call(&Token::dummy(""), &mut state, &[ - Value::String(path) - ]).is_err()); + assert_eq!( + false, + CALL.call(&Token::dummy(""), &mut state, &[Value::String(path)]) + .is_err() + ); assert_eq!(true, state.variables.contains_key("important_value")); assert_eq!(true, state.user_functions.contains_key("factorial")); @@ -150,17 +169,35 @@ mod test_token { fn test_run() { let mut state = ParserState::new(); - assert_eq!(Value::Boolean(false), RUN.call(&Token::dummy(""), &mut state, &[ - Value::String("contains(help(), 'foobar(')".to_string()) - ]).unwrap()); + assert_eq!( + Value::Boolean(false), + RUN.call( + &Token::dummy(""), + &mut state, + &[Value::String("contains(help(), 'foobar(')".to_string())] + ) + .unwrap() + ); - assert_eq!(Value::String("0".to_string()), RUN.call(&Token::dummy(""), &mut state, &[ - Value::String("foobar(x) = 0".to_string()) - ]).unwrap()); + assert_eq!( + Value::String("0".to_string()), + RUN.call( + &Token::dummy(""), + &mut state, + &[Value::String("foobar(x) = 0".to_string())] + ) + .unwrap() + ); - assert_eq!(Value::Boolean(true), RUN.call(&Token::dummy(""), &mut state, &[ - Value::String("contains(help(), 'foobar(')".to_string()) - ]).unwrap()); + assert_eq!( + Value::Boolean(true), + RUN.call( + &Token::dummy(""), + &mut state, + &[Value::String("contains(help(), 'foobar(')".to_string())] + ) + .unwrap() + ); } #[test] @@ -169,38 +206,87 @@ mod test_token { // Help #[cfg(feature = "extensions")] - state.extensions.add("test.js", Extension::new_stub( - None, None, None, - vec!["test".to_string(), "test2".to_string()], - vec!["test3".to_string(), "test4".to_string()] - )); - - assert_eq!(true, HELP.call(&Token::dummy(""), &mut state, &[ - ]).unwrap().as_string().contains("Math Functions")); - - assert_eq!(true, HELP.call(&Token::dummy(""), &mut state, &[ - ]).unwrap().as_string().contains("Built-in Decorators")); - + state.extensions.add( + "test.js", + Extension { + module: Default::default(), + name: "Unnamed Extension".to_string(), + author: "@anon".to_string(), + version: "0.0.0".to_string(), + functions: Some(HashMap::from([("test".to_string(), "test2".to_string())])), + decorators: Some(HashMap::from([("test3".to_string(), "test4".to_string())])), + + function_definitions: None, + decorator_definitions: None, + }, + ); + + assert_eq!( + true, + HELP.call(&Token::dummy(""), &mut state, &[]) + .unwrap() + .as_string() + .contains("Math Functions") + ); + + assert_eq!( + true, + HELP.call(&Token::dummy(""), &mut state, &[]) + .unwrap() + .as_string() + .contains("Built-in Decorators") + ); + + println!( + "{}", + HELP.call(&Token::dummy(""), &mut state, &[]) + .unwrap() + .as_string() + ); + #[cfg(feature = "extensions")] + assert_eq!( + true, + HELP.call(&Token::dummy(""), &mut state, &[]) + .unwrap() + .as_string() + .contains("Unnamed Extension v0.0.0") + ); - assert_eq!(true, HELP.call(&Token::dummy(""), &mut state, &[ - ]).unwrap().as_string().contains("Unnamed Extension v0.0.0")); + assert_eq!( + "strlen(s): Returns the length of the string s", + HELP.call( + &Token::dummy(""), + &mut state, + &[Value::String("strlen".to_string())] + ) + .unwrap() + .as_string() + ); - assert_eq!("strlen(s): Returns the length of the string s", HELP.call(&Token::dummy(""), &mut state, &[ - Value::String("strlen".to_string()) - ]).unwrap().as_string()); + assert_eq!( + "strlen(s): Returns the length of the string s", + Token::new("help('strlen')", &mut state).unwrap().text() + ); + assert_eq!( + "strlen(s): Returns the length of the string s", + Token::new("help(strlen)", &mut state).unwrap().text() + ); - assert_eq!("strlen(s): Returns the length of the string s", Token::new("help('strlen')", &mut state).unwrap().text()); - assert_eq!("strlen(s): Returns the length of the string s", Token::new("help(strlen)", &mut state).unwrap().text()); - Token::new("fn(x, y) = 5x + 10(x * y)", &mut state).unwrap(); - assert_eq!("fn(x, y) = 5x + 10(x * y)", Token::new("help('fn')", &mut state).unwrap().text()); - assert_eq!("fn(x, y) = 5x + 10(x * y)", Token::new("help(fn)", &mut state).unwrap().text()); - - #[cfg(feature = "extensions")] - assert_eq!("test2(...)", Token::new("help('test2')", &mut state).unwrap().text()); - + assert_eq!( + "fn(x, y) = 5x + 10(x * y)", + Token::new("help('fn')", &mut state).unwrap().text() + ); + assert_eq!( + "fn(x, y) = 5x + 10(x * y)", + Token::new("help(fn)", &mut state).unwrap().text() + ); + #[cfg(feature = "extensions")] - assert_eq!("test2(...)", Token::new("help(test2)", &mut state).unwrap().text()); + assert_eq!( + "test(...)", + Token::new("help(test)", &mut state).unwrap().text() + ); } -} \ No newline at end of file +} diff --git a/src/functions/builtins/trig.rs b/src/functions/builtins/trig.rs index 6511dc3..f85c067 100644 --- a/src/functions/builtins/trig.rs +++ b/src/functions/builtins/trig.rs @@ -1,9 +1,13 @@ //! Builtin functions for trigonometry use super::*; -use crate::value::{Value, FloatType}; +use crate::value::{FloatType, Value}; +use crate::ExpectedTypes; -fn builtin_trig(method: fn(FloatType) -> FloatType, args: FunctionArgumentCollection) -> Result { +fn builtin_trig( + method: fn(FloatType) -> FloatType, + args: FunctionArgumentCollection, +) -> Result { let n = args.get("n").required().as_float().unwrap(); Ok(Value::Float(method(n))) } @@ -13,14 +17,17 @@ fn builtin_trig(method: fn(FloatType) -> FloatType, args: FunctionArgumentCollec mod trig_fn_macro { macro_rules! trig_fn { ($a:ident, $b:ident, $c:literal) => { - const $a : FunctionDefinition = FunctionDefinition { + const $a: FunctionDefinition = FunctionDefinition { name: stringify!($b), category: Some("math"), description: concat!("Calculate the ", $c, " of n"), - arguments: || vec![ - FunctionArgument::new_required("n", ExpectedTypes::IntOrFloat) - ], - handler: |_function, _token, _state, args| builtin_trig(FloatType::$b, args) + arguments: || { + vec![FunctionArgument::new_required( + "n", + ExpectedTypes::IntOrFloat, + )] + }, + handler: |_function, _token, _state, args| builtin_trig(FloatType::$b, args), }; }; } @@ -38,30 +45,36 @@ trig_fn!(SIN, sin, "sine"); trig_fn!(ASIN, asin, "arcsine"); trig_fn!(SINH, sinh, "hyperbolic sine"); -const TO_RADIANS : FunctionDefinition = FunctionDefinition { +const TO_RADIANS: FunctionDefinition = FunctionDefinition { name: "to_radians", category: Some("math"), description: "Convert the given degree value into radians", - arguments: || vec![ - FunctionArgument::new_required("n", ExpectedTypes::IntOrFloat) - ], + arguments: || { + vec![FunctionArgument::new_required( + "n", + ExpectedTypes::IntOrFloat, + )] + }, handler: |_function, _token, _state, args| { let n = args.get("n").required().as_float().unwrap(); Ok(Value::Float(n * (std::f64::consts::PI / 180.0))) - } + }, }; -const TO_DEGREES : FunctionDefinition = FunctionDefinition { +const TO_DEGREES: FunctionDefinition = FunctionDefinition { name: "to_degrees", category: Some("math"), description: "Convert the given radian value into degrees", - arguments: || vec![ - FunctionArgument::new_required("n", ExpectedTypes::IntOrFloat) - ], + arguments: || { + vec![FunctionArgument::new_required( + "n", + ExpectedTypes::IntOrFloat, + )] + }, handler: |_function, _token, _state, args| { let n = args.get("n").required().as_float().unwrap(); Ok(Value::Float(n * 180.0 / std::f64::consts::PI)) - } + }, }; /// Register trig functions @@ -72,11 +85,11 @@ pub fn register_functions(table: &mut FunctionTable) { table.register(TAN); table.register(ATAN); table.register(TANH); - + table.register(COS); table.register(ACOS); table.register(COSH); - + table.register(SIN); table.register(ASIN); table.register(SINH); @@ -94,74 +107,83 @@ mod test_builtin_functions { #[test] fn $test_name() { let mut state = ParserState::new(); - let vr1 = $test_fn.call(&Token::dummy(""), &mut state, &[Value::Float($vr1)]).unwrap().as_float().unwrap(); - let vr2 = $test_fn.call(&Token::dummy(""), &mut state, &[Value::Float($vr2)]).unwrap().as_float().unwrap(); - + let vr1 = $test_fn + .call(&Token::dummy(""), &mut state, &[Value::Float($vr1)]) + .unwrap() + .as_float() + .unwrap(); + let vr2 = $test_fn + .call(&Token::dummy(""), &mut state, &[Value::Float($vr2)]) + .unwrap() + .as_float() + .unwrap(); + assert_eq!(Value::Float($vl1), (100.0 * vr1).floor() / 100.0); assert_eq!(Value::Float($vl2), (100.0 * vr2).floor() / 100.0); } }; } } - + #[test] fn test_to_radians() { let mut state = ParserState::new(); - assert_eq!(Value::Float(std::f64::consts::PI), TO_RADIANS.call(&Token::dummy(""), &mut state, &[Value::Integer(180)]).unwrap()); - assert_eq!(Value::Float(4.0 * std::f64::consts::PI), TO_RADIANS.call(&Token::dummy(""), &mut state, &[Value::Integer(720)]).unwrap()); + assert_eq!( + Value::Float(std::f64::consts::PI), + TO_RADIANS + .call(&Token::dummy(""), &mut state, &[Value::Integer(180)]) + .unwrap() + ); + assert_eq!( + Value::Float(4.0 * std::f64::consts::PI), + TO_RADIANS + .call(&Token::dummy(""), &mut state, &[Value::Integer(720)]) + .unwrap() + ); } - + #[test] fn test_to_degrees() { let mut state = ParserState::new(); - assert_eq!(Value::Float(180.0), TO_DEGREES.call(&Token::dummy(""), &mut state, &[Value::Float(std::f64::consts::PI)]).unwrap()); - assert_eq!(Value::Float(90.0), TO_DEGREES.call(&Token::dummy(""), &mut state, &[Value::Float(std::f64::consts::PI / 2.0)]).unwrap()); + assert_eq!( + Value::Float(180.0), + TO_DEGREES + .call( + &Token::dummy(""), + &mut state, + &[Value::Float(std::f64::consts::PI)] + ) + .unwrap() + ); + assert_eq!( + Value::Float(90.0), + TO_DEGREES + .call( + &Token::dummy(""), + &mut state, + &[Value::Float(std::f64::consts::PI / 2.0)] + ) + .unwrap() + ); } - trig_test_fn!(test_tan, TAN, - 0.00, 0.0, - 0.99, std::f64::consts::PI / 4.0 - ); - - trig_test_fn!(test_cos, COS, - 1.00, 0.0, - 0.00, std::f64::consts::PI / 2.0 - ); - - trig_test_fn!(test_sin, SIN, - 0.00, 0.0, - 1.00, std::f64::consts::PI / 2.0 - ); - - trig_test_fn!(test_atan, ATAN, - 0.00, 0.0, - 0.66, std::f64::consts::PI / 4.0 - ); - - trig_test_fn!(test_acos, ACOS, - 0.00, 1.0, - 0.66, std::f64::consts::PI / 4.0 - ); - - trig_test_fn!(test_asin, ASIN, - 0.00, 0.0, - 0.90, std::f64::consts::PI / 4.0 - ); - - trig_test_fn!(test_tanh, TANH, - 0.00, 0.0, - 0.65, std::f64::consts::PI / 4.0 - ); - - trig_test_fn!(test_cosh, COSH, - 1.00, 0.0, - 2.50, std::f64::consts::PI / 2.0 - ); - - trig_test_fn!(test_sinh, SINH, - 0.00, 0.0, - 2.30, std::f64::consts::PI / 2.0 - ); -} \ No newline at end of file + trig_test_fn!(test_tan, TAN, 0.00, 0.0, 0.99, std::f64::consts::PI / 4.0); + + trig_test_fn!(test_cos, COS, 1.00, 0.0, 0.00, std::f64::consts::PI / 2.0); + + trig_test_fn!(test_sin, SIN, 0.00, 0.0, 1.00, std::f64::consts::PI / 2.0); + + trig_test_fn!(test_atan, ATAN, 0.00, 0.0, 0.66, std::f64::consts::PI / 4.0); + + trig_test_fn!(test_acos, ACOS, 0.00, 1.0, 0.66, std::f64::consts::PI / 4.0); + + trig_test_fn!(test_asin, ASIN, 0.00, 0.0, 0.90, std::f64::consts::PI / 4.0); + + trig_test_fn!(test_tanh, TANH, 0.00, 0.0, 0.65, std::f64::consts::PI / 4.0); + + trig_test_fn!(test_cosh, COSH, 1.00, 0.0, 2.50, std::f64::consts::PI / 2.0); + + trig_test_fn!(test_sinh, SINH, 0.00, 0.0, 2.30, std::f64::consts::PI / 2.0); +} diff --git a/src/functions/function_argument.rs b/src/functions/function_argument.rs index 9c10ef4..0d05bf8 100644 --- a/src/functions/function_argument.rs +++ b/src/functions/function_argument.rs @@ -1,22 +1,37 @@ -use crate::value::{Value}; -use crate::errors::*; +use crate::value::Value; +use crate::ExpectedTypes; -use std::ops::Index; -use std::collections::HashMap; use core::slice::Iter; +use std::collections::HashMap; +use std::ops::Index; /// Describes an argument for a callable function #[derive(Clone)] -pub struct FunctionArgument{ name: String, expected: ExpectedTypes, optional: bool, plural: bool } +pub struct FunctionArgument { + name: String, + expected: ExpectedTypes, + optional: bool, + plural: bool, +} impl FunctionArgument { /// Build a new function argument pub fn new(name: &str, expected: ExpectedTypes, optional: bool) -> Self { - Self {name: name.to_string(), expected, optional, plural: false} + Self { + name: name.to_string(), + expected, + optional, + plural: false, + } } - + /// Build a new plural function argument pub fn new_plural(name: &str, expected: ExpectedTypes, optional: bool) -> Self { - Self {name: name.to_string(), expected, optional, plural: true} + Self { + name: name.to_string(), + expected, + optional, + plural: true, + } } /// Build a new required function argument @@ -55,13 +70,13 @@ impl FunctionArgument { ExpectedTypes::Float => value.is_float(), ExpectedTypes::Int => value.is_int(), ExpectedTypes::IntOrFloat => value.is_float() || value.is_int(), - + // These can be converted from any type - ExpectedTypes::String => true, - ExpectedTypes::Boolean => true, - ExpectedTypes::Array => true, - ExpectedTypes::Object => true, - ExpectedTypes::Any => true + ExpectedTypes::String => true, + ExpectedTypes::Boolean => true, + ExpectedTypes::Array => true, + ExpectedTypes::Object => true, + ExpectedTypes::Any => true, } } } @@ -69,11 +84,15 @@ impl std::fmt::Display for FunctionArgument { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { let name = if self.plural { format!("{}1, {}2", self.name, self.name) - } else {self.name().to_string()}; - write!(f, "{}{}{}", - if self.optional {"["} else {""}, + } else { + self.name().to_string() + }; + write!( + f, + "{}{}{}", + if self.optional { "[" } else { "" }, name, - if self.optional {"]"} else {""}, + if self.optional { "]" } else { "" }, ) } } @@ -82,7 +101,7 @@ impl std::fmt::Display for FunctionArgument { pub struct FunctionArgumentValue(Vec); impl FunctionArgumentValue { /// Create a new argument value wrapper - /// + /// /// # Arguments /// * `values` - Value array pub fn new(values: Vec) -> Self { @@ -93,12 +112,12 @@ impl FunctionArgumentValue { pub fn required(&self) -> Value { self.0.first().cloned().unwrap() } - + /// Return the value as an optional argument pub fn optional(&self) -> Option { self.0.first().cloned() } - + /// Return the value as an argument or a default value pub fn optional_or(&self, default: Value) -> Value { self.0.first().cloned().unwrap_or(default) @@ -115,21 +134,21 @@ pub struct FunctionArgumentCollection { values: Vec, map: HashMap>, - next_index: usize + next_index: usize, } impl FunctionArgumentCollection { /// Return a new empty collection pub fn new() -> Self { - Self{ + Self { values: Vec::::new(), map: HashMap::new(), - next_index: 0 + next_index: 0, } } /// Add a new value to the table - /// + /// /// # Arguments /// * `name` - Function argument key /// * `value` - Function value @@ -137,23 +156,23 @@ impl FunctionArgumentCollection { match self.map.get_mut(&name) { Some(v) => { v.push(value.clone()); - }, + } None => { self.map.insert(name.clone(), vec![value.clone()]); } } - + self.values.push(value); } /// Get a value from the table - /// + /// /// # Arguments /// * `name` - Function argument key pub fn get(&self, name: &str) -> FunctionArgumentValue { FunctionArgumentValue::new(match self.map.get(name).cloned() { Some(v) => v, - None => Vec::new() + None => Vec::new(), }) } @@ -208,4 +227,4 @@ impl Iterator for FunctionArgumentCollection { Some(self[self.next_index - 1].clone()) } } -} \ No newline at end of file +} diff --git a/src/functions/function_definition.rs b/src/functions/function_definition.rs index d33c26f..e434725 100644 --- a/src/functions/function_definition.rs +++ b/src/functions/function_definition.rs @@ -1,18 +1,85 @@ -use crate::{ParserState, Value, Token}; -use crate::errors::*; use super::{FunctionArgument, FunctionArgumentCollection, FunctionHandler}; +use crate::Error; +use crate::{ParserState, Token, Value}; -const DEFAULT_CATEGORY : &str = "misc"; +#[macro_use] +pub mod function_macros { + /// Internal macro for function definitions + /// See define_function + #[macro_export] + macro_rules! _define_function_category { + () => { + None + }; + + ($cat:literal) => { + Some($cat) + }; + } + + /// Describes the requirements of an argument to a builtin function + #[macro_export] + macro_rules! function_arg { + ($name:literal:$type:ident) => { + FunctionArgument::new($name, crate::ExpectedTypes::$type, false) + }; + + ("plural", $name:literal:$type:ident) => { + FunctionArgument::new_plural($name, crate::ExpectedTypes::$type, false) + }; + + ("optional", $name:literal:$type:ident) => { + FunctionArgument::new($name, crate::ExpectedTypes::$type, true) + }; + + ("plural+optional", $name:literal:$type:ident) => { + FunctionArgument::new_plural($name, crate::ExpectedTypes::$type, true) + }; + } + + /// Defines a function for registration as a builtin + #[macro_export] + macro_rules! define_function { + ( + name = $function_name:ident, + $(category = $function_cat:expr,)? + description = $function_desc:literal, + arguments = [$($function_arg:expr),*], + handler = $function_impl:expr + ) => { + /// Builtin-function definition for use with Lavendeux + /// It should be registered with 'function_table.register( + #[doc = stringify!($function_name)] + /// ); + #[allow(non_upper_case_globals, unused_variables)] + const $function_name: FunctionDefinition = FunctionDefinition { + name: stringify!($function_name), + category: $crate::_define_function_category!($($function_cat)?), + description: "Returns the SHA256 hash of a given string", + arguments: || { + vec![FunctionArgument::new_plural( + "input", + crate::ExpectedTypes::Any, + false, + )] + }, + handler: $function_impl, + }; + }; + } +} + +const DEFAULT_CATEGORY: &str = "misc"; /// Holds the definition of a builtin callable function #[derive(Clone)] pub struct FunctionDefinition { /// Function call name pub name: &'static str, - + /// Function category pub category: Option<&'static str>, - + /// Function short description pub description: &'static str, @@ -20,19 +87,19 @@ pub struct FunctionDefinition { pub arguments: fn() -> Vec, /// Handler function - pub handler: FunctionHandler + pub handler: FunctionHandler, } impl FunctionDefinition { /// Return the function's name pub fn name(&self) -> &str { self.name } - + /// Return the function's description pub fn description(&self) -> &str { self.description } - + /// Return the function's category pub fn category(&self) -> &str { self.category.unwrap_or(DEFAULT_CATEGORY) @@ -42,58 +109,83 @@ impl FunctionDefinition { pub fn args(&self) -> Vec { (self.arguments)() } - + /// Return the function's signature pub fn signature(&self) -> String { - format!("{}({})", self.name, self.args().iter().map(|e| e.to_string()).collect::>().join(", ")) + format!( + "{}({})", + self.name, + self.args() + .iter() + .map(|e| e.to_string()) + .collect::>() + .join(", ") + ) } - + /// Return the function's help string pub fn help(&self) -> String { format!("{}: {}", self.signature(), self.description()) } /// Validate function arguments, and return the collected arguments - /// + /// /// # Arguments /// * `args` - Function arguments - pub fn collect(&self, token: &Token, args: &[Value]) -> Result { + pub fn collect( + &self, + token: &Token, + args: &[Value], + ) -> Result { let optional_arguments = self.args().iter().filter(|e| e.optional()).count(); let plural_arguments = self.args().iter().filter(|e| e.plural()).count(); let max_arguments = self.args().len(); let min_arguments = max_arguments - optional_arguments; // Prevent ambiguities resulting from plural args - if plural_arguments > 1 { - return Err(AmbiguousFunctionError::new(token, self.name(), "only one plural argument allowed in a function").into()); - } else if plural_arguments == 1 && !self.args().last().unwrap().plural() { - return Err(AmbiguousFunctionError::new(token, self.name(), "plural argument must be the last function argument").into()); + if plural_arguments > 1 || (plural_arguments == 1 && !self.args().last().unwrap().plural()) + { + return Err(Error::AmbiguousFunctionDefinition { + signature: self.signature(), + token: token.clone(), + }); } // Argument count if args.len() < min_arguments || (plural_arguments == 0 && args.len() > max_arguments) { - return Err(FunctionNArgsError::new(token, &self.signature(), min_arguments, max_arguments).into()) + return Err(Error::FunctionArguments { + min: min_arguments, + max: max_arguments, + signature: self.signature(), + token: token.clone(), + }); } // Collect argument values let mut arg_iter = args.iter(); let mut argument_collection = FunctionArgumentCollection::new(); for (args_consumed, arg) in self.args().into_iter().enumerate() { - let values: Vec = arg_iter.by_ref().take(if arg.plural() {args.len() - args_consumed} else {1}).cloned() + let values: Vec = arg_iter + .by_ref() + .take(if arg.plural() { + args.len() - args_consumed + } else { + 1 + }) + .cloned() .collect(); - // Validate types for value in values { if arg.validate_value(&value) { argument_collection.add(arg.name().to_string(), value.clone()); } else { - return Err(FunctionArgTypeError::new( - token, - &self.signature(), - args_consumed+1, - arg.expected().clone() - ).into()); + return Err(Error::FunctionArgumentType { + arg: args_consumed + 1, + expected_type: *arg.expected(), + signature: self.signature(), + token: token.clone(), + }); } } } @@ -102,13 +194,18 @@ impl FunctionDefinition { } // Call the associated function handler - /// + /// /// # Arguments /// * `args` - Function arguments - pub fn call(&self, token: &Token, state: &mut ParserState, args: &[Value]) -> Result { + pub fn call( + &self, + token: &Token, + state: &mut ParserState, + args: &[Value], + ) -> Result { match self.collect(token, args) { Ok(a) => (self.handler)(self, token, state, a), - Err(e) => Err(e) + Err(e) => Err(e), } } -} \ No newline at end of file +} diff --git a/src/functions/function_table.rs b/src/functions/function_table.rs index 6e21dcf..b23ef64 100644 --- a/src/functions/function_table.rs +++ b/src/functions/function_table.rs @@ -1,9 +1,9 @@ -use crate::{ParserState, Value, Token}; -use crate::errors::*; +use crate::Error; +use crate::{ParserState, Token, Value}; use std::collections::HashMap; -use super::FunctionDefinition; use super::builtins; +use super::FunctionDefinition; /// Holds a set of callable functions #[derive(Clone)] @@ -11,7 +11,7 @@ pub struct FunctionTable(HashMap); impl FunctionTable { /// Initialize a new function table, complete with default builtin functions pub fn new() -> FunctionTable { - let mut table : FunctionTable = FunctionTable(HashMap::new()); + let mut table: FunctionTable = FunctionTable(HashMap::new()); table.register_builtins(); table } @@ -30,7 +30,7 @@ impl FunctionTable { } /// Register a function in the table - /// + /// /// # Arguments /// * `name` - Function name /// * `handler` - Function handler @@ -39,7 +39,7 @@ impl FunctionTable { } /// Remove a function from the table - /// + /// /// # Arguments /// * `name` - Function name pub fn remove(&mut self, name: &str) { @@ -47,7 +47,7 @@ impl FunctionTable { } /// Check if the table contains a function by the given name - /// + /// /// # Arguments /// * `name` - Function name pub fn has(&self, name: &str) -> bool { @@ -55,7 +55,7 @@ impl FunctionTable { } /// Return a given function - /// + /// /// # Arguments /// * `name` - Function name pub fn get(&self, name: &str) -> Option<&FunctionDefinition> { @@ -64,8 +64,8 @@ impl FunctionTable { /// Get a collection of all included functions pub fn all(&self) -> Vec<&FunctionDefinition> { - let mut a: Vec<&FunctionDefinition> = self.0.values().collect(); - a.sort_by(|f1, f2|f1.name().cmp(f2.name())); + let mut a: Vec<&FunctionDefinition> = self.0.values().collect(); + a.sort_by(|f1, f2| f1.name().cmp(f2.name())); a } @@ -79,33 +79,47 @@ impl FunctionTable { /// Return all included functions sorted by category pub fn all_by_category(&self) -> HashMap<&str, Vec<&FunctionDefinition>> { - let f: Vec<(&str, Vec<&FunctionDefinition>)> = self.all_categories().iter().map( - |c| (*c, - self.all() - .iter() - .filter(|f| f.category() == *c) - .copied() - .collect::>() - ) - ).collect(); + let f: Vec<(&str, Vec<&FunctionDefinition>)> = self + .all_categories() + .iter() + .map(|c| { + ( + *c, + self.all() + .iter() + .filter(|f| f.category() == *c) + .copied() + .collect::>(), + ) + }) + .collect(); let m: HashMap<_, _> = f.into_iter().collect(); m } /// Call a function - /// + /// /// # Arguments /// * `name` - Function name /// * `args` - Function arguments - pub fn call(&self, name: &str, token: &Token, state: &mut ParserState, args: &[Value]) -> Result { + pub fn call( + &self, + name: &str, + token: &Token, + state: &mut ParserState, + args: &[Value], + ) -> Result { match self.0.get(name) { Some(f) => f.call(token, state, args), - None => Err(FunctionNameError::new(token, name).into()) + None => Err(Error::FunctionName { + name: name.to_string(), + token: token.clone(), + }), } } /// Return a function's signature - /// + /// /// # Arguments /// * `name` - Function name pub fn signature(&self, name: &str) -> Option { @@ -113,7 +127,7 @@ impl FunctionTable { } /// Return a function's description - /// + /// /// # Arguments /// * `name` - Function name pub fn description(&self, name: &str) -> Option { @@ -125,4 +139,4 @@ impl Default for FunctionTable { fn default() -> Self { Self::new() } -} \ No newline at end of file +} diff --git a/src/functions.rs b/src/functions/mod.rs similarity index 51% rename from src/functions.rs rename to src/functions/mod.rs index 3e66cae..6b69617 100644 --- a/src/functions.rs +++ b/src/functions/mod.rs @@ -1,8 +1,13 @@ -use crate::{Value, ParserState, Token}; -use super::errors::*; +use super::Error; +use crate::{ParserState, Token, Value}; /// Handler for executing a builtin function -pub type FunctionHandler = fn(&FunctionDefinition, &Token, state: &mut ParserState, FunctionArgumentCollection) -> Result; +pub type FunctionHandler = fn( + &FunctionDefinition, + &Token, + state: &mut ParserState, + FunctionArgumentCollection, +) -> Result; mod function_argument; pub use function_argument::*; @@ -18,34 +23,37 @@ pub use builtins::*; #[cfg(test)] mod test_builtin_table { + use crate::ExpectedTypes; + use super::*; - const EXAMPLE : FunctionDefinition = FunctionDefinition { + const EXAMPLE: FunctionDefinition = FunctionDefinition { name: "example", category: None, description: "Sample function", - arguments: || vec![ - FunctionArgument::new_required("n", ExpectedTypes::IntOrFloat), - ], - handler: |_function, _token, _state, _args| { - Ok(Value::Integer(4)) - } + arguments: || { + vec![FunctionArgument::new_required( + "n", + ExpectedTypes::IntOrFloat, + )] + }, + handler: |_function, _token, _state, _args| Ok(Value::Integer(4)), }; - + #[test] fn test_register() { let mut table = FunctionTable::new(); table.register(EXAMPLE); assert_eq!(true, table.has("example")); } - + #[test] fn test_has() { let mut table = FunctionTable::new(); table.register(EXAMPLE); assert_eq!(true, table.has("example")); } - + #[test] fn test_call() { let mut state = ParserState::new(); @@ -55,7 +63,16 @@ mod test_builtin_table { let token = Token::dummy(""); table.call("example", &token, &mut state, &[]).unwrap_err(); - table.call("example", &token, &mut state, &[Value::String("".to_string())]).unwrap_err(); - table.call("example", &token, &mut state, &[Value::Integer(4)]).unwrap(); + table + .call( + "example", + &token, + &mut state, + &[Value::String("".to_string())], + ) + .unwrap_err(); + table + .call("example", &token, &mut state, &[Value::Integer(4)]) + .unwrap(); } -} \ No newline at end of file +} diff --git a/src/handlers/bitwise.rs b/src/handlers/bitwise.rs index 2992e5f..e87c19c 100644 --- a/src/handlers/bitwise.rs +++ b/src/handlers/bitwise.rs @@ -1,11 +1,10 @@ use std::collections::HashMap; -use super::{ RuleHandler, perform_int_calculation }; +use super::{perform_int_calculation, RuleHandler}; use crate::{ - token::{Rule, Token}, state::ParserState, - IntegerType, - errors::* + token::{Rule, Token}, + Error, ExpectedTypes, IntegerType, }; pub fn handler_table() -> HashMap { @@ -20,27 +19,37 @@ pub fn handler_table() -> HashMap { /// A bitwise shift expression /// x << 3 /// x >> 3 -fn rule_sh_expression(token: &mut Token, _state: &mut ParserState) -> Option { +fn rule_sh_expression(token: &mut Token, _state: &mut ParserState) -> Option { token.set_value(token.child(0).unwrap().value()); if token.children().len() > 1 { let mut i = 2; while i < token.children().len() { let ih = match token.child(i - 1).unwrap().rule() { - Rule::lshift => |l:IntegerType, r:IntegerType| Some(l << r), - Rule::rshift => |l:IntegerType, r:IntegerType| Some(l >> r), - _ => return Some(InternalError::new(token).into()) + Rule::lshift => |l: IntegerType, r: IntegerType| Some(l << r), + Rule::rshift => |l: IntegerType, r: IntegerType| Some(l >> r), + _ => return Some(Error::Internal(token.clone())), }; if token.value().is_float() { - return Some(ValueTypeError::new(token, ExpectedTypes::Int).into()); + return Some(Error::ValueType { + value: token.value(), + expected_type: ExpectedTypes::Int, + token: token.clone(), + }); } else if token.child(i).unwrap().value().is_float() { - return Some(ValueTypeError::new(token.child(i).unwrap(), ExpectedTypes::Int).into()); + let token = token.child(i).unwrap(); + return Some(Error::ValueType { + value: token.value(), + expected_type: ExpectedTypes::Int, + token: token.clone(), + }); } - match perform_int_calculation(token, token.value(), token.child(i).unwrap().value(), ih) { + match perform_int_calculation(token, token.value(), token.child(i).unwrap().value(), ih) + { Ok(n) => token.set_value(n), - Err(e) => return Some(e) + Err(e) => return Some(e), } i += 2; @@ -52,19 +61,29 @@ fn rule_sh_expression(token: &mut Token, _state: &mut ParserState) -> Option Option { +fn rule_and_expression(token: &mut Token, _state: &mut ParserState) -> Option { token.set_value(token.child(0).unwrap().value()); if token.children().len() > 1 { let mut i = 2; while i < token.children().len() { if token.value().is_float() || token.child(i).unwrap().value().is_float() { - return Some(ValueTypeError::new(token, ExpectedTypes::IntOrFloat).into()); + let token = token.child(i).unwrap(); + return Some(Error::ValueType { + value: token.value(), + expected_type: ExpectedTypes::IntOrFloat, + token: token.clone(), + }); } - match perform_int_calculation(token, token.value(), token.child(i).unwrap().value(), |l:IntegerType, r:IntegerType| Some(l & r)) { + match perform_int_calculation( + token, + token.value(), + token.child(i).unwrap().value(), + |l: IntegerType, r: IntegerType| Some(l & r), + ) { Ok(n) => token.set_value(n), - Err(e) => return Some(e) + Err(e) => return Some(e), } i += 2; @@ -76,19 +95,28 @@ fn rule_and_expression(token: &mut Token, _state: &mut ParserState) -> Option Option { +fn rule_xor_expression(token: &mut Token, _state: &mut ParserState) -> Option { token.set_value(token.child(0).unwrap().value()); if token.children().len() > 1 { let mut i = 2; while i < token.children().len() { if token.value().is_float() || token.child(i).unwrap().value().is_float() { - return Some(ValueTypeError::new(token, ExpectedTypes::Int).into()); + return Some(Error::ValueType { + value: token.value(), + expected_type: ExpectedTypes::Int, + token: token.clone(), + }); } - match perform_int_calculation(token, token.value(), token.child(i).unwrap().value(), |l:IntegerType, r:IntegerType| Some(l ^ r)) { + match perform_int_calculation( + token, + token.value(), + token.child(i).unwrap().value(), + |l: IntegerType, r: IntegerType| Some(l ^ r), + ) { Ok(n) => token.set_value(n), - Err(e) => return Some(e) + Err(e) => return Some(e), } i += 2; @@ -100,19 +128,28 @@ fn rule_xor_expression(token: &mut Token, _state: &mut ParserState) -> Option Option { +fn rule_or_expression(token: &mut Token, _state: &mut ParserState) -> Option { token.set_value(token.child(0).unwrap().value()); if token.children().len() > 1 { let mut i = 2; while i < token.children().len() { if token.value().is_float() || token.child(i).unwrap().value().is_float() { - return Some(ValueTypeError::new(token, ExpectedTypes::Int).into()); + return Some(Error::ValueType { + value: token.value(), + expected_type: ExpectedTypes::Int, + token: token.clone(), + }); } - match perform_int_calculation(token, token.value(), token.child(i).unwrap().value(), |l:IntegerType, r:IntegerType| Some(l | r)) { + match perform_int_calculation( + token, + token.value(), + token.child(i).unwrap().value(), + |l: IntegerType, r: IntegerType| Some(l | r), + ) { Ok(n) => token.set_value(n), - Err(e) => return Some(e) + Err(e) => return Some(e), } i += 2; @@ -124,15 +161,24 @@ fn rule_or_expression(token: &mut Token, _state: &mut ParserState) -> Option> [1,2]", Value::from(vec![Value::from(2), Value::from(1)])); - assert_token_value!("[4,16] >> [1,2]", Value::from(vec![Value::from(2), Value::from(4)])); - assert_token_value!("[4,8] >> 2", Value::from(vec![Value::from(1), Value::from(2)])); + assert_token_value!( + "4 >> [1,2]", + Value::from(vec![Value::from(2), Value::from(1)]) + ); + assert_token_value!( + "[4,16] >> [1,2]", + Value::from(vec![Value::from(2), Value::from(4)]) + ); + assert_token_value!( + "[4,8] >> 2", + Value::from(vec![Value::from(1), Value::from(2)]) + ); // Integer values assert_token_value!("4 >> 1", Value::from(2)); @@ -148,9 +194,18 @@ mod test_token { #[test] fn rule_and_expression() { // Array values - assert_token_value!("0xFF & [0xF0,0x0F]", Value::from(vec![Value::from(0xF0), Value::from(0x0F)])); - assert_token_value!("[0xF0,0x0F] & [0xA0,0x0A]", Value::from(vec![Value::from(0xA0), Value::from(0x0A)])); - assert_token_value!("[0xF0,0x0F] & 0xFF", Value::from(vec![Value::from(0xF0), Value::from(0x0F)])); + assert_token_value!( + "0xFF & [0xF0,0x0F]", + Value::from(vec![Value::from(0xF0), Value::from(0x0F)]) + ); + assert_token_value!( + "[0xF0,0x0F] & [0xA0,0x0A]", + Value::from(vec![Value::from(0xA0), Value::from(0x0A)]) + ); + assert_token_value!( + "[0xF0,0x0F] & 0xFF", + Value::from(vec![Value::from(0xF0), Value::from(0x0F)]) + ); // Integer values assert_token_value!("0xA & 0xF", Value::from(0xA)); @@ -162,33 +217,58 @@ mod test_token { assert_token_error!("false & 1", ValueType); assert_token_error!("4 & 'test'", ValueType); - - let mut state = ParserState::new(); - assert_eq!(Value::Array(vec![ - Value::Integer(15), Value::Integer(0), - ]), Token::new("0xFF & [0x0F, 0]", &mut state).unwrap().value()); - - assert_eq!(Value::Integer(15), Token::new("0xFF & 0x0F", &mut state).unwrap().value()); - assert_eq!(Value::Integer(8), Token::new("0b1100 & 0b1110 & 0b1000", &mut state).unwrap().value()); + assert_eq!( + Value::Array(vec![Value::Integer(15), Value::Integer(0),]), + Token::new("0xFF & [0x0F, 0]", &mut state).unwrap().value() + ); + + assert_eq!( + Value::Integer(15), + Token::new("0xFF & 0x0F", &mut state).unwrap().value() + ); + assert_eq!( + Value::Integer(8), + Token::new("0b1100 & 0b1110 & 0b1000", &mut state) + .unwrap() + .value() + ); } #[test] fn rule_xor_expression() { let mut state = ParserState::new(); - assert_eq!(Value::Array(vec![ - Value::Integer(240), Value::Integer(255), - ]), Token::new("0xFF ^ [0x0F, 0]", &mut state).unwrap().value()); - - assert_eq!(Value::Integer(240), Token::new("0xFF ^ 0x0F", &mut state).unwrap().value()); - assert_eq!(Value::Integer(80), Token::new("0xFF ^ 0x0F ^ 0xA0", &mut state).unwrap().value()); + assert_eq!( + Value::Array(vec![Value::Integer(240), Value::Integer(255),]), + Token::new("0xFF ^ [0x0F, 0]", &mut state).unwrap().value() + ); + + assert_eq!( + Value::Integer(240), + Token::new("0xFF ^ 0x0F", &mut state).unwrap().value() + ); + assert_eq!( + Value::Integer(80), + Token::new("0xFF ^ 0x0F ^ 0xA0", &mut state) + .unwrap() + .value() + ); // Array values - assert_token_value!("0xFF ^ [0x0F, 0]", Value::from(vec![Value::from(0xF0), Value::from(0xFF)])); - assert_token_value!("[0x0F, 0] ^ [0xFF, 0xFF]", Value::from(vec![Value::from(0xF0), Value::from(0xFF)])); - assert_token_value!("[0x0F, 0] ^ 0xFF", Value::from(vec![Value::from(0xF0), Value::from(0xFF)])); + assert_token_value!( + "0xFF ^ [0x0F, 0]", + Value::from(vec![Value::from(0xF0), Value::from(0xFF)]) + ); + assert_token_value!( + "[0x0F, 0] ^ [0xFF, 0xFF]", + Value::from(vec![Value::from(0xF0), Value::from(0xFF)]) + ); + assert_token_value!( + "[0x0F, 0] ^ 0xFF", + Value::from(vec![Value::from(0xF0), Value::from(0xFF)]) + ); // Integer values assert_token_value!("0xFF ^ 0x0F", Value::from(0xF0)); @@ -203,9 +283,18 @@ mod test_token { #[test] fn rule_or_expression() { // Array values - assert_token_value!("0xFF00 | [0x00F0,0x000F]", Value::from(vec![Value::from(0xFFF0), Value::from(0xFF0F)])); - assert_token_value!("[0x00F0,0x000F] | [0xF000,0x0F00]", Value::from(vec![Value::from(0xF0F0), Value::from(0x0F0F)])); - assert_token_value!("[0x00F0,0x000F] | 0xFF00", Value::from(vec![Value::from(0xFFF0), Value::from(0xFF0F)])); + assert_token_value!( + "0xFF00 | [0x00F0,0x000F]", + Value::from(vec![Value::from(0xFFF0), Value::from(0xFF0F)]) + ); + assert_token_value!( + "[0x00F0,0x000F] | [0xF000,0x0F00]", + Value::from(vec![Value::from(0xF0F0), Value::from(0x0F0F)]) + ); + assert_token_value!( + "[0x00F0,0x000F] | 0xFF00", + Value::from(vec![Value::from(0xFFF0), Value::from(0xFF0F)]) + ); // Integer values assert_token_value!("0x0A | 0xF0", Value::from(0xFA)); @@ -217,4 +306,4 @@ mod test_token { assert_token_error!("false | 1", ValueType); assert_token_error!("4 | 'test'", ValueType); } -} \ No newline at end of file +} diff --git a/src/handlers/boolean.rs b/src/handlers/boolean.rs index ac1b444..d074a71 100644 --- a/src/handlers/boolean.rs +++ b/src/handlers/boolean.rs @@ -2,46 +2,47 @@ use std::collections::HashMap; use super::RuleHandler; use crate::{ - token::{Rule, Token, OutputFormat}, state::ParserState, - Value, - errors::* + token::{OutputFormat, Rule, Token}, + Error, Value, }; pub fn handler_table() -> HashMap { HashMap::from([ - (Rule::bool_cmp_expression, rule_bool_cmp_expression as RuleHandler), - (Rule::bool_and_expression, rule_bool_and_expression as RuleHandler), - (Rule::bool_or_expression, rule_bool_or_expression as RuleHandler), + ( + Rule::bool_cmp_expression, + rule_bool_cmp_expression as RuleHandler, + ), + ( + Rule::bool_and_expression, + rule_bool_and_expression as RuleHandler, + ), + ( + Rule::bool_or_expression, + rule_bool_or_expression as RuleHandler, + ), ]) } /// A boolean comparison /// x < 3 /// x == 3 -fn rule_bool_cmp_expression(token: &mut Token, _state: &mut ParserState) -> Option { - +fn rule_bool_cmp_expression(token: &mut Token, _state: &mut ParserState) -> Option { let mut i = 0; token.set_value(token.child(i).unwrap().value()); while i < token.children().len() - 2 { let l = token.value(); - let r = token.child(i+2).unwrap().value(); - - token.set_value( - Value::Boolean( - match token.child(i+1).unwrap().rule() { - Rule::lt => l.lt(&r), - Rule::gt => l.gt(&r), - Rule::eq => l.eq(&r), - Rule::ne => l.ne(&r), - Rule::ge => l.ge(&r), - Rule::le => l.le(&r), - _ => { - return Some(InternalError::new(token).into()) - } - } - ) - ); + let r = token.child(i + 2).unwrap().value(); + + token.set_value(Value::Boolean(match token.child(i + 1).unwrap().rule() { + Rule::lt => l.lt(&r), + Rule::gt => l.gt(&r), + Rule::eq => l.eq(&r), + Rule::ne => l.ne(&r), + Rule::ge => l.ge(&r), + Rule::le => l.le(&r), + _ => return Some(Error::Internal(token.clone()).into()), + })); i += 2; } @@ -52,11 +53,13 @@ fn rule_bool_cmp_expression(token: &mut Token, _state: &mut ParserState) -> Opti /// A boolean and expression /// a && b -fn rule_bool_and_expression(token: &mut Token, _state: &mut ParserState) -> Option { +fn rule_bool_and_expression(token: &mut Token, _state: &mut ParserState) -> Option { let mut i = 0; token.set_value(token.child(i).unwrap().value()); while i < token.children().len() - 2 { - token.set_value(Value::Boolean(token.value().as_bool() && token.child(i+2).unwrap().value().as_bool())); + token.set_value(Value::Boolean( + token.value().as_bool() && token.child(i + 2).unwrap().value().as_bool(), + )); i += 2 } @@ -66,11 +69,13 @@ fn rule_bool_and_expression(token: &mut Token, _state: &mut ParserState) -> Opti /// A boolean or expression /// a || b -fn rule_bool_or_expression(token: &mut Token, _state: &mut ParserState) -> Option { +fn rule_bool_or_expression(token: &mut Token, _state: &mut ParserState) -> Option { let mut i = 0; token.set_value(token.child(i).unwrap().value()); while i < token.children().len() - 2 { - token.set_value(Value::Boolean(token.value().as_bool() || token.child(i+2).unwrap().value().as_bool())); + token.set_value(Value::Boolean( + token.value().as_bool() || token.child(i + 2).unwrap().value().as_bool(), + )); i += 2 } @@ -80,12 +85,12 @@ fn rule_bool_or_expression(token: &mut Token, _state: &mut ParserState) -> Optio #[cfg(test)] mod test_token { - use crate::{Value, test::assert_token_value}; use super::*; use crate::test::*; + use crate::{test::assert_token_value, Value}; #[test] - fn rule_bool_cmp_expression() { + fn rule_bool_cmp_expression() { assert_token_value!("'a' < 'b'", Value::from(true)); assert_token_value!("'b' < 'a'", Value::from(false)); assert_token_value!("'a' > 'b'", Value::from(false)); @@ -96,7 +101,7 @@ mod test_token { assert_token_value!("'a' != 'a'", Value::from(false)); assert_token_value!("'a' >= 'a'", Value::from(true)); assert_token_value!("'a' <= 'b'", Value::from(true)); - + assert_token_value!("false < true", Value::from(true)); assert_token_value!("true < false", Value::from(false)); assert_token_value!("false > true", Value::from(false)); @@ -107,7 +112,7 @@ mod test_token { assert_token_value!("false != false", Value::from(false)); assert_token_value!("false >= false", Value::from(true)); assert_token_value!("false <= true", Value::from(true)); - + assert_token_value!("1 < 2", Value::from(true)); assert_token_value!("2 < 1", Value::from(false)); assert_token_value!("1 > 2", Value::from(false)); @@ -118,7 +123,7 @@ mod test_token { assert_token_value!("1 != 1", Value::from(false)); assert_token_value!("1 >= 1", Value::from(true)); assert_token_value!("1 <= 1", Value::from(true)); - + assert_token_value!("1.3 < 2", Value::from(true)); assert_token_value!("2 < 1.3", Value::from(false)); assert_token_value!("1.3 > 2", Value::from(false)); @@ -129,7 +134,7 @@ mod test_token { assert_token_value!("1.3 != 1.3", Value::from(false)); assert_token_value!("1.3 >= 1.3", Value::from(true)); assert_token_value!("1.3 <= 1.3", Value::from(true)); - + assert_token_value!("'test' == 1", Value::from(false)); } @@ -152,4 +157,4 @@ mod test_token { assert_token_value!("false || false || false || false", Value::from(false)); assert_token_value!("false || false || false || true", Value::from(true)); } -} \ No newline at end of file +} diff --git a/src/handlers/errors.rs b/src/handlers/errors.rs index 64d1b8f..df2ac6a 100644 --- a/src/handlers/errors.rs +++ b/src/handlers/errors.rs @@ -2,56 +2,77 @@ use std::collections::HashMap; use super::RuleHandler; use crate::{ - token::{Rule, Token}, state::ParserState, - errors::* + token::{Rule, Token}, + Error, }; pub fn handler_table() -> HashMap { HashMap::from([ - (Rule::error_unterminated_literal, rule_error_unterminated_literal as RuleHandler), - (Rule::error_unterminated_linebreak, rule_error_unterminated_linebreak as RuleHandler), - (Rule::error_unterminated_array, rule_error_unterminated_array as RuleHandler), - (Rule::error_unterminated_object, rule_error_unterminated_object as RuleHandler), - (Rule::error_unterminated_paren, rule_error_unterminated_paren as RuleHandler), - (Rule::error_unexpected_decorator, rule_error_unexpected_decorator as RuleHandler), - (Rule::error_unexpected_postfix, rule_error_unexpected_postfix as RuleHandler), + ( + Rule::error_unterminated_literal, + rule_error_unterminated_literal as RuleHandler, + ), + ( + Rule::error_unterminated_linebreak, + rule_error_unterminated_linebreak as RuleHandler, + ), + ( + Rule::error_unterminated_array, + rule_error_unterminated_array as RuleHandler, + ), + ( + Rule::error_unterminated_object, + rule_error_unterminated_object as RuleHandler, + ), + ( + Rule::error_unterminated_paren, + rule_error_unterminated_paren as RuleHandler, + ), + ( + Rule::error_unexpected_decorator, + rule_error_unexpected_decorator as RuleHandler, + ), + ( + Rule::error_unexpected_postfix, + rule_error_unexpected_postfix as RuleHandler, + ), ]) } /// Catches unterminated string literals -fn rule_error_unterminated_literal(token: &mut Token, _state: &mut ParserState) -> Option { - Some(UnterminatedLiteralError::new(token).into()) +fn rule_error_unterminated_literal(token: &mut Token, _state: &mut ParserState) -> Option { + Some(Error::UnterminatedLiteral(token.clone()).into()) } /// Catches unterminated linebreaks -fn rule_error_unterminated_linebreak(token: &mut Token, _state: &mut ParserState) -> Option { - Some(UnterminatedLinebreakError::new(token).into()) +fn rule_error_unterminated_linebreak(token: &mut Token, _state: &mut ParserState) -> Option { + Some(Error::UnterminatedLinebreak(token.clone()).into()) } /// Catches unterminated arrays -fn rule_error_unterminated_array(token: &mut Token, _state: &mut ParserState) -> Option { - Some(UnterminatedArrayError::new(token).into()) +fn rule_error_unterminated_array(token: &mut Token, _state: &mut ParserState) -> Option { + Some(Error::UnterminatedArray(token.clone()).into()) } /// Catches unterminated objects -fn rule_error_unterminated_object(token: &mut Token, _state: &mut ParserState) -> Option { - Some(UnterminatedObjectError::new(token).into()) +fn rule_error_unterminated_object(token: &mut Token, _state: &mut ParserState) -> Option { + Some(Error::UnterminatedObject(token.clone()).into()) } /// Catches unterminated parens -fn rule_error_unterminated_paren(token: &mut Token, _state: &mut ParserState) -> Option { - Some(UnterminatedParenError::new(token).into()) +fn rule_error_unterminated_paren(token: &mut Token, _state: &mut ParserState) -> Option { + Some(Error::UnterminatedParen(token.clone()).into()) } /// Catches decorator errors -fn rule_error_unexpected_decorator(token: &mut Token, _state: &mut ParserState) -> Option { - Some(UnexpectedDecoratorError::new(token).into()) +fn rule_error_unexpected_decorator(token: &mut Token, _state: &mut ParserState) -> Option { + Some(Error::UnexpectedDecorator(token.clone()).into()) } /// Catches postfix errors -fn rule_error_unexpected_postfix(token: &mut Token, _state: &mut ParserState) -> Option { - Some(UnexpectedPostfixError::new(token).into()) +fn rule_error_unexpected_postfix(token: &mut Token, _state: &mut ParserState) -> Option { + Some(Error::UnexpectedPostfix(token.clone()).into()) } #[cfg(test)] @@ -100,4 +121,4 @@ mod test_token { fn test_rule_error_unexpected_postfix() { assert_token_error!("!1", UnexpectedPostfix); } -} \ No newline at end of file +} diff --git a/src/handlers/functions.rs b/src/handlers/functions.rs index efaa8a1..1cc29f7 100644 --- a/src/handlers/functions.rs +++ b/src/handlers/functions.rs @@ -2,26 +2,23 @@ use std::collections::HashMap; use super::RuleHandler; use crate::{ - token::{Rule, Token}, state::ParserState, - Value, - errors::* + token::{Rule, Token}, + Error, Value, }; pub fn handler_table() -> HashMap { - HashMap::from([ - (Rule::call_expression, rule_call_expression as RuleHandler), - ]) + HashMap::from([(Rule::call_expression, rule_call_expression as RuleHandler)]) } -fn rule_call_expression(token: &mut Token, state: &mut ParserState) -> Option { +fn rule_call_expression(token: &mut Token, state: &mut ParserState) -> Option { // Get function name and arguments let name = &token.child(0).unwrap().text().to_string(); let mut arg_tokens = Vec::<&Token>::new(); - let mut args : Vec = Vec::new(); + let mut args: Vec = Vec::new(); match token.child(2).unwrap().rule() { - Rule::rparen => { }, + Rule::rparen => {} Rule::expression_list => { let mut i = 0; while i < token.child(2).unwrap().children().len() { @@ -30,7 +27,7 @@ fn rule_call_expression(token: &mut Token, state: &mut ParserState) -> Option { let t = token.child(2).unwrap(); args.push(t.value()); @@ -41,11 +38,14 @@ fn rule_call_expression(token: &mut Token, state: &mut ParserState) -> Option { token.set_value(v); - return None - }, + return None; + } Err(e) => return Some(e), } } @@ -56,16 +56,21 @@ fn rule_call_expression(token: &mut Token, state: &mut ParserState) -> Option { token.set_value(v); - return None - }, + return None; + } Err(e) => return Some(e), } } - + // User functions if let Some(f) = state.user_functions.get(name) { if args.len() != f.arguments().len() { - return Some(FunctionNArgsError::new(token, f.name(), f.arguments().len(), f.arguments().len()).into()) + return Some(Error::FunctionArguments { + min: f.arguments().len(), + max: f.arguments().len(), + signature: f.signature(), + token: token.clone(), + }); } else if let Some(mut inner_state) = state.spawn_inner() { // Populate arguments for (i, arg) in f.arguments().clone().into_iter().enumerate() { @@ -76,16 +81,19 @@ fn rule_call_expression(token: &mut Token, state: &mut ParserState) -> Option { token.set_value(t.value()); - return None - }, - Err(e) => return Some(e) + return None; + } + Err(e) => return Some(e), } } else { - return Some(StackError::new(token).into()) + return Some(Error::StackOverflow(token.clone())); } } - Some(FunctionNameError::new(token, name).into()) + Some(Error::FunctionName { + name: name.to_string(), + token: token.clone(), + }) } #[cfg(test)] @@ -96,8 +104,8 @@ mod test_token { #[test] fn test_builtin_function_call() { assert_token_error!("rooplipp(9)", FunctionName); - assert_token_error!("sqrt('string')", FunctionArgType); - assert_token_error!("sqrt()", FunctionNArgs); + assert_token_error!("sqrt('string')", FunctionArgumentType); + assert_token_error!("sqrt()", FunctionArguments); assert_token_value!("sqrt(9)", Value::Float(3.0)); assert_token_value!("sqrt(8 + 1)", Value::Float(3.0)); assert_token_value!("root(9, 2)", Value::Float(3.0)); @@ -108,9 +116,17 @@ mod test_token { let mut state: ParserState = ParserState::new(); assert_token_text_stateful!("5+5\nfn(x, y) = x * y\n5+5", "10\nx * y\n10", &mut state); assert_token_value_stateful!("fn(5,5)", Value::Integer(25), &mut state); - assert_token_text_stateful!("fn(x, y) = 5x + 10(x * y)\nfn(2, 3)", "5x + 10(x * y)\n70", &mut state); - assert_token_error!("f(x) = f(x)\nf(0)", Stack); - assert_token_text_stateful!("sum(a) = element(a, 0) + ( len(a)>1 ? sum(dequeue(a)) : 0 )", "element(a, 0) + ( len(a)>1 ? sum(dequeue(a)) : 0 )", &mut state); + assert_token_text_stateful!( + "fn(x, y) = 5x + 10(x * y)\nfn(2, 3)", + "5x + 10(x * y)\n70", + &mut state + ); + assert_token_error!("f(x) = f(x)\nf(0)", StackOverflow); + assert_token_text_stateful!( + "sum(a) = element(a, 0) + ( len(a)>1 ? sum(dequeue(a)) : 0 )", + "element(a, 0) + ( len(a)>1 ? sum(dequeue(a)) : 0 )", + &mut state + ); assert_token_value_stateful!("sum([10, 10, 11])", Value::Integer(31), &mut state); } @@ -118,7 +134,10 @@ mod test_token { #[cfg(feature = "extensions")] fn test_extension_function_call() { let mut state: ParserState = ParserState::new(); - state.extensions.load("example_extensions/colour_utils.js").ok(); + state + .extensions + .load("example_extensions/colour_utils.js") + .ok(); assert_token_value_stateful!("complement(0xFFAA00)", Value::from(0x00FFFF), &mut state); } -} \ No newline at end of file +} diff --git a/src/handlers/math.rs b/src/handlers/math.rs index c1b2700..1660058 100644 --- a/src/handlers/math.rs +++ b/src/handlers/math.rs @@ -1,157 +1,188 @@ use std::collections::HashMap; -use super::{ RuleHandler, perform_calculation }; +use super::{perform_calculation, RuleHandler}; use crate::{ - token::{Rule, Token}, state::ParserState, - Value, - FloatType, - IntegerType, - errors::*, errors::ValueTypeError + token::{Rule, Token}, + Error, ExpectedTypes, FloatType, IntegerType, Value, }; /// Perform overflow checked exponentiation -/// +/// /// # Arguments /// * `l` - Left value /// * `r` - Right value -fn integer_type_checked_pow(l:IntegerType, r:IntegerType) -> Option { - if r > u32::MAX as IntegerType { return None; } - if r == IntegerType::MIN { return None; } +fn integer_type_checked_pow(l: IntegerType, r: IntegerType) -> Option { + if r > u32::MAX as IntegerType { + return None; + } + if r == IntegerType::MIN { + return None; + } match l.checked_pow(r.checked_abs().unwrap() as u32) { Some(v) => { if r < 0 { - Some(1/v) + Some(1 / v) } else { Some(v) } - }, - None => None + } + None => None, } } /// Perform a checked factorial -/// +/// /// # Arguments /// * `source` - Source token /// * `input` - input value -pub fn factorial(source: &Token, input: &Value) -> Result { +pub fn factorial(source: &Token, input: &Value) -> Result { if input.is_identifier() { - return Err(VariableNameError::new( source, &input.as_string()).into()) + return Err(Error::VariableName { + name: input.as_string(), + token: source.clone(), + }); } if let Some(v) = input.as_int() { match v { 0 => Ok(Value::Integer(1)), 1.. => { - let mut acc : IntegerType = 1; + let mut acc: IntegerType = 1; for i in 1..=v { if let Some(acc_) = acc.checked_mul(i as IntegerType) { acc = acc_; } else { - return Err(OverflowError::new(source).into()) + return Err(Error::Overflow(source.clone()).into()); } } - + Ok(Value::Integer(acc)) - }, - - _ => Err(UnderflowError::new(source).into()) + } + + _ => Err(Error::Underflow(source.clone()).into()), } } else if input.is_array() { let mut out = input.as_array(); for (i, e) in out.clone().iter().enumerate() { match factorial(source, e) { Ok(v) => out[i] = v, - Err(e) => return Err(e) + Err(e) => return Err(e), } } Ok(Value::Array(out)) } else { - Err(ValueTypeError::new(source, ExpectedTypes::IntOrFloat).into()) + Err(Error::ValueType { + value: input.clone(), + expected_type: ExpectedTypes::IntOrFloat, + token: source.clone(), + }) } } /// Trim a binary value to match the precision of a given base. Useful for inversion -/// +/// /// # Arguments /// * `input` - Source value /// * `base` - Number to check against fn trim_binary(input: Value, base: IntegerType) -> Option { match input.as_int() { Some(n) => { - let mask : IntegerType = ((2_u32).pow( ((base as FloatType).log2().floor() + 1.0) as u32) - 1) as IntegerType; - Some(Value::Integer(n & if mask==0 {!mask} else {mask})) - }, - None => None + let mask: IntegerType = + ((2_u32).pow(((base as FloatType).log2().floor() + 1.0) as u32) - 1) as IntegerType; + Some(Value::Integer(n & if mask == 0 { !mask } else { mask })) + } + None => None, } } /// Perform a unary arithmetic negation -/// +/// /// # Arguments /// * `expression` - Source token /// * `value` - Value to process -fn unary_minus(expression: &Token, value: Value) -> Result { +fn unary_minus(expression: &Token, value: Value) -> Result { match value { Value::Integer(n) => Ok(Value::Integer(-n)), Value::Float(n) => Ok(Value::Float(-n)), Value::Boolean(n) => Ok(Value::Boolean(!n)), - Value::Identifier(s) => Err(VariableNameError::new(expression, &s).into()), + Value::Identifier(s) => Err(Error::VariableName { + name: s, + token: expression.clone(), + }), Value::Array(a) => { let mut ra = a; for (pos, e) in ra.clone().iter().enumerate() { match unary_minus(expression, e.clone()) { Ok(n) => ra[pos] = n, - Err(e) => return Err(e) + Err(e) => return Err(e), } } Ok(Value::Array(ra)) - }, - _ => Err(ValueTypeError::new(expression, ExpectedTypes::IntOrFloat).into()) + } + _ => Err(Error::ValueType { + value: value, + expected_type: ExpectedTypes::IntOrFloat, + token: expression.clone(), + }), } } /// Perform a unary bitwise negation -/// +/// /// # Arguments /// * `expression` - Source token /// * `value` - Value to process -fn unary_not(expression: &Token, value: Value) -> Result { +fn unary_not(expression: &Token, value: Value) -> Result { match value { Value::Boolean(n) => Ok(Value::Boolean(!n)), - Value::Integer(n) => { - match trim_binary(Value::Integer(!n), n) { - Some(v) => Ok(v), - None => Err(ValueTypeError::new(expression, ExpectedTypes::Int).into()) - } + Value::Integer(n) => match trim_binary(Value::Integer(!n), n) { + Some(v) => Ok(v), + None => Err(Error::ValueType { + value: value, + expected_type: ExpectedTypes::Int, + token: expression.clone(), + }), }, Value::Array(a) => { let mut ra = a; for (pos, e) in ra.clone().iter().enumerate() { match unary_not(expression, e.clone()) { Ok(n) => ra[pos] = n, - Err(e) => return Err(e) + Err(e) => return Err(e), } } Ok(Value::Array(ra)) - }, - _ => Err(ValueTypeError::new(expression, ExpectedTypes::Int).into()) + } + _ => Err(Error::ValueType { + value: value, + expected_type: ExpectedTypes::Int, + token: expression.clone(), + }), } } pub fn handler_table() -> HashMap { HashMap::from([ (Rule::as_expression, rule_as_expression as RuleHandler), - (Rule::implied_mul_expression, rule_implied_mul_expression as RuleHandler), + ( + Rule::implied_mul_expression, + rule_implied_mul_expression as RuleHandler, + ), (Rule::md_expression, rule_md_expression as RuleHandler), (Rule::power_expression, rule_power_expression as RuleHandler), - (Rule::postfix_unary_expression, rule_postfix_unary_expression as RuleHandler), - (Rule::prefix_unary_expression, rule_prefix_unary_expression as RuleHandler), + ( + Rule::postfix_unary_expression, + rule_postfix_unary_expression as RuleHandler, + ), + ( + Rule::prefix_unary_expression, + rule_prefix_unary_expression as RuleHandler, + ), ]) } -fn rule_as_expression(token: &mut Token, _state: &mut ParserState) -> Option { +fn rule_as_expression(token: &mut Token, _state: &mut ParserState) -> Option { token.set_value(token.child(0).unwrap().value()); if token.children().len() > 1 { let mut i = 2; @@ -159,29 +190,39 @@ fn rule_as_expression(token: &mut Token, _state: &mut ParserState) -> Option { if token.value().is_string() || token.child(i).unwrap().value().is_string() { - token.set_value(Value::String(format!("{}{}", token.value().as_string(), token.child(i).unwrap().value().as_string()))); + token.set_value(Value::String(format!( + "{}{}", + token.value().as_string(), + token.child(i).unwrap().value().as_string() + ))); } else { match perform_calculation( - token, token.value(), token.child(i).unwrap().value(), - IntegerType::checked_add, |l: FloatType, r: FloatType| l + r + token, + token.value(), + token.child(i).unwrap().value(), + IntegerType::checked_add, + |l: FloatType, r: FloatType| l + r, ) { Ok(n) => token.set_value(n), - Err(e) => return Some(e) + Err(e) => return Some(e), }; } - }, + } Rule::minus => { match perform_calculation( - token, token.value(), token.child(i).unwrap().value(), - IntegerType::checked_sub, |l: FloatType, r: FloatType| l - r + token, + token.value(), + token.child(i).unwrap().value(), + IntegerType::checked_sub, + |l: FloatType, r: FloatType| l - r, ) { Ok(n) => token.set_value(n), - Err(e) => return Some(e) + Err(e) => return Some(e), }; - }, + } - _ => return Some(InternalError::new(token).into()) + _ => return Some(Error::Internal(token.clone())), } i += 2; @@ -191,7 +232,7 @@ fn rule_as_expression(token: &mut Token, _state: &mut ParserState) -> Option Option { +fn rule_implied_mul_expression(token: &mut Token, _state: &mut ParserState) -> Option { token.set_value(token.child(0).unwrap().value()); if token.children().len() > 1 { let mut i = 1; @@ -204,19 +245,25 @@ fn rule_implied_mul_expression(token: &mut Token, _state: &mut ParserState) -> O let ih = IntegerType::checked_mul; let fh = |l: FloatType, r: FloatType| l * r; - match perform_calculation(token, token.value(), token.child(i).unwrap().value(), ih, fh) { + match perform_calculation( + token, + token.value(), + token.child(i).unwrap().value(), + ih, + fh, + ) { Ok(n) => token.set_value(n), - Err(e) => return Some(e) + Err(e) => return Some(e), } i += 1; } } - + None } -fn rule_md_expression(token: &mut Token, _state: &mut ParserState) -> Option { +fn rule_md_expression(token: &mut Token, _state: &mut ParserState) -> Option { token.set_value(token.child(0).unwrap().value()); if token.children().len() > 1 { @@ -226,19 +273,25 @@ fn rule_md_expression(token: &mut Token, _state: &mut ParserState) -> Option IntegerType::checked_mul, Rule::divide => IntegerType::checked_div, Rule::modulus => IntegerType::checked_rem_euclid, - _ => return Some(InternalError::new(token).into()) + _ => return Some(Error::Internal(token.clone())), }; - + let fh = match token.child(i - 1).unwrap().rule() { Rule::multiply => |l: FloatType, r: FloatType| l * r, Rule::divide => |l: FloatType, r: FloatType| l / r, Rule::modulus => FloatType::rem_euclid, - _ => return Some(InternalError::new(token).into()) + _ => return Some(Error::Internal(token.clone())), }; - match perform_calculation(token, token.value(), token.child(i).unwrap().value(), ih, fh) { + match perform_calculation( + token, + token.value(), + token.child(i).unwrap().value(), + ih, + fh, + ) { Ok(n) => token.set_value(n), - Err(e) => return Some(e) + Err(e) => return Some(e), } i += 2; @@ -248,15 +301,21 @@ fn rule_md_expression(token: &mut Token, _state: &mut ParserState) -> Option Option { +fn rule_power_expression(token: &mut Token, _state: &mut ParserState) -> Option { token.set_value(token.child(0).unwrap().value()); if token.children().len() > 1 { let mut i = 2; while i < token.children().len() { - match perform_calculation(token, token.value(), token.child(i).unwrap().value(), integer_type_checked_pow, FloatType::powf) { + match perform_calculation( + token, + token.value(), + token.child(i).unwrap().value(), + integer_type_checked_pow, + FloatType::powf, + ) { Ok(n) => token.set_value(n), - Err(e) => return Some(e) + Err(e) => return Some(e), } i += 2; @@ -266,22 +325,22 @@ fn rule_power_expression(token: &mut Token, _state: &mut ParserState) -> Option< None } -fn rule_prefix_unary_expression(token: &mut Token, _state: &mut ParserState) -> Option { +fn rule_prefix_unary_expression(token: &mut Token, _state: &mut ParserState) -> Option { if token.children().len() >= 2 { let mut idx = token.children().len() - 1; token.set_value(token.child(idx).unwrap().value()); - while idx >0 { - idx-=1; + while idx > 0 { + idx -= 1; if token.child(idx).unwrap().rule() == Rule::minus { match unary_minus(token, token.value()) { Ok(n) => token.set_value(n), - Err(e) => return Some(e) + Err(e) => return Some(e), } } else if token.child(idx).unwrap().rule() == Rule::not { match unary_not(token, token.value()) { Ok(n) => token.set_value(n), - Err(e) => return Some(e) + Err(e) => return Some(e), } } } @@ -290,7 +349,7 @@ fn rule_prefix_unary_expression(token: &mut Token, _state: &mut ParserState) -> None } -fn rule_postfix_unary_expression(token: &mut Token, _state: &mut ParserState) -> Option { +fn rule_postfix_unary_expression(token: &mut Token, _state: &mut ParserState) -> Option { if token.children().last().unwrap().text() == "!" { token.set_value(token.child(0).unwrap().value()); if token.children().len() >= 2 { @@ -299,16 +358,15 @@ fn rule_postfix_unary_expression(token: &mut Token, _state: &mut ParserState) -> if token.child(i).unwrap().rule() == Rule::factorial { match factorial(token, &token.value()) { Ok(v) => token.set_value(v), - Err(e) => return Some(e) - + Err(e) => return Some(e), } } - i+=1; + i += 1; } } } - + None } @@ -330,51 +388,118 @@ mod test_token { let mut state = ParserState::new(); let token = Token::new("1", &mut state).unwrap(); - assert_eq!(1, factorial(&token, &Value::Integer(0)).unwrap().as_int().unwrap()); - assert_eq!(1, factorial(&token, &Value::Integer(1)).unwrap().as_int().unwrap()); - assert_eq!(2, factorial(&token, &Value::Integer(2)).unwrap().as_int().unwrap()); - assert_eq!(24, factorial(&token, &Value::Integer(4)).unwrap().as_int().unwrap()); - assert_eq!(24, factorial(&token, &Value::Float(4.0)).unwrap().as_int().unwrap()); + assert_eq!( + 1, + factorial(&token, &Value::Integer(0)) + .unwrap() + .as_int() + .unwrap() + ); + assert_eq!( + 1, + factorial(&token, &Value::Integer(1)) + .unwrap() + .as_int() + .unwrap() + ); + assert_eq!( + 2, + factorial(&token, &Value::Integer(2)) + .unwrap() + .as_int() + .unwrap() + ); + assert_eq!( + 24, + factorial(&token, &Value::Integer(4)) + .unwrap() + .as_int() + .unwrap() + ); + assert_eq!( + 24, + factorial(&token, &Value::Float(4.0)) + .unwrap() + .as_int() + .unwrap() + ); assert_eq!(true, factorial(&token, &Value::Integer(99)).is_err()); assert_eq!(true, factorial(&token, &Value::Integer(-1)).is_err()); } #[test] fn test_trim_binary() { - assert_eq!(Value::Integer(255), trim_binary(Value::Integer(65535), 255).unwrap()); - assert_eq!(Value::Integer(9999), trim_binary(Value::Integer(9999), 9999).unwrap()); + assert_eq!( + Value::Integer(255), + trim_binary(Value::Integer(65535), 255).unwrap() + ); + assert_eq!( + Value::Integer(9999), + trim_binary(Value::Integer(9999), 9999).unwrap() + ); } #[test] fn test_prefix_unary_expression_minus() { let mut state = ParserState::new(); - assert_eq!(Value::Array(vec![ - Value::Integer(-1), Value::Integer(1), Value::Float(1.0), - ]), Token::new("-[1,-1, -1.0]", &mut state).unwrap().value()); - assert_eq!(Value::Integer(-255), Token::new("-255", &mut state).unwrap().value()); - assert_eq!(Value::Float(-255.0), Token::new("-255.0", &mut state).unwrap().value()); - assert_eq!(Value::Boolean(true), Token::new("-false", &mut state).unwrap().value()); + assert_eq!( + Value::Array(vec![ + Value::Integer(-1), + Value::Integer(1), + Value::Float(1.0), + ]), + Token::new("-[1,-1, -1.0]", &mut state).unwrap().value() + ); + assert_eq!( + Value::Integer(-255), + Token::new("-255", &mut state).unwrap().value() + ); + assert_eq!( + Value::Float(-255.0), + Token::new("-255.0", &mut state).unwrap().value() + ); + assert_eq!( + Value::Boolean(true), + Token::new("-false", &mut state).unwrap().value() + ); assert_eq!(true, Token::new("-'test'", &mut state).is_err()); } #[test] fn test_prefix_unary_expression_not() { let mut state = ParserState::new(); - assert_eq!(Value::Array(vec![ - Value::Integer(0), Value::Integer(3), Value::Boolean(false), - ]), Token::new("~[255, 0b1100, true]", &mut state).unwrap().value()); - assert_eq!(Value::Boolean(false), Token::new("~true", &mut state).unwrap().value()); - assert_eq!(Value::Integer(0), Token::new("~255", &mut state).unwrap().value()); - assert_eq!(Value::Integer(3), Token::new("~0b1100", &mut state).unwrap().value()); + assert_eq!( + Value::Array(vec![ + Value::Integer(0), + Value::Integer(3), + Value::Boolean(false), + ]), + Token::new("~[255, 0b1100, true]", &mut state) + .unwrap() + .value() + ); + assert_eq!( + Value::Boolean(false), + Token::new("~true", &mut state).unwrap().value() + ); + assert_eq!( + Value::Integer(0), + Token::new("~255", &mut state).unwrap().value() + ); + assert_eq!( + Value::Integer(3), + Token::new("~0b1100", &mut state).unwrap().value() + ); assert_eq!(true, Token::new("~1.2", &mut state).is_err()); assert_eq!(true, Token::new("~'test'", &mut state).is_err()); } #[test] fn test_postfix_unary_expression_factorial() { - assert_token_value!("[0, 2, 4]!", Value::from(vec![ - Value::from(1), Value::from(2), Value::from(24), - ])); + assert_token_value!( + "[0, 2, 4]!", + Value::from(vec![Value::from(1), Value::from(2), Value::from(24),]) + ); assert_token_value!("0!", Value::from(1)); assert_token_value!("1!", Value::from(1)); assert_token_value!("2!", Value::from(2)); @@ -385,12 +510,14 @@ mod test_token { #[test] fn test_power_expression() { - assert_token_value!("[2, 2**2, 0]**2", Value::from(vec![ - Value::from(4), Value::from(16), Value::from(0), - ])); - assert_token_value!("2**[0, 1, 2]", Value::from(vec![ - Value::from(1), Value::from(2), Value::from(4), - ])); + assert_token_value!( + "[2, 2**2, 0]**2", + Value::from(vec![Value::from(4), Value::from(16), Value::from(0),]) + ); + assert_token_value!( + "2**[0, 1, 2]", + Value::from(vec![Value::from(1), Value::from(2), Value::from(4),]) + ); assert_token_value!("2**2", Value::from(4)); assert_token_value!("2**2**2", Value::from(16)); assert_token_value!("2**2**(2)", Value::from(16)); @@ -398,18 +525,18 @@ mod test_token { #[test] fn test_md_expression() { - assert_token_value!("[2, 4]*2", Value::from(vec![ - Value::from(4), Value::from(8), - ])); - assert_token_value!("2/[2, 4]", Value::from(vec![ - Value::from(1), Value::from(0), - ])); + assert_token_value!( + "[2, 4]*2", + Value::from(vec![Value::from(4), Value::from(8),]) + ); + assert_token_value!( + "2/[2, 4]", + Value::from(vec![Value::from(1), Value::from(0),]) + ); assert_token_value!("2*2", Value::from(4)); assert_token_value!("2/2", Value::from(1)); assert_token_value!("11%10", Value::from(1)); assert_token_value!("12%10 * 2 / 2", Value::from(2)); - - } #[test] @@ -423,20 +550,34 @@ mod test_token { assert_token_value_stateful!("4(x)", Value::from(16), &mut state); assert_token_value_stateful!("(4)x", Value::from(16), &mut state); - assert_token_value!("2[2,4]2", Value::from(vec![Value::from(8), Value::from(16)])); - assert_token_value!("[2,4][3,3]", Value::from(vec![Value::from(6), Value::from(12)])); + assert_token_value!( + "2[2,4]2", + Value::from(vec![Value::from(8), Value::from(16)]) + ); + assert_token_value!( + "[2,4][3,3]", + Value::from(vec![Value::from(6), Value::from(12)]) + ); assert_token_value!("2(2)(2)(2)(2)(2)", Value::from(64)); } #[test] fn test_as_expression() { - assert_token_text!("2*$2", "$4.00"); assert_token_value!("2+2", Value::Integer(4)); assert_token_value!("2+2+2", Value::Integer(6)); assert_token_value!("2+2-2/2", Value::Integer(3)); - assert_token_value!("2-[2,4]", Value::from(vec![Value::from(0), Value::from(-2)])); - assert_token_value!("[2,4] - 2", Value::from(vec![Value::from(0), Value::from(2)])); - assert_token_value!("[2,4] - [2,3]", Value::from(vec![Value::from(0), Value::from(1)])); + assert_token_value!( + "2-[2,4]", + Value::from(vec![Value::from(0), Value::from(-2)]) + ); + assert_token_value!( + "[2,4] - 2", + Value::from(vec![Value::from(0), Value::from(2)]) + ); + assert_token_value!( + "[2,4] - [2,3]", + Value::from(vec![Value::from(0), Value::from(1)]) + ); } -} \ No newline at end of file +} diff --git a/src/handlers.rs b/src/handlers/mod.rs similarity index 51% rename from src/handlers.rs rename to src/handlers/mod.rs index bc3e85f..b556b00 100644 --- a/src/handlers.rs +++ b/src/handlers/mod.rs @@ -1,26 +1,25 @@ -use std::collections::HashMap; use crate::{ - token::{Rule, Token, OutputFormat, LavendeuxHandler}, state::{ParserState, UserFunction}, - Value, - errors::* + token::{LavendeuxHandler, OutputFormat, Rule, Token}, + Error, ExpectedTypes, Value, }; +use std::collections::HashMap; mod utils; use utils::*; // Handlers -mod values; -mod functions; mod bitwise; mod boolean; -mod math; mod errors; +mod functions; +mod math; +mod values; #[derive(Default)] -pub struct Handler{} +pub struct Handler {} impl LavendeuxHandler for Handler { - fn handle_tree(&self, token: &mut Token, state: &mut ParserState) -> Result<(), ParserError> { + fn handle_tree(&self, token: &mut Token, state: &mut ParserState) -> Result<(), Error> { // Ternary expression handler - enables short-circuit interpretation if token.rule() == Rule::ternary_expression { let condition = token.mut_child(0).unwrap(); @@ -42,19 +41,24 @@ impl LavendeuxHandler for Handler { let definition = token.children().last().unwrap().text(); // Compile arguments - let mut arguments : Vec = Vec::new(); + let mut arguments: Vec = Vec::new(); for child in token.children().iter().skip(2) { let s = child.text(); - if s == "," { continue; } - if s == ")" { break; } + if s == "," { + continue; + } + if s == ")" { + break; + } arguments.push(s.to_string()); } // Store new function - state.user_functions.insert(name.to_string(), - UserFunction::new(name.to_string(), arguments, definition.to_string()) + state.user_functions.insert( + name.to_string(), + UserFunction::new(name.to_string(), arguments, definition.to_string()), ); - + let def = token.children().last().unwrap().clone(); token.set_text(def.text()); token.set_value(Value::String(def.text().to_string())); @@ -69,23 +73,32 @@ impl LavendeuxHandler for Handler { // Check for unresolve identifier errors for child in token.children() { if child.value().is_identifier() { - let err = VariableNameError::new(child, &child.value().to_string()); - // Help function is allowed to have an unresolved identifier - if !(token.rule() == Rule::call_expression && token.child(0).unwrap().text() == "help") { - return Err(err.into()); + if !(token.rule() == Rule::call_expression + && token.child(0).unwrap().text() == "help") + { + return Err(Error::VariableName { + name: child.text().to_string(), + token: child.clone(), + }); } } } - + // Bubble up output format from children - let format = token.children().iter().fold(OutputFormat::Default, |a,f| if f.format() as i32 / 10 > a as i32 / 10 {f.format()} else {a}); + let format = token.children().iter().fold(OutputFormat::Default, |a, f| { + if f.format() as i32 / 10 > a as i32 / 10 { + f.format() + } else { + a + } + }); token.set_format(format); // Get handler from table if let Some(f) = handler_table().get(&token.rule()) { if let Some(e) = f(token, state) { - return Err(e) + return Err(e); } } @@ -93,14 +106,18 @@ impl LavendeuxHandler for Handler { } } -type RuleHandler = fn(token: &mut Token, state: &mut ParserState) -> Option; +type RuleHandler = fn(token: &mut Token, state: &mut ParserState) -> Option; fn handler_table() -> HashMap { HashMap::from([ (Rule::script, rule_script as RuleHandler), (Rule::line, rule_line as RuleHandler), (Rule::term, rule_term as RuleHandler), - (Rule::assignment_expression, rule_assignment_expression as RuleHandler), - ]).into_iter() + ( + Rule::assignment_expression, + rule_assignment_expression as RuleHandler, + ), + ]) + .into_iter() .chain(values::handler_table()) .chain(functions::handler_table()) .chain(bitwise::handler_table()) @@ -111,14 +128,22 @@ fn handler_table() -> HashMap { } /// A series of lines -fn rule_script(token: &mut Token, _state: &mut ParserState) -> Option { +fn rule_script(token: &mut Token, _state: &mut ParserState) -> Option { // Concatenate output from all child tokens (lines) token.set_text( - &token.children().iter().map(|t| { - t.text().to_string() + if !t.children().is_empty() { - t.children().last().unwrap().text() - } else { "" } - }).collect::>().join("") + &token + .children() + .iter() + .map(|t| { + t.text().to_string() + + if !t.children().is_empty() { + t.children().last().unwrap().text() + } else { + "" + } + }) + .collect::>() + .join(""), ); // Set script value if there is only one line @@ -130,7 +155,7 @@ fn rule_script(token: &mut Token, _state: &mut ParserState) -> Option Option { +fn rule_line(token: &mut Token, state: &mut ParserState) -> Option { // Bubble up child value and output format token.set_value(token.child(0).unwrap().value()); if matches!(token.format(), OutputFormat::Unknown) { @@ -159,12 +184,15 @@ fn rule_line(token: &mut Token, state: &mut ParserState) -> Option // Extension decorators #[cfg(feature = "extensions")] if state.extensions.has_decorator(decorator_name) { - match state.extensions.call_decorator(decorator_name, token, &mut state.variables) { + match state + .extensions + .call_decorator(decorator_name, token, &mut state.variables) + { Ok(s) => { token.set_text(&s); return None; - }, - Err(e) => return Some(e) + } + Err(e) => return Some(e), } } @@ -173,7 +201,11 @@ fn rule_line(token: &mut Token, state: &mut ParserState) -> Option } if token.value().is_identifier() { - return Some(VariableNameError::new(token.child(0).unwrap(), token.child(0).unwrap().text()).into()); + let token = token.child(0).unwrap(); + return Some(Error::VariableName { + name: token.text().to_string(), + token: token.clone(), + }); } None @@ -182,7 +214,7 @@ fn rule_line(token: &mut Token, state: &mut ParserState) -> Option /// Term /// expression /// ( expression ) -fn rule_term(token: &mut Token, _state: &mut ParserState) -> Option { +fn rule_term(token: &mut Token, _state: &mut ParserState) -> Option { // Unwrap parentheses if needed if token.children().len() == 3 { token.set_value(token.child(1).unwrap().value()); @@ -194,7 +226,7 @@ fn rule_term(token: &mut Token, _state: &mut ParserState) -> Option /// Assignment expression /// identifier[index] = expression /// identifier = expression -fn rule_assignment_expression(token: &mut Token, state: &mut ParserState) -> Option { +fn rule_assignment_expression(token: &mut Token, state: &mut ParserState) -> Option { if token.child(0).unwrap().rule() == Rule::index_assignment_prefix { rule_assignment_expression_indexed(token, state) } else { @@ -202,62 +234,84 @@ fn rule_assignment_expression(token: &mut Token, state: &mut ParserState) -> Opt } } - /// Array indexed assignment expressions - /// identifier[index] = expression - fn rule_assignment_expression_indexed(token: &mut Token, state: &mut ParserState) -> Option { - let prefix = token.child(0).unwrap().clone(); - let identifier = prefix.child(0).unwrap().text(); - let index = prefix.child(2).unwrap().value(); - let result = token.children().last().unwrap().value(); - - if let Some(value) = state.variables.clone().get(identifier) { - match value.clone() { - Value::Object(mut v) => { - v.insert(index, result.clone()); - state.variables.insert(identifier.to_string(), Value::Object(v)); - token.set_value(result); - }, - - _ => { - match index.as_int() { - Some(i) => { - let mut array = value.as_array(); - if i as usize > array.len() || i < 0 { - return Some(ArrayIndexError::new(token, i as usize).into()); - } - - // Update array - if i as usize == array.len() { - array.insert(i as usize, result.clone()); - } else { - array[i as usize] = result.clone(); - } - - state.variables.insert(identifier.to_string(), Value::Array(array)); - token.set_value(result); - }, - None => return Some(ValueTypeError::new(token, ExpectedTypes::Int).into()) +/// Array indexed assignment expressions +/// identifier[index] = expression +fn rule_assignment_expression_indexed(token: &mut Token, state: &mut ParserState) -> Option { + let prefix = token.child(0).unwrap().clone(); + let identifier = prefix.child(0).unwrap().text(); + let index = prefix.child(2).unwrap().value(); + let result = token.children().last().unwrap().value(); + + if let Some(value) = state.variables.clone().get(identifier) { + match value.clone() { + Value::Object(mut v) => { + v.insert(index, result.clone()); + state + .variables + .insert(identifier.to_string(), Value::Object(v)); + token.set_value(result); + } + + _ => { + match index.as_int() { + Some(i) => { + let mut array = value.as_array(); + if i as usize > array.len() || i < 0 { + return Some(Error::Index { + key: index, + token: token.clone(), + }); + } + + // Update array + if i as usize == array.len() { + array.insert(i as usize, result.clone()); + } else { + array[i as usize] = result.clone(); + } + + state + .variables + .insert(identifier.to_string(), Value::Array(array)); + token.set_value(result); + } + None => { + return Some(Error::ValueType { + value: index, + expected_type: ExpectedTypes::Int, + token: token.clone(), + }) } } } } - - None } - /// Variable assignment expressions - /// identifier = expression - fn rule_assignment_expression_variable(token: &mut Token, state: &mut ParserState) -> Option { - let identifier = token.child(0).unwrap().child(0).unwrap(); - - if state.constants.contains_key(identifier.text()) { - // Cannot overwrite constant - return Some(ConstantValueError::new(token, identifier.text()).into()) - } else { - // Update value - state.variables.insert(identifier.text().to_string(), token.child(1).unwrap().value()); - token.set_value(token.child(1).unwrap().value()); - } + None +} - None - } \ No newline at end of file +/// Variable assignment expressions +/// identifier = expression +fn rule_assignment_expression_variable( + token: &mut Token, + state: &mut ParserState, +) -> Option { + let identifier = token.child(0).unwrap().child(0).unwrap(); + + if state.constants.contains_key(identifier.text()) { + // Cannot overwrite constant + return Some(Error::ConstantValue { + name: identifier.text().to_string(), + token: token.clone(), + }); + } else { + // Update value + state.variables.insert( + identifier.text().to_string(), + token.child(1).unwrap().value(), + ); + token.set_value(token.child(1).unwrap().value()); + } + + None +} diff --git a/src/handlers/utils.rs b/src/handlers/utils.rs index e5b8f6d..0f2bada 100644 --- a/src/handlers/utils.rs +++ b/src/handlers/utils.rs @@ -1,38 +1,43 @@ -use crate::{ - token::Token, - Value, - IntegerType, - FloatType, - errors::* -}; +use crate::{token::Token, Error, ExpectedTypes, FloatType, IntegerType, Value}; -pub type IntHandler = fn(l:IntegerType, r:IntegerType) -> Option; -pub type FloatHandler = fn(l:FloatType, r:FloatType) -> FloatType; +pub type IntHandler = fn(l: IntegerType, r: IntegerType) -> Option; +pub type FloatHandler = fn(l: FloatType, r: FloatType) -> FloatType; /// Perform an integer calculation against 2 values -/// +/// /// # Arguments /// * `l` - Left value /// * `r` - Right value /// * `handler` - checked_* function -pub fn perform_int_calculation(expression: &Token, l: Value, r: Value, handler: IntHandler) -> Result { +pub fn perform_int_calculation( + expression: &Token, + l: Value, + r: Value, + handler: IntHandler, +) -> Result { if l.is_identifier() { - return Err(VariableNameError::new(expression, &l.to_string()).into()) + return Err(Error::VariableName { + name: l.to_string(), + token: expression.clone(), + }); } else if r.is_identifier() { - return Err(VariableNameError::new(expression, &r.to_string()).into()) + return Err(Error::VariableName { + name: r.to_string(), + token: expression.clone(), + }); } - + if l.is_array() && r.is_array() { let mut la = l.as_array(); let ra = r.as_array(); if la.len() != ra.len() { - Err(ArrayLengthError::new(expression).into()) + Err(Error::ArrayLengths(expression.clone())) } else { for (pos, e) in la.clone().iter().enumerate() { match perform_int_calculation(expression, e.clone(), ra[pos].clone(), handler) { Ok(n) => la[pos] = n, - Err(e) => return Err(e) + Err(e) => return Err(e), } } Ok(Value::Array(la)) @@ -42,7 +47,7 @@ pub fn perform_int_calculation(expression: &Token, l: Value, r: Value, handler: for (pos, e) in la.clone().iter().enumerate() { match perform_int_calculation(expression, e.clone(), r.clone(), handler) { Ok(n) => la[pos] = n, - Err(e) => return Err(e) + Err(e) => return Err(e), } } Ok(Value::Array(la)) @@ -51,36 +56,53 @@ pub fn perform_int_calculation(expression: &Token, l: Value, r: Value, handler: for (pos, e) in ra.clone().iter().enumerate() { match perform_int_calculation(expression, l.clone(), e.clone(), handler) { Ok(n) => ra[pos] = n, - Err(e) => return Err(e) + Err(e) => return Err(e), } } Ok(Value::Array(ra)) } else { // Perform datatype conversions - let lv = l.as_int(); let rv = r.as_int(); - if lv.is_none() || rv.is_none() { - Err(ValueTypeError::new(expression, ExpectedTypes::IntOrFloat).into()) - } else { - // Detect overflow and return resulting value - match handler(lv.unwrap(), rv.unwrap()) { - Some(n) => Ok(Value::Integer(n)), - None => Err(OverflowError::new(expression).into()) - } + let lv = l.as_int().ok_or(Error::ValueType { + value: l, + expected_type: ExpectedTypes::IntOrFloat, + token: expression.clone(), + })?; + let rv = r.as_int().ok_or(Error::ValueType { + value: r, + expected_type: ExpectedTypes::IntOrFloat, + token: expression.clone(), + })?; + + // Detect overflow and return resulting value + match handler(lv, rv) { + Some(n) => Ok(Value::Integer(n)), + None => Err(Error::Overflow(expression.clone())), } } } /// Perform a floating point calculation against 2 values -/// +/// /// # Arguments /// * `l` - Left value /// * `r` - Right value /// * `handler` - checked_* function -pub fn perform_float_calculation(expression: &Token, l: Value, r: Value, handler: FloatHandler) -> Result { +pub fn perform_float_calculation( + expression: &Token, + l: Value, + r: Value, + handler: FloatHandler, +) -> Result { if l.is_identifier() { - return Err(VariableNameError::new(expression, &l.to_string()).into()) + return Err(Error::VariableName { + name: l.to_string(), + token: expression.clone(), + }); } else if r.is_identifier() { - return Err(VariableNameError::new(expression, &r.to_string()).into()) + return Err(Error::VariableName { + name: r.to_string(), + token: expression.clone(), + }); } if l.is_array() && r.is_array() { @@ -88,12 +110,12 @@ pub fn perform_float_calculation(expression: &Token, l: Value, r: Value, handler let ra = r.as_array(); if la.len() != ra.len() { - Err(ArrayLengthError::new(expression).into()) + Err(Error::ArrayLengths(expression.clone())) } else { for (pos, e) in la.clone().iter().enumerate() { match perform_float_calculation(expression, e.clone(), ra[pos].clone(), handler) { Ok(n) => la[pos] = n, - Err(e) => return Err(e) + Err(e) => return Err(e), } } Ok(Value::Array(la)) @@ -103,7 +125,7 @@ pub fn perform_float_calculation(expression: &Token, l: Value, r: Value, handler for (pos, e) in la.clone().iter().enumerate() { match perform_float_calculation(expression, e.clone(), r.clone(), handler) { Ok(n) => la[pos] = n, - Err(e) => return Err(e) + Err(e) => return Err(e), } } Ok(Value::Array(la)) @@ -112,37 +134,49 @@ pub fn perform_float_calculation(expression: &Token, l: Value, r: Value, handler for (pos, e) in ra.clone().iter().enumerate() { match perform_float_calculation(expression, l.clone(), e.clone(), handler) { Ok(n) => ra[pos] = n, - Err(e) => return Err(e) + Err(e) => return Err(e), } } Ok(Value::Array(ra)) } else { // Perform datatype conversions - let lv = l.as_float(); let rv = r.as_float(); - if lv.is_none() || rv.is_none() { - return Err(ValueTypeError::new(expression, ExpectedTypes::IntOrFloat).into()) - } - + let lv = l.as_float().ok_or(Error::ValueType { + value: l, + expected_type: ExpectedTypes::IntOrFloat, + token: expression.clone(), + })?; + let rv = r.as_float().ok_or(Error::ValueType { + value: r, + expected_type: ExpectedTypes::IntOrFloat, + token: expression.clone(), + })?; + // Detect overflow - let r = handler(lv.unwrap(), rv.unwrap()); + let r = handler(lv, rv); if r == FloatType::INFINITY { - return Err(OverflowError::new(expression).into()) + return Err(Error::Overflow(expression.clone())); } else if r == FloatType::NEG_INFINITY { - return Err(UnderflowError::new(expression).into()) + return Err(Error::Underflow(expression.clone())); } - + // Return resulting value Ok(Value::Float(r)) } } /// Perform a calculation against 2 values -/// +/// /// # Arguments /// * `l` - Left value /// * `r` - Right value /// * `handler` - checked_* function -pub fn perform_calculation(expression: &Token, l: Value, r: Value, i_handler: IntHandler, f_handler: FloatHandler) -> Result { +pub fn perform_calculation( + expression: &Token, + l: Value, + r: Value, + i_handler: IntHandler, + f_handler: FloatHandler, +) -> Result { if l.as_array().iter().any(|e| e.is_float()) || r.as_array().iter().any(|e| e.is_float()) { perform_float_calculation(expression, l, r, f_handler) } else { @@ -152,46 +186,54 @@ pub fn perform_calculation(expression: &Token, l: Value, r: Value, i_handler: In #[cfg(test)] mod test_token { - use crate::{ ParserState, Value }; use super::*; + use crate::{ParserState, Value}; #[test] fn test_perform_int_calculation() { let mut state = ParserState::new(); assert_eq!( - Value::Integer(1), - perform_int_calculation(&Token::new("2 - 1", &mut state).unwrap(), - Value::Integer(2), - Value::Integer(1), - |l,r| Some(l-r) - ).unwrap() + Value::Integer(1), + perform_int_calculation( + &Token::new("2 - 1", &mut state).unwrap(), + Value::Integer(2), + Value::Integer(1), + |l, r| Some(l - r) + ) + .unwrap() ); - + assert_eq!( - Value::Array(vec![Value::Integer(1), Value::Integer(1)]), - perform_int_calculation(&Token::new("[2, 2] - 1", &mut state).unwrap(), - Value::Array(vec![Value::Integer(2), Value::Integer(2)]), - Value::Integer(1), - |l,r| Some(l-r) - ).unwrap() + Value::Array(vec![Value::Integer(1), Value::Integer(1)]), + perform_int_calculation( + &Token::new("[2, 2] - 1", &mut state).unwrap(), + Value::Array(vec![Value::Integer(2), Value::Integer(2)]), + Value::Integer(1), + |l, r| Some(l - r) + ) + .unwrap() ); - + assert_eq!( - Value::Array(vec![Value::Integer(-1), Value::Integer(-1)]), - perform_int_calculation(&Token::new("1 - [2, 2]", &mut state).unwrap(), - Value::Integer(1), - Value::Array(vec![Value::Integer(2), Value::Integer(2)]), - |l,r| Some(l-r) - ).unwrap() + Value::Array(vec![Value::Integer(-1), Value::Integer(-1)]), + perform_int_calculation( + &Token::new("1 - [2, 2]", &mut state).unwrap(), + Value::Integer(1), + Value::Array(vec![Value::Integer(2), Value::Integer(2)]), + |l, r| Some(l - r) + ) + .unwrap() ); - + assert_eq!( - Value::Array(vec![Value::Integer(1), Value::Integer(1)]), - perform_int_calculation(&Token::new("[2, 2] - [1, 1]", &mut state).unwrap(), - Value::Array(vec![Value::Integer(2), Value::Integer(2)]), - Value::Array(vec![Value::Integer(1), Value::Integer(1)]), - |l,r| Some(l-r) - ).unwrap() + Value::Array(vec![Value::Integer(1), Value::Integer(1)]), + perform_int_calculation( + &Token::new("[2, 2] - [1, 1]", &mut state).unwrap(), + Value::Array(vec![Value::Integer(2), Value::Integer(2)]), + Value::Array(vec![Value::Integer(1), Value::Integer(1)]), + |l, r| Some(l - r) + ) + .unwrap() ); } @@ -200,39 +242,47 @@ mod test_token { let mut state = ParserState::new(); assert_eq!( - Value::Float(1.0), - perform_float_calculation(&Token::new("2.0 - 1.0", &mut state).unwrap(), - Value::Float(2.0), - Value::Float(1.0), - |l,r| l-r - ).unwrap() + Value::Float(1.0), + perform_float_calculation( + &Token::new("2.0 - 1.0", &mut state).unwrap(), + Value::Float(2.0), + Value::Float(1.0), + |l, r| l - r + ) + .unwrap() ); - + assert_eq!( - Value::Array(vec![Value::Float(1.0), Value::Float(1.0)]), - perform_float_calculation(&Token::new("[2, 2] - 1", &mut state).unwrap(), - Value::Array(vec![Value::Integer(2), Value::Float(2.0)]), - Value::Integer(1), - |l,r| l-r - ).unwrap() + Value::Array(vec![Value::Float(1.0), Value::Float(1.0)]), + perform_float_calculation( + &Token::new("[2, 2] - 1", &mut state).unwrap(), + Value::Array(vec![Value::Integer(2), Value::Float(2.0)]), + Value::Integer(1), + |l, r| l - r + ) + .unwrap() ); - + assert_eq!( - Value::Array(vec![Value::Float(-1.0), Value::Float(-1.0)]), - perform_float_calculation(&Token::new("1.0 - [2, 2]", &mut state).unwrap(), - Value::Float(1.0), - Value::Array(vec![Value::Integer(2), Value::Integer(2)]), - |l,r| l-r - ).unwrap() + Value::Array(vec![Value::Float(-1.0), Value::Float(-1.0)]), + perform_float_calculation( + &Token::new("1.0 - [2, 2]", &mut state).unwrap(), + Value::Float(1.0), + Value::Array(vec![Value::Integer(2), Value::Integer(2)]), + |l, r| l - r + ) + .unwrap() ); - + assert_eq!( - Value::Array(vec![Value::Float(1.0), Value::Float(1.0)]), - perform_float_calculation(&Token::new("[2, 2] - [1, 1.0]", &mut state).unwrap(), - Value::Array(vec![Value::Integer(2), Value::Integer(2)]), - Value::Array(vec![Value::Integer(1), Value::Float(1.0)]), - |l,r| l-r - ).unwrap() + Value::Array(vec![Value::Float(1.0), Value::Float(1.0)]), + perform_float_calculation( + &Token::new("[2, 2] - [1, 1.0]", &mut state).unwrap(), + Value::Array(vec![Value::Integer(2), Value::Integer(2)]), + Value::Array(vec![Value::Integer(1), Value::Float(1.0)]), + |l, r| l - r + ) + .unwrap() ); } @@ -240,17 +290,60 @@ mod test_token { fn test_perform_calculation() { let mut state = ParserState::new(); let token = Token::new("1.0 + 1.0", &mut state).unwrap(); - assert_eq!(Value::Array(vec![Value::Integer(1), - Value::Integer(1)]), - perform_calculation(&token, - Value::Array(vec![Value::Integer(2), Value::Integer(2)]), - Value::Integer(1), - |l,r| Some(l-r), |l,r| l-r - ).unwrap() + assert_eq!( + Value::Array(vec![Value::Integer(1), Value::Integer(1)]), + perform_calculation( + &token, + Value::Array(vec![Value::Integer(2), Value::Integer(2)]), + Value::Integer(1), + |l, r| Some(l - r), + |l, r| l - r + ) + .unwrap() + ); + assert_eq!( + Value::Integer(1), + perform_calculation( + &token, + Value::Integer(2), + Value::Integer(1), + |l, r| Some(l - r), + |l, r| l - r + ) + .unwrap() + ); + assert_eq!( + Value::Float(1.0), + perform_calculation( + &token, + Value::Integer(2), + Value::Float(1.0), + |l, r| Some(l - r), + |l, r| l - r + ) + .unwrap() + ); + assert_eq!( + Value::Float(1.0), + perform_calculation( + &token, + Value::Float(2.0), + Value::Integer(1), + |l, r| Some(l - r), + |l, r| l - r + ) + .unwrap() + ); + assert_eq!( + Value::Float(1.0), + perform_calculation( + &token, + Value::Float(2.0), + Value::Float(1.0), + |l, r| Some(l - r), + |l, r| l - r + ) + .unwrap() ); - assert_eq!(Value::Integer(1), perform_calculation(&token, Value::Integer(2), Value::Integer(1), |l,r| Some(l-r), |l,r| l-r).unwrap()); - assert_eq!(Value::Float(1.0), perform_calculation(&token, Value::Integer(2), Value::Float(1.0), |l,r| Some(l-r), |l,r| l-r).unwrap()); - assert_eq!(Value::Float(1.0), perform_calculation(&token, Value::Float(2.0), Value::Integer(1), |l,r| Some(l-r), |l,r| l-r).unwrap()); - assert_eq!(Value::Float(1.0), perform_calculation(&token, Value::Float(2.0), Value::Float(1.0), |l,r| Some(l-r), |l,r| l-r).unwrap()); } -} \ No newline at end of file +} diff --git a/src/handlers/values.rs b/src/handlers/values.rs index baf0c15..f78015c 100644 --- a/src/handlers/values.rs +++ b/src/handlers/values.rs @@ -2,26 +2,28 @@ use std::collections::HashMap; use super::RuleHandler; use crate::{ - token::{Rule, Token, OutputFormat}, state::ParserState, - Value, - IntegerType, - FloatType, - errors::*, value::ObjectType + token::{OutputFormat, Rule, Token}, + value::ObjectType, + Error, ExpectedTypes, FloatType, IntegerType, Value, }; /// Parse a string as an integer of a given base -/// +/// /// # Arguments /// * `input` - Source string /// * `prefix` - Number prefix to remove from the string /// * `base` - Numeric base -fn parse_radix(input: &str, prefix: &[&str], base: u32) -> Result { +fn parse_radix( + input: &str, + prefix: &[&str], + base: u32, +) -> Result { let mut trimmed = input.to_string(); for p in prefix { trimmed = trimmed.trim_start_matches(p).to_string(); } - + IntegerType::from_str_radix(&trimmed, base) } @@ -45,28 +47,33 @@ pub fn handler_table() -> HashMap { } /// A single value -fn rule_atomic_value(token: &mut Token, _state: &mut ParserState) -> Option { +fn rule_atomic_value(token: &mut Token, _state: &mut ParserState) -> Option { token.set_value(token.child(0).unwrap().value()); if matches!(token.value(), Value::None) { - return Some(VariableNameError::new(token, token.text()).into()); + return Some(Error::VariableName { + name: token.text().to_string(), + token: token.clone(), + }); } None } /// Array value /// [5,2,'test'] -fn rule_array(token: &mut Token, _state: &mut ParserState) -> Option { +fn rule_array(token: &mut Token, _state: &mut ParserState) -> Option { let child_container = token.child(1).unwrap().clone(); if matches!(child_container.rule(), Rule::expression_list) { token.set_value(Value::Array( - child_container.children().iter() - .filter(|e| !matches!(e.rule(), Rule::comma)) - .map(|e| e.value()) - .collect::>() + child_container + .children() + .iter() + .filter(|e| !matches!(e.rule(), Rule::comma)) + .map(|e| e.value()) + .collect::>(), )); - } else if matches!(child_container.rule(), Rule::rbracket) { + } else if matches!(child_container.rule(), Rule::rbracket) { token.set_value(Value::Array(vec![])); - } else { + } else { token.set_value(Value::Array(vec![child_container.value()])); } @@ -75,11 +82,11 @@ fn rule_array(token: &mut Token, _state: &mut ParserState) -> Option Option { +fn rule_object(token: &mut Token, _state: &mut ParserState) -> Option { let child_container = token.child(1).unwrap().clone(); if matches!(child_container.rule(), Rule::property_list) { let mut object = ObjectType::new(); - let mut buffer : Vec = vec![]; + let mut buffer: Vec = vec![]; for child in child_container.children() { if child.text() == "," { object.insert(buffer[0].clone(), buffer[1].clone()); @@ -94,7 +101,7 @@ fn rule_object(token: &mut Token, _state: &mut ParserState) -> Option Option Option { +fn rule_variable(token: &mut Token, state: &mut ParserState) -> Option { if let Some(v) = state.constants.get(token.text()) { token.set_value(v.clone()); } else if let Some(v) = state.variables.get(token.text()) { @@ -119,7 +126,7 @@ fn rule_variable(token: &mut Token, state: &mut ParserState) -> Option Option { +fn rule_string(token: &mut Token, _state: &mut ParserState) -> Option { // Remove the first and last characters - the quotes around our string // This would not work great with graphemes like é, but we know that it's // either ' or " so this should be safe @@ -130,13 +137,18 @@ fn rule_string(token: &mut Token, _state: &mut ParserState) -> Option>().join("\\"); + let string = c + .as_str() + .split("\\\\") + .map(|s| { + s.replace("\\'", "\'") + .replace("\\\"", "\"") + .replace("\\n", "\n") + .replace("\\r", "\r") + .replace("\\t", "\t") + }) + .collect::>() + .join("\\"); token.set_value(Value::String(string)); None @@ -145,17 +157,23 @@ fn rule_string(token: &mut Token, _state: &mut ParserState) -> Option Option { +fn rule_int(token: &mut Token, _state: &mut ParserState) -> Option { match token.text().replace(',', "").parse::() { Ok(n) => token.set_value(Value::Integer(n)), - Err(e) => return Some(ParseValueError::new(token, &e.to_string(), ExpectedTypes::Int).into()), + Err(e) => { + return Some(Error::ValueParsing { + input: e.to_string(), + expected_type: ExpectedTypes::Int, + token: token.clone(), + }); + } } None } /// Currency value /// -fn rule_currency(token: &mut Token, _state: &mut ParserState) -> Option { +fn rule_currency(token: &mut Token, _state: &mut ParserState) -> Option { for child in token.clone().children() { if child.rule() == Rule::currency_symbol { token.set_format(match child.text() { @@ -163,7 +181,7 @@ fn rule_currency(token: &mut Token, _state: &mut ParserState) -> Option OutputFormat::Euros, "£" => OutputFormat::Pounds, "¥" => OutputFormat::Yen, - &_ => return Some(InternalError::new(token).into()) + &_ => return Some(Error::Internal(token.clone())), }); } else { token.set_value(child.value()); @@ -176,7 +194,7 @@ fn rule_currency(token: &mut Token, _state: &mut ParserState) -> Option Option { +fn rule_boolean(token: &mut Token, _state: &mut ParserState) -> Option { if token.text().to_lowercase() == *"true" { token.set_value(Value::Boolean(true)); } else if token.text().to_lowercase() == *"false" { @@ -188,47 +206,71 @@ fn rule_boolean(token: &mut Token, _state: &mut ParserState) -> Option Option { +fn rule_float(token: &mut Token, _state: &mut ParserState) -> Option { match token.text().replace(',', "").parse::() { Ok(n) => token.set_value(Value::Float(n)), - Err(e) => return Some(ParseValueError::new(token, &e.to_string(), ExpectedTypes::Float).into()), + Err(e) => { + return Some(Error::ValueParsing { + input: e.to_string(), + expected_type: ExpectedTypes::Float, + token: token.clone(), + }); + } } None } /// Base 8 value /// 0x77 -fn rule_oct(token: &mut Token, _state: &mut ParserState) -> Option { - match parse_radix(token.text(), &["0o","0O"], 8) { +fn rule_oct(token: &mut Token, _state: &mut ParserState) -> Option { + match parse_radix(token.text(), &["0o", "0O"], 8) { Ok(n) => token.set_value(Value::Integer(n)), - Err(e) => return Some(ParseValueError::new(token, &e.to_string(), ExpectedTypes::Int).into()) + Err(e) => { + return Some(Error::ValueParsing { + input: e.to_string(), + expected_type: ExpectedTypes::Int, + token: token.clone(), + }); + } } None } /// Base 2 value /// 0b11 -fn rule_bin(token: &mut Token, _state: &mut ParserState) -> Option { - match parse_radix(token.text(), &["0b","0B"], 2) { +fn rule_bin(token: &mut Token, _state: &mut ParserState) -> Option { + match parse_radix(token.text(), &["0b", "0B"], 2) { Ok(n) => token.set_value(Value::Integer(n)), - Err(e) => return Some(ParseValueError::new(token, &e.to_string(), ExpectedTypes::Int).into()) + Err(e) => { + return Some(Error::ValueParsing { + input: e.to_string(), + expected_type: ExpectedTypes::Int, + token: token.clone(), + }); + } } None } /// Base 16 value /// 0xFF -fn rule_hex(token: &mut Token, _state: &mut ParserState) -> Option { - match parse_radix(token.text(), &["0x","0X"], 16) { +fn rule_hex(token: &mut Token, _state: &mut ParserState) -> Option { + match parse_radix(token.text(), &["0x", "0X"], 16) { Ok(n) => token.set_value(Value::Integer(n)), - Err(e) => return Some(ParseValueError::new(token, &e.to_string(), ExpectedTypes::Int).into()) + Err(e) => { + return Some(Error::ValueParsing { + input: e.to_string(), + expected_type: ExpectedTypes::Int, + token: token.clone(), + }); + } } None } /// indexing operator /// x[5] -fn rule_index_expression(token: &mut Token, _state: &mut ParserState) -> Option { +fn rule_index_expression(token: &mut Token, _state: &mut ParserState) -> Option { let mut source = token.child(0).unwrap().value(); for child in token.children().iter().skip(2) { if child.rule() == Rule::lbracket || child.rule() == Rule::rbracket { @@ -237,29 +279,39 @@ fn rule_index_expression(token: &mut Token, _state: &mut ParserState) -> Option< let index = child.value(); match source { - Value::Object(v) => { - match v.get(&index) { - Some(v) => source = v.clone(), - None => return Some(ObjectKeyError::new(token, &index.as_string()).into()) + Value::Object(v) => match v.get(&index) { + Some(v) => source = v.clone(), + None => { + return Some(Error::Index { + key: index, + token: token.clone(), + }) } }, - - _ => { - match index.as_int() { - Some(i) => { - let array = source.as_array(); - if i as usize >= array.len() || i < 0 { - return Some(ArrayIndexError::new(token, i as usize).into()); - } - - source = array[i as usize].clone(); - }, - None => return Some(ValueTypeError::new(token, ExpectedTypes::Int).into()) + + _ => match index.as_int() { + Some(i) => { + let array = source.as_array(); + if i as usize >= array.len() || i < 0 { + return Some(Error::Index { + key: index, + token: token.clone(), + }); + } + + source = array[i as usize].clone(); } - } + None => { + return Some(Error::ValueType { + value: index, + expected_type: ExpectedTypes::Int, + token: token.clone(), + }) + } + }, } } - + token.set_value(source); None } @@ -279,120 +331,243 @@ mod test_token { #[test] fn test_value_handler_hex() { let mut state = ParserState::new(); - assert_eq!(Value::Integer(255), Token::new("0xFF", &mut state).unwrap().value()); - assert_eq!(Value::Integer(255), Token::new("0XFF", &mut state).unwrap().value()); + assert_eq!( + Value::Integer(255), + Token::new("0xFF", &mut state).unwrap().value() + ); + assert_eq!( + Value::Integer(255), + Token::new("0XFF", &mut state).unwrap().value() + ); } #[test] fn test_value_handler_bin() { let mut state = ParserState::new(); - assert_eq!(Value::Integer(3), Token::new("0b11", &mut state).unwrap().value()); - assert_eq!(Value::Integer(3), Token::new("0B11", &mut state).unwrap().value()); + assert_eq!( + Value::Integer(3), + Token::new("0b11", &mut state).unwrap().value() + ); + assert_eq!( + Value::Integer(3), + Token::new("0B11", &mut state).unwrap().value() + ); } #[test] fn test_value_handler_oct() { let mut state = ParserState::new(); - assert_eq!(Value::Integer(7), Token::new("07", &mut state).unwrap().value()); - assert_eq!(Value::Integer(7), Token::new("0o7", &mut state).unwrap().value()); - assert_eq!(Value::Integer(7), Token::new("0O7", &mut state).unwrap().value()); + assert_eq!( + Value::Integer(7), + Token::new("07", &mut state).unwrap().value() + ); + assert_eq!( + Value::Integer(7), + Token::new("0o7", &mut state).unwrap().value() + ); + assert_eq!( + Value::Integer(7), + Token::new("0O7", &mut state).unwrap().value() + ); } #[test] fn test_value_handler_sci() { let mut state = ParserState::new(); - assert_eq!(Value::Float(5.0), Token::new("5e+0", &mut state).unwrap().value()); - assert_eq!(Value::Float(50.0), Token::new("5e+1", &mut state).unwrap().value()); - assert_eq!(Value::Float(50.0), Token::new("5e1", &mut state).unwrap().value()); - assert_eq!(Value::Float(52.0), Token::new("5.2e+1", &mut state).unwrap().value()); - assert_eq!(Value::Float(0.52), Token::new("5.2e-1", &mut state).unwrap().value()); - assert_eq!(Value::Float(0.020000000000000004), Token::new("1e-1.2", &mut state).unwrap().value()); + assert_eq!( + Value::Float(5.0), + Token::new("5e+0", &mut state).unwrap().value() + ); + assert_eq!( + Value::Float(50.0), + Token::new("5e+1", &mut state).unwrap().value() + ); + assert_eq!( + Value::Float(50.0), + Token::new("5e1", &mut state).unwrap().value() + ); + assert_eq!( + Value::Float(52.0), + Token::new("5.2e+1", &mut state).unwrap().value() + ); + assert_eq!( + Value::Float(0.52), + Token::new("5.2e-1", &mut state).unwrap().value() + ); + assert_eq!( + Value::Float(0.020000000000000004), + Token::new("1e-1.2", &mut state).unwrap().value() + ); } #[test] fn test_value_handler_float() { let mut state = ParserState::new(); - assert_eq!(Value::Float(10000.0), Token::new("10,000.0", &mut state).unwrap().value()); - assert_eq!(Value::Float(1.0), Token::new("1.00000", &mut state).unwrap().value()); + assert_eq!( + Value::Float(10000.0), + Token::new("10,000.0", &mut state).unwrap().value() + ); + assert_eq!( + Value::Float(1.0), + Token::new("1.00000", &mut state).unwrap().value() + ); } #[test] fn test_value_handler_boolean() { let mut state = ParserState::new(); - assert_eq!(Value::Boolean(true), Token::new("true", &mut state).unwrap().value()); - assert_eq!(Value::Boolean(false), Token::new("false", &mut state).unwrap().value()); + assert_eq!( + Value::Boolean(true), + Token::new("true", &mut state).unwrap().value() + ); + assert_eq!( + Value::Boolean(false), + Token::new("false", &mut state).unwrap().value() + ); } #[test] fn test_value_handler_currency() { let mut state = ParserState::new(); - assert_eq!(Value::Float(10000.0), Token::new("$10,000.00", &mut state).unwrap().value()); - assert_eq!(Value::Float(1.0), Token::new("$1.0", &mut state).unwrap().value()); - assert_eq!(Value::Integer(1), Token::new("£1", &mut state).unwrap().value()); - assert_eq!(Value::Integer(1), Token::new("€1", &mut state).unwrap().value()); - assert_eq!(Value::Integer(1), Token::new("¥1", &mut state).unwrap().value()); + assert_eq!( + Value::Float(10000.0), + Token::new("$10,000.00", &mut state).unwrap().value() + ); + assert_eq!( + Value::Float(1.0), + Token::new("$1.0", &mut state).unwrap().value() + ); + assert_eq!( + Value::Integer(1), + Token::new("£1", &mut state).unwrap().value() + ); + assert_eq!( + Value::Integer(1), + Token::new("€1", &mut state).unwrap().value() + ); + assert_eq!( + Value::Integer(1), + Token::new("¥1", &mut state).unwrap().value() + ); } #[test] fn test_value_handler_int() { let mut state = ParserState::new(); - assert_eq!(Value::Integer(10000), Token::new("10,000", &mut state).unwrap().value()); - assert_eq!(Value::Integer(99), Token::new("99", &mut state).unwrap().value()); - assert_eq!(Value::Integer(0), Token::new("0", &mut state).unwrap().value()); + assert_eq!( + Value::Integer(10000), + Token::new("10,000", &mut state).unwrap().value() + ); + assert_eq!( + Value::Integer(99), + Token::new("99", &mut state).unwrap().value() + ); + assert_eq!( + Value::Integer(0), + Token::new("0", &mut state).unwrap().value() + ); } #[test] fn test_value_handler_string() { let mut state = ParserState::new(); - assert_eq!(Value::String("".to_string()), Token::new("''", &mut state).unwrap().value()); - assert_eq!(Value::String("\"".to_string()), Token::new("'\"'", &mut state).unwrap().value()); - assert_eq!(Value::String("'".to_string()), Token::new("\"'\"", &mut state).unwrap().value()); - assert_eq!(Value::String("test".to_string()), Token::new("'test'", &mut state).unwrap().value()); - assert_eq!(Value::String("test".to_string()), Token::new("\"test\"", &mut state).unwrap().value()); + assert_eq!( + Value::String("".to_string()), + Token::new("''", &mut state).unwrap().value() + ); + assert_eq!( + Value::String("\"".to_string()), + Token::new("'\"'", &mut state).unwrap().value() + ); + assert_eq!( + Value::String("'".to_string()), + Token::new("\"'\"", &mut state).unwrap().value() + ); + assert_eq!( + Value::String("test".to_string()), + Token::new("'test'", &mut state).unwrap().value() + ); + assert_eq!( + Value::String("test".to_string()), + Token::new("\"test\"", &mut state).unwrap().value() + ); } #[test] fn test_string_escapes() { let mut state = ParserState::new(); - assert_eq!(Value::String("\"".to_string()), Token::new("'\\\"'", &mut state).unwrap().value()); - assert_eq!(Value::String("\'".to_string()), Token::new("'\\''", &mut state).unwrap().value()); - assert_eq!(Value::String("\\".to_string()), Token::new("'\\\\'", &mut state).unwrap().value()); - assert_eq!(Value::String("\n".to_string()), Token::new("'\\n'", &mut state).unwrap().value()); - assert_eq!(Value::String("\n".to_string()), Token::new("'\\n'", &mut state).unwrap().value()); - assert_eq!(Value::String("\r".to_string()), Token::new("'\\r'", &mut state).unwrap().value()); - assert_eq!(Value::String("\t".to_string()), Token::new("'\\t'", &mut state).unwrap().value()); + assert_eq!( + Value::String("\"".to_string()), + Token::new("'\\\"'", &mut state).unwrap().value() + ); + assert_eq!( + Value::String("\'".to_string()), + Token::new("'\\''", &mut state).unwrap().value() + ); + assert_eq!( + Value::String("\\".to_string()), + Token::new("'\\\\'", &mut state).unwrap().value() + ); + assert_eq!( + Value::String("\n".to_string()), + Token::new("'\\n'", &mut state).unwrap().value() + ); + assert_eq!( + Value::String("\n".to_string()), + Token::new("'\\n'", &mut state).unwrap().value() + ); + assert_eq!( + Value::String("\r".to_string()), + Token::new("'\\r'", &mut state).unwrap().value() + ); + assert_eq!( + Value::String("\t".to_string()), + Token::new("'\\t'", &mut state).unwrap().value() + ); } #[test] fn test_value_handler_identifier() { let mut state = ParserState::new(); Token::new("x=4", &mut state).unwrap(); - assert_eq!(Value::Integer(4), Token::new("x", &mut state).unwrap().value()); + assert_eq!( + Value::Integer(4), + Token::new("x", &mut state).unwrap().value() + ); assert_eq!(true, Token::new("y + 1", &mut state).is_err()); } #[test] fn test_value_handler_array() { let mut state = ParserState::new(); - assert_eq!(Value::Array(vec![ - Value::Integer(5), - Value::Float(2.0), - Value::String("test".to_string()) - ]), Token::new("[5, 2.0, 'test']", &mut state).unwrap().value()); - assert_eq!(Value::Array(vec![ - Value::Integer(5) - ]), Token::new("[5]", &mut state).unwrap().value()); - assert_eq!(Value::Array(vec![ - ]), Token::new("[]", &mut state).unwrap().value()); + assert_eq!( + Value::Array(vec![ + Value::Integer(5), + Value::Float(2.0), + Value::String("test".to_string()) + ]), + Token::new("[5, 2.0, 'test']", &mut state).unwrap().value() + ); + assert_eq!( + Value::Array(vec![Value::Integer(5)]), + Token::new("[5]", &mut state).unwrap().value() + ); + assert_eq!( + Value::Array(vec![]), + Token::new("[]", &mut state).unwrap().value() + ); } #[test] fn test_rule_index_expression() { let mut state = ParserState::new(); Token::new("array = [1,2,3]", &mut state).unwrap(); - assert_eq!(Value::Integer(3), Token::new("array[2]", &mut state).unwrap().value()); + assert_eq!( + Value::Integer(3), + Token::new("array[2]", &mut state).unwrap().value() + ); assert_eq!(true, Token::new("array[-1]", &mut state).is_err()); assert_eq!(true, Token::new("array['test']", &mut state).is_err()); assert_eq!(true, Token::new("array[3]", &mut state).is_err()); } -} \ No newline at end of file +} diff --git a/src/help.rs b/src/help.rs index 3b28110..b316a36 100644 --- a/src/help.rs +++ b/src/help.rs @@ -1,5 +1,5 @@ -use crate::{ParserState, state::UserFunction}; -use std::{fmt, collections::HashMap}; +use crate::{state::UserFunction, ParserState}; +use std::{collections::HashMap, fmt}; fn get_divider(title: &str) -> String { (0..title.len()).map(|_| "=").collect::() @@ -13,7 +13,7 @@ fn noun_case(text: &str) -> String { pub struct HelpBlock { title: String, entries: Vec, - order: usize + order: usize, } impl HelpBlock { @@ -21,7 +21,7 @@ impl HelpBlock { Self { title: title.to_string(), entries: Vec::new(), - order: i + order: i, } } @@ -46,25 +46,27 @@ impl fmt::Display for HelpBlock { } pub struct Help { - blocks: HashMap + blocks: HashMap, } impl Help { pub fn new() -> Self { - Self { blocks: HashMap::new() } + Self { + blocks: HashMap::new(), + } } pub fn add_std(&mut self, state: &mut ParserState) { self.add_std_functions(state); self.add_std_decorators(state); - + #[cfg(feature = "extensions")] self.add_extensions(state); - + self.add_user_functions(state); self.add_variables(state); } - + /// Add the built-in functions to the help instance pub fn add_std_functions(&mut self, state: &ParserState) { for (category, functions) in state.functions.all_by_category() { @@ -90,17 +92,21 @@ impl Help { let title = format!("{} v{}", extension.name(), extension.version()); self.add_block(&title) .add_entry(&format!("Author: {}", extension.author())) - .add_entry(&format!("Functions: {}", extension.functions().join(", "))) - .add_entry(&format!("Decorators: {}", extension.decorators().into_iter().map(|f| - format!("@{}", f) - ).collect::>().join(", "))); + .add_entry(&format!( + "Functions:\n {}", + extension.function_signatures().join("\n ") + )) + .add_entry(&format!( + "Decorators:\n {}", + extension.decorator_signatures().join("\n ") + )); } } pub fn add_user_functions(&mut self, state: &ParserState) { let block = self.add_block("User-defined Functions"); let mut functions: Vec<&UserFunction> = state.user_functions.values().collect(); - functions.sort_by(|f1, f2|f1.name().cmp(f2.name())); + functions.sort_by(|f1, f2| f1.name().cmp(f2.name())); if functions.is_empty() { block.add_entry(" -- None --"); @@ -122,7 +128,8 @@ impl Help { } pub fn add_block(&mut self, title: &str) -> &mut HelpBlock { - self.blocks.insert(title.to_string(), HelpBlock::new(title, self.blocks.len())); + self.blocks + .insert(title.to_string(), HelpBlock::new(title, self.blocks.len())); self.get_block(title).unwrap() } @@ -136,9 +143,11 @@ impl fmt::Display for Help { let mut blocks: Vec<&HelpBlock> = self.blocks.values().collect(); blocks.sort_by_key(|f| f.order()); - let text = blocks.iter() + let text = blocks + .iter() .map(|b| format!("{}\n{}\n{}\n", b.title(), get_divider(b.title()), b)) - .collect::>().join("\n"); - write!(f, "{}", text ) + .collect::>() + .join("\n"); + write!(f, "{}", text) } -} \ No newline at end of file +} diff --git a/src/lib.rs b/src/lib.rs index 9c2c3c9..864ddc9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,25 +2,25 @@ //! [![Crates.io](https://img.shields.io/crates/v/lavendeux-parser.svg)](https://crates.io/crates/lavendeux-parser) //! [![Build Status](https://github.com/rscarson/lavendeux-parser/workflows/Rust/badge.svg)](https://github.com/rscarson/lavendeux-parser/actions?workflow=Rust) //! [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/rscarson/lavendeux-parser/master/LICENSE) -//! +//! //! lavendeux-parser is rust library that provides an extensible parsing engine for mathematical expressions. //! It enables the parsing of user-supplied expressions to operate on a variety of types of data. //! It supports variable and function assignments, a variety of datatypes, and can //! be extended easily at runtime through extensions written in javascript. -//! +//! //! Extensions are run in a sandboxed environment with no host or network access. //! This project is the engine behind [Lavendeux](https://rscarson.github.io/lavendeux/). -//! +//! //! ## Getting Started //! To use it, create a `ParserState` object, and use it to tokenize input with `Token::new`: //! ```rust -//! use lavendeux_parser::{ParserState, ParserError, Token, Value}; -//! -//! fn main() -> Result<(), ParserError> { +//! use lavendeux_parser::{ParserState, Error, Token, Value}; +//! +//! fn main() -> Result<(), Error> { //! // Create a new parser, and tokenize 2 lines //! let mut state : ParserState = ParserState::new(); //! let lines = Token::new("x=9\nsqrt(x) @bin", &mut state)?; -//! +//! //! // The resulting token contains the resulting values and text //! assert_eq!(lines.text(), "9\n0b11"); //! assert_eq!(lines.child(1).unwrap().value(), Value::Float(3.0)); @@ -30,32 +30,31 @@ //! ``` //! The result will be a `Token` object: //! ```rust -//! use lavendeux_parser::{ParserState, ParserError, Token, Value}; -//! -//! fn main() -> Result<(), ParserError> { +//! use lavendeux_parser::{ParserState, Error, Token, Value}; +//! +//! fn main() -> Result<(), Error> { //! let mut state : ParserState = ParserState::new(); //! let lines = Token::new("x=9\nsqrt(x) @bin", &mut state)?; -//! +//! //! // String representation of the full result -//! assert_eq!(lines.text(), "9\n0b11"); -//! +//! assert_eq!(lines.text(), "9\n0b11"); +//! //! // String representation of the first line's result //! assert_eq!(lines.child(0).unwrap().text(), "9"); -//! +//! //! // Actual value of the first line's result //! // Values are integers, floats, booleans or strings //! let value = lines.child(0).unwrap().value(); //! assert_eq!(value.as_int().unwrap(), 9); //! assert_eq!(true, matches!(value, Value::Integer(_))); -//! +//! //! Ok(()) //! } //! ``` -//! +//! //! A number of functions and @decorators are available for expressions to use - add more using the state: //! ```rust -//! use lavendeux_parser::{ParserState, ParserError, DecoratorDefinition, FunctionDefinition, FunctionArgument, Value}; -//! use lavendeux_parser::errors::*; +//! use lavendeux_parser::{ParserState, Error, DecoratorDefinition, FunctionDefinition, FunctionArgument, Value, ExpectedTypes}; //! //! let mut state : ParserState = ParserState::new(); //! state.decorators.register(DecoratorDefinition { @@ -64,7 +63,7 @@ //! argument: ExpectedTypes::Any, //! handler: |_, _token, input| Ok(input.as_string().to_uppercase()) //! }); -//! +//! //! // Functions take in an array of values, and return a single value //! state.functions.register(FunctionDefinition { //! name: "echo", @@ -77,14 +76,14 @@ //! Ok(Value::String(args.get("input").required().as_string())) //! } //! }); -//! +//! //! // Expressions being parsed can now call new_function(), and use the @new_decorator //! ``` -//! +//! //! Javascript extensions give a flexible way of adding functionality at runtime. //! Extensions are run in a sandboxed environment, with no network or host access. //! An extension must implement an extension() function taking no arguments and returning an object describing the extension - see example below -//! +//! //! Extensions can also access parser variables through getState, and mutate the state with setState //! Always check if getState is defined prior to use, to maintain compatibility with older versions of the parser. //! @@ -110,7 +109,7 @@ //! }, //! } //! } -//! +//! //! /** //! * This function can be called from Lavendeux as callable_name(...) //! * args is an array of value objects with either the key Integer, Float or String @@ -122,7 +121,7 @@ //! "Integer": 5, //! }; //! } -//! +//! //! /** //! * Functions can also be stateful, gaining access to the parser's variables //! * It takes in arguments and a state, a hash of strings and values @@ -132,7 +131,7 @@ //! state.foobar = {"Integer": 5}; //! return [state.foobar, state]; //! } -//! +//! //! /** //! * This decorator can be called from Lavendeux as @callable_name //! * arg is a value object with either the key Integer, Float or String @@ -143,23 +142,23 @@ //! return "formatted value"; //! } //! ``` -//! +//! //! ## Using Extensions //! Extensions are enabled by default, and can be excluded by disabling the crate's "extensions" feature -//! +//! //! Extensions can be loaded as follows: //! ```rust -//! use lavendeux_parser::{ParserState, ParserError, Value, Token}; -//! -//! fn main() -> Result<(), ParserError> { +//! use lavendeux_parser::{ParserState, Error, Value, Token}; +//! +//! fn main() -> Result<(), Error> { //! let mut state : ParserState = ParserState::new(); -//! +//! //! // Load one extension //! state.extensions.load("example_extensions/colour_utils.js"); -//! +//! //! // Load a whole directory - this will return a vec of Extension/Error results //! state.extensions.load_all("./example_extensions"); -//! +//! //! // Once loaded, functions and @decorators decribed in the extensions //! // can be called in expressions being parsed //! let token = Token::new("complement(0xFF0000) @colour", &mut state)?; @@ -167,84 +166,84 @@ //! Ok(()) //! } //! ``` -//! +//! //! ## Syntax //! Expressions can be composed of integers, floats, strings, as well as numbers of various bases: //! ```text //! // Integer, floating point or scientific notation numbers //! 5 + 5.56 + .2e+3 -//! +//! //! // Currency values //! // Note that no exchange rate is being applied automatically //! $1,000.00 == ¥1,000.00 -//! +//! //! // Scientific numbers can be represented a number of ways //! 5.6e+7 - .6E7 + .2e-3 -//! +//! //! // Booleans //! in_range = 5 > 3 && 5 < 10 //! true || false -//! +//! //! // Integers can also be represented in base 2, 8 or 16 //! 0xFFA & 0b110 & 0777 -//! +//! //! // Strings are also supported //! concat("foo", "bar") -//! +//! //! [1, 2, "test"] // Arrays can be composed of any combination of types //! [10, 12] + [1.2, 1.3] // Operations can be performed between arrays of the same size //! 2 * [10, 5] // Operations can also be applied between scalar values and arrays //! [false, 0, true] == true // An array evaluates to true if any element is true //! a = [1, 2, "test"] //! a[1] // You can use indexing on array elements -//! +//! //! // Objects are also supported: //! b = {3: "test", "plumbus": true} //! b["plumbus"] //! ``` -//! +//! //! Beyond the simpler operators, the following operations are supported: //! ```text //! 5 ** 2 // Exponentiation //! 6 % 2 // Modulo //! 3! // Factorial -//! +//! //! // Bitwise operators AND, OR, and XOR: //! 0xF & 0xA | 0x2 ^ 0xF -//! +//! //! // Bitwise SHIFT, and NOT //! 0xF << 1 //! 0x1 >> 2 //! ~0xA -//! +//! //! // Boolean operators //! true || false && true //! 1 < 2 > 5 // true //! ``` -//! +//! //! You can also assign values to variables to be used later: //! They are case sensitive, and can be composed of underscores or alphanumeric characters //! ```text //! // You can also assign values to variables to be used later //! x = 0xFFA & 0xFF0 //! x - 55 // The result will be 200 -//! +//! //! // A few constants are also pre-defined //! value = pi * e * tau -//! +//! //! // You can also define functions //! f(x) = 2*x**2 + 3*x + 5 //! f(2.3) -//! +//! //! // Functions work well with arrays //! sum(a) = element(a, 0) + ( len(a)>1 ? sum(dequeue(a)) : 0 ) //! sum([10, 10, 11]) -//! +//! //! // Recursive functions work too! //! factorial(x) = x==0 ? 1 : (x * factorial(x - 1) ) //! factorial(5) //! ``` -//! +//! //! Decorators can be put at the end of a line to change the output format. Valid decorators include: //! ```text //! 255 @hex // The result will be 0xFF @@ -253,7 +252,7 @@ //! 5 @usd // Also works with @dollars @cad, @aud, @yen, @pounds, or @euros //! 1647950086 @utc // 2022-03-22 11:54:46 //! ``` -//! +//! //! Full list of built-in types, operators and functions: //! ```text //! Operators @@ -262,7 +261,7 @@ //! Boolean: AND (true && false), OR (true || false), CMP (1 < 2, 4 >= 5), EQ (1 == 1, 2 != 5) //! Arithmetic: Add/Sub (+, -), Mul/Div (*, /), Exponentiation (**), Modulo (%), Implied Mul ((5)(5), 5x) //! Unary: Factorial (5!!), Negation (-1, -(1+1)) -//! +//! //! Data Types //! ========== //! String: Text delimited by 'quotes' or "double-quotes" @@ -274,7 +273,7 @@ //! Object: A comma separated list of key/value pairs in curly braces; {'test': 5} //! Variable: An identifier representing a value. Set it with x=5, then use it in an expression (5x) //! Contant: A preset read-only variable representing a common value, such as pi, e, and tau -//! +//! //! Misc Functions //! ============== //! atob(input): Convert a string into a base64 encoded string @@ -287,7 +286,7 @@ //! time(): Returns a unix timestamp for the current system time //! urldecode(input): Decode urlencoded character escape sequences in a string //! urlencode(input): Escape characters in a string for use in a URL -//! +//! //! Network Functions //! ================= //! api(name, [endpoint]): Make a call to a registered API @@ -297,7 +296,7 @@ //! get(url, [headers]): Return the resulting text-format body of an HTTP GET call //! post(url, body, [headers]): Return the resulting text-format body of an HTTP POST call //! resolve(hostname): Returns the IP address associated to a given hostname -//! +//! //! Arrays Functions //! ================ //! dequeue(array): Remove the first element from an array @@ -311,7 +310,7 @@ //! push(array, element): Add an element to the end of an array //! remove(input, index): Removes an element from an array //! values(input): Get a list of values in the object or array -//! +//! //! Strings Functions //! ================= //! concat([s1, s2]): Concatenate a set of strings @@ -322,14 +321,14 @@ //! substr(s, start, [length]): Returns a substring from s, beginning at [start], and going to the end, or for [length] characters //! trim(s): Trim whitespace from a string //! uppercase(s): Converts the string s to uppercase -//! +//! //! Cryptography Functions //! ====================== //! choose(option1, option2): Returns any one of the provided arguments at random //! md5(input1, input2): Returns the MD5 hash of a given string //! rand([m], [n]): With no arguments, return a float from 0 to 1. Otherwise return an integer from 0 to m, or m to n //! sha256(input1, input2): Returns the SHA256 hash of a given string -//! +//! //! Math Functions //! ============== //! abs(n): Returns the absolute value of n @@ -358,7 +357,7 @@ //! tanh(n): Calculate the hyperbolic tangent of n //! to_degrees(n): Convert the given radian value into degrees //! to_radians(n): Convert the given degree value into radians -//! +//! //! Built-in Decorators //! =================== //! @array: Format a number as an array @@ -392,11 +391,15 @@ #![doc(html_root_url = "https://docs.rs/lavendeux-parser/0.9.0")] #![warn(missing_docs)] -mod help; +mod errors; mod handlers; +mod help; +mod state; mod token; mod value; -mod state; + +mod expected_types; +pub use expected_types::ExpectedTypes; #[macro_use] pub mod test; @@ -404,7 +407,9 @@ pub mod test; mod network; mod functions; -pub use functions::{FunctionDefinition, FunctionArgument, FunctionArgumentCollection, FunctionHandler}; +pub use functions::{ + FunctionArgument, FunctionArgumentCollection, FunctionDefinition, FunctionHandler, +}; mod decorators; pub use decorators::{DecoratorDefinition, DecoratorHandler}; @@ -416,14 +421,13 @@ mod extensions; pub use extensions::Extension; /// Module defining errors that can occur during parsing -pub mod errors; -pub use errors::ParserError; -pub use token::Token; +pub use errors::Error; pub use state::ParserState; -pub use value::Value; +pub use token::Token; pub use value::ArrayType; -pub use value::IntegerType; pub use value::FloatType; +pub use value::IntegerType; +pub use value::Value; #[cfg(test)] mod test_token { @@ -436,4 +440,4 @@ mod test_token { fn test_html_root_url() { version_sync::assert_html_root_url_updated!("src/lib.rs"); } -} \ No newline at end of file +} diff --git a/src/network.rs b/src/network/mod.rs similarity index 61% rename from src/network.rs rename to src/network/mod.rs index 4d4c5de..a94885a 100644 --- a/src/network.rs +++ b/src/network/mod.rs @@ -2,4 +2,4 @@ mod utils; pub use utils::*; mod api_instance; -pub use api_instance::*; \ No newline at end of file +pub use api_instance::*; diff --git a/src/state.rs b/src/state.rs index 77b377a..fdcff5a 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,8 +1,8 @@ use super::value::Value; use std::collections::HashMap; -use super::functions; use super::decorators; +use super::functions; use super::network::ApiInstance; @@ -16,18 +16,20 @@ const MAX_STACK_DEPTH: usize = 50; pub struct UserFunction { name: String, arguments: Vec, - definition: String + definition: String, } impl UserFunction { /// Return a new user function - /// + /// /// # Arguments /// * `name` - Function name /// * `arguments` - Arguments expected by the function /// * `definition` - Function definition string pub fn new(name: String, arguments: Vec, definition: String) -> Self { Self { - name, arguments, definition + name, + arguments, + definition, } } @@ -35,12 +37,12 @@ impl UserFunction { pub fn name(&self) -> &str { &self.name } - + /// Return the function's expected arguments pub fn arguments(&self) -> &Vec { &self.arguments } - + /// Return the function's definition string pub fn definition(&self) -> &str { &self.definition @@ -48,17 +50,21 @@ impl UserFunction { /// Return the function's signature pub fn signature(&self) -> String { - format!("{}({}) = {}", self.name(), self.arguments().join(", "), self.definition()) + format!( + "{}({}) = {}", + self.name(), + self.arguments().join(", "), + self.definition() + ) } } - /// Represents the current state of the parser /// Holds the functions, decorators, variables and extensions /// available for expressions to use #[derive(Clone)] pub struct ParserState { - depth : usize, + depth: usize, /// The assigned variables usable in expressions pub variables: HashMap, @@ -87,7 +93,7 @@ impl Default for ParserState { fn default() -> Self { Self::new() } -} +} impl ParserState { /// Create a new parser state @@ -165,4 +171,4 @@ impl ParserState { pub fn depth(&self) -> usize { self.depth } -} \ No newline at end of file +} diff --git a/src/test.rs b/src/test.rs index 50b548c..d819eb2 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,5 +1,5 @@ //! Contains macros to test the parser -//! +//! /// Assert that a token parses succesfully into a given string /// assert_token_text_stateful!("input text", target_value) @@ -8,7 +8,7 @@ macro_rules! assert_token_text_stateful { ($input:expr, $text:expr, $state:expr) => { match Token::new($input, $state) { Ok(result) => assert_eq!($text, result.text()), - Err(e) => panic!("{}", e) + Err(e) => panic!("{}", e), } }; } @@ -33,7 +33,7 @@ macro_rules! assert_token_value_stateful { ($input:expr, $value:expr, $state:expr) => { match Token::new($input, $state) { Ok(result) => assert_eq!($value, result.value()), - Err(e) => panic!("{}", e) + Err(e) => panic!("{}", e), } }; } @@ -58,8 +58,15 @@ macro_rules! assert_token_error_stateful { ($input:expr, $error:ident, $state:expr) => { match Token::new($input, $state) { Ok(result) => panic!("No error in {} (result was {})", $input, result.text()), - Err(e) => if !matches!(e, crate::ParserError::$error(_)) { - panic!("Error '{}' does not match {:?} for token {}", e, stringify!($error), $input) + Err(e) => { + if !matches!(e, $crate::Error::$error { .. }) { + panic!( + "Error '{}' does not match {:?} for token {}", + e, + stringify!($error), + $input + ) + } } } }; @@ -76,4 +83,4 @@ macro_rules! assert_token_error { }; } #[allow(unused_imports)] -pub(crate) use assert_token_error; \ No newline at end of file +pub(crate) use assert_token_error; diff --git a/src/token.rs b/src/token.rs index 24e3163..225e4f0 100644 --- a/src/token.rs +++ b/src/token.rs @@ -1,4 +1,6 @@ -use crate::{ParserState, Value, errors::*}; +use std::fmt::Display; + +use crate::{Error, ParserState, Value}; extern crate pest; extern crate pest_derive; @@ -13,19 +15,22 @@ struct LavendeuxParser; pub enum OutputFormat { Unknown = 0, Default = 10, - Dollars = 20, Euros = 21, Pounds = 22, Yen = 23 + Dollars = 20, + Euros = 21, + Pounds = 22, + Yen = 23, } /// Represents a token tree for a parsed expression /// The root contains the text result of parsing the expression, /// as well as one child per line being parsed -/// +/// /// So if you were to parse: /// ```text /// 5 + 5 /// sqrt(5!) /// ``` -/// +/// /// The token tree would look like this: /// ```text /// script: 5 + 5\nsqrt(5!) @@ -40,7 +45,7 @@ pub enum OutputFormat { /// int: 5 /// operator: ! /// ``` -/// +/// /// Each token in the tree stores the text and actual value representations of the result #[derive(Clone, Debug)] pub struct Token { @@ -50,7 +55,7 @@ pub struct Token { format: OutputFormat, value: Value, index: usize, - children: Vec + children: Vec, } impl Default for Token { @@ -62,26 +67,26 @@ impl Default for Token { value: Value::None, children: Vec::new(), index: 0, - rule: Rule::script + rule: Rule::script, } } } pub trait LavendeuxHandler { - fn handle_tree(&self, token: &mut Token, state: &mut ParserState) -> Result<(), ParserError>; + fn handle_tree(&self, token: &mut Token, state: &mut ParserState) -> Result<(), Error>; } impl Token { /// Parses an input string, and returns the resulting token tree - /// + /// /// ```rust - /// use lavendeux_parser::{ParserState, ParserError, Token, Value}; - /// - /// fn main() -> Result<(), ParserError> { + /// use lavendeux_parser::{ParserState, Error, Token, Value}; + /// + /// fn main() -> Result<(), Error> { /// // Create a new parser, and tokenize 2 lines /// let mut state : ParserState = ParserState::new(); /// let lines = Token::new("x=9\nsqrt(x) @bin", &mut state)?; - /// + /// /// // The resulting token contains the resulting values and text /// assert_eq!(lines.text(), "9\n0b11"); /// assert_eq!(lines.child(1).unwrap().value(), Value::Float(3.0)); @@ -89,17 +94,17 @@ impl Token { /// Ok(()) /// } /// ``` - /// + /// /// # Arguments /// * `input` - Source string /// * `state` - The current parser state - pub fn new(input: &str, state: &mut ParserState) -> Result { + pub fn new(input: &str, state: &mut ParserState) -> Result { Self::parse(input, crate::handlers::Handler::default(), state) } /// Convert one pair into a token /// Does not process child tokens - /// + /// /// # Arguments /// * `input` - Source fn from_pair(input: pest::iterators::Pair) -> Token { @@ -110,14 +115,14 @@ impl Token { format: OutputFormat::Unknown, value: Value::None, index: input.as_span().start(), - children: Vec::new() + children: Vec::new(), } } /// Convert raw input into a dummy Token /// Usually for error handling /// Does not process child tokens - /// + /// /// # Arguments /// * `input` - Source input pub fn dummy(input: &str) -> Token { @@ -128,54 +133,55 @@ impl Token { format: OutputFormat::Unknown, value: Value::None, index: 0, - children: Vec::new() + children: Vec::new(), } } /// Parses an input string, and returns the resulting token tree - /// + /// /// # Arguments /// * `input` - Source string /// * `handler` - A LavendeuxHandler /// * `state` - The current parser state - fn parse(input: &str, handler: A, state: &mut ParserState) -> Result - where A: LavendeuxHandler { + fn parse(input: &str, handler: A, state: &mut ParserState) -> Result + where + A: LavendeuxHandler, + { let pairs = LavendeuxParser::parse(Rule::script, input); match pairs { - Ok(mut r) => { - match r.next() { - None => Ok(Self::default()), - Some(p) => { - let mut token = Self::build_tree(p); - handler.handle_tree(&mut token, state)?; - Ok(token) - }, + Ok(mut r) => match r.next() { + None => Ok(Self::default()), + Some(p) => { + let mut token = Self::build_tree(p); + handler.handle_tree(&mut token, state)?; + Ok(token) } - }, - Err(_) => Err(PestError::new(&Token::dummy(input)).into()) + }, + Err(e) => Err(Error::Pest(e, Token::dummy(input)).into()), } } /// Build a token tree from a parser pair - /// + /// /// # Arguments /// * `root` - Pair that will form the tree's root fn build_tree(root: pest::iterators::Pair) -> Token { // Collapse tree let mut next_pair = root; - let mut children : Vec<_> = next_pair.clone().into_inner().collect(); - while children.len() == 1 && next_pair.as_rule() != Rule::script && next_pair.as_rule() != Rule::line { + let mut children: Vec<_> = next_pair.clone().into_inner().collect(); + while children.len() == 1 + && next_pair.as_rule() != Rule::script + && next_pair.as_rule() != Rule::line + { next_pair = children[0].clone(); children = next_pair.clone().into_inner().collect(); } // Collect basic properties let mut token = Token::from_pair(next_pair); - + for child in children { - token.children.push( - Self::build_tree(child) - ); + token.children.push(Self::build_tree(child)); } token @@ -255,6 +261,12 @@ impl Token { } } +impl Display for Token { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.text()) + } +} + #[cfg(test)] mod test_token { use super::*; @@ -269,7 +281,10 @@ mod test_token { #[test] fn test_from_input() { let mut state: ParserState = ParserState::new(); - assert_eq!(Value::Integer(10), Token::new("5+5", &mut state).unwrap().value); + assert_eq!( + Value::Integer(10), + Token::new("5+5", &mut state).unwrap().value + ); } #[test] @@ -318,7 +333,10 @@ mod test_token { // String assert_token_value!("'test'", Value::String("test".to_string())); - assert_token_value!(" ' test ' ", Value::String(" test ".to_string())); + assert_token_value!( + " ' test ' ", + Value::String(" test ".to_string()) + ); assert_token_value!("'test\"'", Value::String("test\"".to_string())); assert_token_value!("'test\\\"'", Value::String("test\"".to_string())); assert_token_value!("\"test\\\'\"", Value::String("test\'".to_string())); @@ -326,7 +344,9 @@ mod test_token { // Identifier state.variables.insert("x".to_string(), Value::Integer(99)); - state.variables.insert("x_9".to_string(), Value::Integer(99)); + state + .variables + .insert("x_9".to_string(), Value::Integer(99)); assert_token_value_stateful!("x", Value::Integer(99), &mut state); assert_token_value_stateful!("x_9", Value::Integer(99), &mut state); } @@ -351,23 +371,35 @@ mod test_token { // Comments assert_token_value!("5 //test", Value::Integer(5)); assert_token_value!("//test", Value::None); - + // Assignment expression state = ParserState::new(); assert_token_value_stateful!("x = 5", Value::Integer(5), &mut state); assert_eq!(1, state.variables.len()); - + // Indexed assignment expression state = ParserState::new(); - state.variables.insert("x".to_string(), Value::Array(vec![Value::Integer(5)])); + state + .variables + .insert("x".to_string(), Value::Array(vec![Value::Integer(5)])); assert_token_value_stateful!("x[0] = 3", Value::Integer(3), &mut state); - assert_token_value_stateful!("x[1] = 'test'", Value::String("test".to_string()), &mut state); - assert_token_error_stateful!("x[-1] = 5", ArrayIndex, &mut state); + assert_token_value_stateful!( + "x[1] = 'test'", + Value::String("test".to_string()), + &mut state + ); + assert_token_error_stateful!("x[-1] = 5", Index, &mut state); assert_token_error_stateful!("x['test'] = 5", ValueType, &mut state); - assert_token_error_stateful!("x[3] = 5", ArrayIndex, &mut state); + assert_token_error_stateful!("x[3] = 5", Index, &mut state); assert_eq!(1, state.variables.len()); - assert_eq!(Value::Integer(3), state.variables.get("x").unwrap().as_array()[0]); - assert_eq!(Value::String("test".to_string()), state.variables.get("x").unwrap().as_array()[1]); + assert_eq!( + Value::Integer(3), + state.variables.get("x").unwrap().as_array()[0] + ); + assert_eq!( + Value::String("test".to_string()), + state.variables.get("x").unwrap().as_array()[1] + ); // Term assert_token_value!("(5)", Value::Integer(5)); @@ -390,8 +422,8 @@ mod test_token { // Overflows and errors assert_token_error!("1/0", Overflow); assert_token_error!("5+5\n 1/0", Overflow); - assert_token_error!("99999999999999999999999999999999999999999", ParseValue); - assert_token_error!("1+99999999999999999999999999999999999999999", ParseValue); + assert_token_error!("99999999999999999999999999999999999999999", ValueParsing); + assert_token_error!("1+99999999999999999999999999999999999999999", ValueParsing); assert_token_error!("999999999999999999*999999999999999999", Overflow); assert_token_error!("999!", Overflow); @@ -401,8 +433,14 @@ mod test_token { assert_token_value!("false ? 1/0 : 2", Value::Integer(2)); // arrays - assert_token_value!("[10, 12] + [1.2, 1.3]", Value::Array(vec![Value::Float(11.2), Value::Float(13.3)])); - assert_token_value!("2 * [10, 5]", Value::Array(vec![Value::Integer(20), Value::Integer(10)])); + assert_token_value!( + "[10, 12] + [1.2, 1.3]", + Value::Array(vec![Value::Float(11.2), Value::Float(13.3)]) + ); + assert_token_value!( + "2 * [10, 5]", + Value::Array(vec![Value::Integer(20), Value::Integer(10)]) + ); assert_token_value!("[false, 0, true] == true", Value::Boolean(true)); } -} \ No newline at end of file +} diff --git a/src/value.rs b/src/value.rs index b962649..1fe599e 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use std::collections::HashMap; use std::cmp::Ordering; +use std::collections::HashMap; const MAX_FLOAT_PRECISION: i32 = 8; @@ -18,7 +18,7 @@ pub type ObjectType = HashMap; /// Represents a single value resulting from a calculation /// Can take the form of an integer, float, boolean or string -/// +/// /// Some types are interchangeable: /// ```rust /// use lavendeux_parser::Value; @@ -28,20 +28,20 @@ pub type ObjectType = HashMap; #[derive(Debug)] pub enum Value { /// The lack of a value - None, + None, /// An unresolved identifier Identifier(String), - + /// A boolean value - all types can be expressed as booleans - Boolean(bool), - + Boolean(bool), + /// An integer value - floats can also be expressed as integers - Integer(IntegerType), - + Integer(IntegerType), + /// A floating point value - integers can also be expressed as floats - Float(FloatType), - + Float(FloatType), + /// A string value - all types can be expressed as strings String(String), @@ -54,21 +54,22 @@ pub enum Value { impl<'de> Deserialize<'de> for Value { fn deserialize(deserializer: D) -> Result - where D: Deserializer<'de>, { - + where + D: Deserializer<'de>, + { #[derive(Deserialize)] enum IntermediateValue { /// The lack of a value - None, + None, Identifier(String), - Boolean(bool), - Integer(IntegerType), - Float(FloatType), + Boolean(bool), + Integer(IntegerType), + Float(FloatType), String(String), Array(ArrayType), Object(Vec<(Value, Value)>), } - + let _value = IntermediateValue::deserialize(deserializer)?; match _value { IntermediateValue::None => Ok(Value::None), @@ -88,10 +89,14 @@ impl<'de> Deserialize<'de> for Value { impl Serialize for Value { fn serialize(&self, serializer: S) -> Result - where S: Serializer, { + where + S: Serializer, + { match self { Value::None => serializer.serialize_newtype_variant("Value", 0, "None", &()), - Value::Identifier(id) => serializer.serialize_newtype_variant("Value", 1, "Identifier", id), + Value::Identifier(id) => { + serializer.serialize_newtype_variant("Value", 1, "Identifier", id) + } Value::Boolean(b) => serializer.serialize_newtype_variant("Value", 2, "Boolean", b), Value::Integer(i) => serializer.serialize_newtype_variant("Value", 3, "Integer", i), Value::Float(f) => serializer.serialize_newtype_variant("Value", 4, "Float", f), @@ -135,46 +140,74 @@ impl Value { /// Return the value as a string pub fn as_string(&self) -> String { match self { - Value::Boolean(v) => (if *v {"true"} else {"false"}).to_string(), - Value::Integer(n) => {format!("{}", *n)}, + Value::Boolean(v) => (if *v { "true" } else { "false" }).to_string(), + Value::Integer(n) => { + format!("{}", *n) + } Value::Float(n) => { let multiplier = f64::powi(10.0, MAX_FLOAT_PRECISION); let mut v = (*n * multiplier).round() / multiplier; - if v == -0.0 { v = 0.0; } + if v == -0.0 { + v = 0.0; + } let mut f = format!("{:}", v); if !f.contains('.') { f += ".0"; } - + f - }, + } Value::String(s) => s.to_string(), - Value::Array(v) => format!("[{}]", v.iter().map(|e| e.as_string()).collect::>().join(", ")), - Value::Object(v) => format!("{{{}}}", v.keys() - .map(|k| format!("{}:{}", - if k.is_string() {format!("\"{}\"", k.as_string() - .replace('\'', "\\'") - .replace('\"', "\\\"") - .replace('\n', "\\n") - .replace('\r', "\\r") - .replace('\t', "\\t") - )} else {k.to_string()}, - if v.get(k).unwrap().is_string() {format!("\"{}\"", v.get(k).unwrap().as_string() - .replace('\'', "\\'") - .replace('\"', "\\\"") - .replace('\n', "\\n") - .replace('\r', "\\r") - .replace('\t', "\\t") - )} else {v.get(k).unwrap().to_string()})) - .collect::>() - .join(", ") + Value::Array(v) => format!( + "[{}]", + v.iter() + .map(|e| e.as_string()) + .collect::>() + .join(", ") + ), + Value::Object(v) => format!( + "{{{}}}", + v.keys() + .map(|k| format!( + "{}:{}", + if k.is_string() { + format!( + "\"{}\"", + k.as_string() + .replace('\'', "\\'") + .replace('\"', "\\\"") + .replace('\n', "\\n") + .replace('\r', "\\r") + .replace('\t', "\\t") + ) + } else { + k.to_string() + }, + if v.get(k).unwrap().is_string() { + format!( + "\"{}\"", + v.get(k) + .unwrap() + .as_string() + .replace('\'', "\\'") + .replace('\"', "\\\"") + .replace('\n', "\\n") + .replace('\r', "\\r") + .replace('\t', "\\t") + ) + } else { + v.get(k).unwrap().to_string() + } + )) + .collect::>() + .join(", ") ), Value::Identifier(s) => s.to_string(), Value::None => "".to_string(), } } - + /// Return the value as a boolean pub fn as_bool(&self) -> bool { match self { @@ -184,11 +217,11 @@ impl Value { Value::Integer(n) => *n != 0, Value::Float(n) => *n != 0.0, Value::String(s) => !s.is_empty(), - Value::Array(v) => v.iter().any(|e|e.as_bool()), - Value::Object(v) => v.values().any(|e|e.as_bool()) + Value::Array(v) => v.iter().any(|e| e.as_bool()), + Value::Object(v) => v.values().any(|e| e.as_bool()), } } - + /// Return the value as an integer, if possible pub fn as_int(&self) -> Option { match self { @@ -202,7 +235,7 @@ impl Value { Value::Object(_) => None, } } - + /// Return the value as a float, if possible pub fn as_float(&self) -> Option { match self { @@ -216,7 +249,7 @@ impl Value { Value::Object(_) => None, } } - + /// Return the value as an array pub fn as_array(&self) -> ArrayType { match self { @@ -230,12 +263,17 @@ impl Value { Value::Object(v) => v.values().cloned().collect(), } } - + /// Return the value as an object pub fn as_object(&self) -> ObjectType { match self { Value::Object(v) => v.clone(), - _ => self.as_array().iter().enumerate().map(|(i, v)| (Value::Integer(i as IntegerType), v.clone())).collect() + _ => self + .as_array() + .iter() + .enumerate() + .map(|(i, v)| (Value::Integer(i as IntegerType), v.clone())) + .collect(), } } @@ -321,17 +359,21 @@ impl PartialOrd for Value { // For objects, compare sorted values (Value::Object(obj1), _) => { - let mut v1: Vec<_> = obj1.values().collect(); v1.sort(); + let mut v1: Vec<_> = obj1.values().collect(); + v1.sort(); let obj2 = other.as_object(); - let mut v2: Vec<_> = obj2.values().collect(); v2.sort(); + let mut v2: Vec<_> = obj2.values().collect(); + v2.sort(); v1.partial_cmp(&v2) - }, + } (_, Value::Object(obj2)) => { let obj1 = self.as_object(); - let mut v1: Vec<_> = obj1.values().collect(); v1.sort(); - let mut v2: Vec<_> = obj2.values().collect(); v2.sort(); + let mut v1: Vec<_> = obj1.values().collect(); + v1.sort(); + let mut v2: Vec<_> = obj2.values().collect(); + v2.sort(); v1.partial_cmp(&v2) - }, + } // Array comparisons (Value::Array(a1), _) => a1.partial_cmp(&other.as_array()), @@ -346,7 +388,9 @@ impl PartialOrd for Value { // String comparisons, If one is a string, both are strings (Value::String(s1), _) => s1.partial_cmp(&other.as_string()), (_, Value::String(s2)) => self.as_string().partial_cmp(s2), - (Value::Identifier(_), Value::Identifier(_)) => self.as_string().partial_cmp(&other.as_string()), + (Value::Identifier(_), Value::Identifier(_)) => { + self.as_string().partial_cmp(&other.as_string()) + } // Treat identifiers and none as false (Value::Identifier(_), _) => Some(Ordering::Less), @@ -358,7 +402,6 @@ impl PartialOrd for Value { } } - impl PartialEq for Value { fn eq(&self, other: &bool) -> bool { self.as_bool() == *other @@ -399,8 +442,12 @@ impl PartialEq<&str> for Value { impl PartialEq for Value { fn eq(&self, other: &ArrayType) -> bool { - self.as_array().len() == other.len() && - self.as_array().iter().zip(other.iter()).all(|(a,b)| a == b) + self.as_array().len() == other.len() + && self + .as_array() + .iter() + .zip(other.iter()) + .all(|(a, b)| a == b) } } @@ -456,8 +503,8 @@ impl From<&str> for Value { #[cfg(test)] mod test_atomic_value { - use std::hash::{Hash, Hasher}; use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; use super::*; @@ -469,14 +516,14 @@ mod test_atomic_value { assert_eq!("test", Value::String("test".to_string()).as_string()); assert_eq!("", Value::None.as_string()); } - + #[test] fn test_as_bool() { assert_eq!(true, Value::Float(5.0).as_bool()); assert_eq!(true, Value::Integer(5).as_bool()); assert_eq!(true, Value::String("5.0".to_string()).as_bool()); } - + #[test] fn test_as_int() { assert_eq!(true, Value::Float(5.0).as_int().is_some()); @@ -487,7 +534,7 @@ mod test_atomic_value { assert_eq!(false, Value::String("".to_string()).as_int().is_some()); } - + #[test] fn test_as_float() { assert_eq!(true, Value::Float(5.0).as_float().is_some()); @@ -498,13 +545,18 @@ mod test_atomic_value { assert_eq!(false, Value::String("".to_string()).as_float().is_some()); } - + #[test] fn test_as_array() { assert_eq!(1, Value::Float(5.0).as_array().len()); - assert_eq!(2, Value::Array(vec![Value::Integer(5), Value::Integer(5)]).as_array().len()); + assert_eq!( + 2, + Value::Array(vec![Value::Integer(5), Value::Integer(5)]) + .as_array() + .len() + ); } - + #[test] fn test_hash() { let mut hasher = DefaultHasher::new(); @@ -527,7 +579,7 @@ mod test_atomic_value { assert_eq!(false, hint2 == hint); assert_eq!(true, hint2 == hint2b); } - + #[test] fn test_object() { let object = Value::Object(HashMap::from([ @@ -536,35 +588,47 @@ mod test_atomic_value { (Value::Integer(2), Value::Integer(3)), ])); - assert_eq!(Value::Integer(2), *object.as_object().get(&Value::Integer(1)).unwrap()); - assert_eq!(Value::Integer(1), *object.as_object().get(&Value::String("1".to_string())).unwrap()); - assert_eq!(Value::Integer(3), *object.as_object().get(&Value::Integer(2)).unwrap()); + assert_eq!( + Value::Integer(2), + *object.as_object().get(&Value::Integer(1)).unwrap() + ); + assert_eq!( + Value::Integer(1), + *object + .as_object() + .get(&Value::String("1".to_string())) + .unwrap() + ); + assert_eq!( + Value::Integer(3), + *object.as_object().get(&Value::Integer(2)).unwrap() + ); } - + #[test] fn test_is_float() { assert_eq!(true, Value::Float(5.0).is_float()); assert_eq!(false, Value::Integer(5).is_float()); } - + #[test] fn test_is_string() { assert_eq!(true, Value::String("5.0".to_string()).is_string()); assert_eq!(false, Value::Integer(5).is_string()); } - + #[test] fn test_is_array() { assert_eq!(true, Value::Array(vec![Value::Integer(5)]).is_array()); assert_eq!(false, Value::Integer(5).is_array()); } - + #[test] fn test_is_identifier() { assert_eq!(false, Value::Array(vec![Value::Integer(5)]).is_identifier()); assert_eq!(false, Value::Integer(5).is_array()); } - + #[test] fn test_eq() { assert_eq!(false, Value::Float(5.0) == Value::Float(5.1)); @@ -572,8 +636,14 @@ mod test_atomic_value { assert_eq!(true, Value::Integer(5) == Value::Integer(5)); assert_eq!(false, Value::Integer(6) == Value::Integer(5)); assert_eq!(true, Value::None == Value::None); - assert_eq!(true, Value::String("test".to_string()) == Value::String("test".to_string())); - assert_eq!(false, Value::String("test".to_string()) == Value::String("test2".to_string())); + assert_eq!( + true, + Value::String("test".to_string()) == Value::String("test".to_string()) + ); + assert_eq!( + false, + Value::String("test".to_string()) == Value::String("test2".to_string()) + ); } #[test] @@ -627,11 +697,11 @@ mod test_atomic_value { assert!(Value::from(false) == Value::from(vec![])); assert!(Value::from(vec![]) == Value::from(false)); // - assert!(Value::from(false) != Value::from(vec![ Value::from(1) ])); - assert!(Value::from(vec![ Value::from(1) ]) != Value::from(false)); + assert!(Value::from(false) != Value::from(vec![Value::from(1)])); + assert!(Value::from(vec![Value::from(1)]) != Value::from(false)); // - assert!(Value::from(false) < Value::from(vec![ Value::from(1) ])); - assert!(Value::from(vec![ Value::from(1) ]) > Value::from(false)); + assert!(Value::from(false) < Value::from(vec![Value::from(1)])); + assert!(Value::from(vec![Value::from(1)]) > Value::from(false)); // assert!(Value::from(true) > Value::from(vec![])); assert!(Value::from(vec![]) < Value::from(true)); @@ -640,11 +710,11 @@ mod test_atomic_value { assert!(Value::from(false) == Value::from(Value::from(vec![]).as_object())); assert!(Value::from(vec![]) == Value::from(false)); // - assert!(Value::from(false) != Value::from(Value::from(vec![ Value::from(1) ]).as_object())); - assert!(Value::from(Value::from(vec![ Value::from(1) ]).as_object()) != Value::from(false)); + assert!(Value::from(false) != Value::from(Value::from(vec![Value::from(1)]).as_object())); + assert!(Value::from(Value::from(vec![Value::from(1)]).as_object()) != Value::from(false)); // - assert!(Value::from(false) < Value::from(Value::from(vec![ Value::from(1) ]).as_object())); - assert!(Value::from(Value::from(vec![ Value::from(1) ]).as_object()) > Value::from(false)); + assert!(Value::from(false) < Value::from(Value::from(vec![Value::from(1)]).as_object())); + assert!(Value::from(Value::from(vec![Value::from(1)]).as_object()) > Value::from(false)); // assert!(Value::from(true) > Value::from(Value::from(vec![]).as_object())); assert!(Value::from(vec![]) < Value::from(true)); @@ -683,7 +753,7 @@ mod test_atomic_value { assert!(Value::from(0) < Value::from("1")); // Integer - Array - assert!(Value::from(1) == Value::from(vec![ Value::from(1) ])); + assert!(Value::from(1) == Value::from(vec![Value::from(1)])); // assert!(Value::from(1) != Value::from(vec![])); assert!(Value::from(vec![]) != Value::from(1)); @@ -692,10 +762,10 @@ mod test_atomic_value { assert!(Value::from(vec![]) < Value::from(1)); // Integer - Object - assert!(Value::from(1) == Value::from(Value::from(vec![ Value::from(1) ]).as_object())); + assert!(Value::from(1) == Value::from(Value::from(vec![Value::from(1)]).as_object())); // - assert!(Value::from(1) != Value::from(Value::from(vec![ ]).as_object())); - assert!(Value::from(Value::from(vec![ ]).as_object()) != Value::from(1)); + assert!(Value::from(1) != Value::from(Value::from(vec![]).as_object())); + assert!(Value::from(Value::from(vec![]).as_object()) != Value::from(1)); // assert!(Value::from(1) > Value::from(Value::from(vec![]).as_object())); assert!(Value::from(Value::from(vec![]).as_object()) < Value::from(1)); @@ -724,8 +794,8 @@ mod test_atomic_value { assert!(Value::from("0.0") < Value::from(1.0)); // Float - Array - assert!(Value::from(1.0) == Value::from(vec![ Value::from(1.0) ])); - assert!(Value::from(vec![ Value::from(1.0) ]) == Value::from(1.0)); + assert!(Value::from(1.0) == Value::from(vec![Value::from(1.0)])); + assert!(Value::from(vec![Value::from(1.0)]) == Value::from(1.0)); // assert!(Value::from(1.0) != Value::from(vec![])); assert!(Value::from(vec![]) != Value::from(1.0)); @@ -734,11 +804,11 @@ mod test_atomic_value { assert!(Value::from(vec![]) < Value::from(1.0)); // Float - Object - assert!(Value::from(1.0) == Value::from(Value::from(vec![ Value::from(1.0) ]).as_object())); - assert!(Value::from(Value::from(vec![ Value::from(1.0) ]).as_object()) == Value::from(1.0)); + assert!(Value::from(1.0) == Value::from(Value::from(vec![Value::from(1.0)]).as_object())); + assert!(Value::from(Value::from(vec![Value::from(1.0)]).as_object()) == Value::from(1.0)); // - assert!(Value::from(1.0) != Value::from(Value::from(vec![ ]).as_object())); - assert!(Value::from(Value::from(vec![ ]).as_object()) != Value::from(1.0)); + assert!(Value::from(1.0) != Value::from(Value::from(vec![]).as_object())); + assert!(Value::from(Value::from(vec![]).as_object()) != Value::from(1.0)); // assert!(Value::from(1.0) > Value::from(Value::from(vec![]).as_object())); assert!(Value::from(Value::from(vec![]).as_object()) < Value::from(1.0)); @@ -756,8 +826,8 @@ mod test_atomic_value { assert!(Value::from("") < Value::from("test")); // String - Array - assert!(Value::from("1") == Value::from(vec![ Value::from(1) ])); - assert!(Value::from(vec![ Value::from(1) ]) == Value::from("1")); + assert!(Value::from("1") == Value::from(vec![Value::from(1)])); + assert!(Value::from(vec![Value::from(1)]) == Value::from("1")); // assert!(Value::from("test") != Value::from(vec![])); assert!(Value::from(vec![]) != Value::from("test")); @@ -766,11 +836,11 @@ mod test_atomic_value { assert!(Value::from(vec![]) < Value::from("test")); // String - Object - assert!(Value::from("1") == Value::from(Value::from(vec![ Value::from(1) ]).as_object())); - assert!(Value::from(Value::from(vec![ Value::from(1) ]).as_object()) == Value::from("1")); + assert!(Value::from("1") == Value::from(Value::from(vec![Value::from(1)]).as_object())); + assert!(Value::from(Value::from(vec![Value::from(1)]).as_object()) == Value::from("1")); // - assert!(Value::from("test") != Value::from(Value::from(vec![ ]).as_object())); - assert!(Value::from(Value::from(vec![ ]).as_object()) != Value::from("test")); + assert!(Value::from("test") != Value::from(Value::from(vec![]).as_object())); + assert!(Value::from(Value::from(vec![]).as_object()) != Value::from("test")); // assert!(Value::from("test") > Value::from(Value::from(vec![]).as_object())); assert!(Value::from(Value::from(vec![]).as_object()) < Value::from("test")); @@ -779,34 +849,52 @@ mod test_atomic_value { #[test] fn test_ord_array() { // Array - Array - assert!(Value::from(vec![ Value::from(1) ]) == Value::from(vec![ Value::from(1) ])); + assert!(Value::from(vec![Value::from(1)]) == Value::from(vec![Value::from(1)])); // - assert!(Value::from(vec![ Value::from(1) ]) != Value::from(vec![])); - assert!(Value::from(vec![]) != Value::from(vec![ Value::from(1) ]) ); + assert!(Value::from(vec![Value::from(1)]) != Value::from(vec![])); + assert!(Value::from(vec![]) != Value::from(vec![Value::from(1)])); // - assert!(Value::from(vec![ Value::from(1) ]) > Value::from(vec![])); - assert!(Value::from(vec![]) < Value::from(vec![ Value::from(1) ]) ); + assert!(Value::from(vec![Value::from(1)]) > Value::from(vec![])); + assert!(Value::from(vec![]) < Value::from(vec![Value::from(1)])); // Array - Object - assert!(Value::from(vec![ Value::from(1) ]) == Value::from(Value::from(vec![ Value::from(1) ]).as_object())); + assert!( + Value::from(vec![Value::from(1)]) + == Value::from(Value::from(vec![Value::from(1)]).as_object()) + ); assert!(Value::from(Value::from(vec![]).as_object()) == Value::from(vec![])); // - assert!(Value::from(vec![ Value::from(1) ]) != Value::from(Value::from(vec![ ]).as_object())); - assert!(Value::from(Value::from(vec![ ]).as_object()) != Value::from(vec![ Value::from(1) ])); + assert!(Value::from(vec![Value::from(1)]) != Value::from(Value::from(vec![]).as_object())); + assert!(Value::from(Value::from(vec![]).as_object()) != Value::from(vec![Value::from(1)])); // - assert!(Value::from(vec![ Value::from(1) ]) > Value::from(Value::from(vec![]).as_object())); - assert!(Value::from(Value::from(vec![]).as_object()) < Value::from(vec![ Value::from(1) ])); + assert!(Value::from(vec![Value::from(1)]) > Value::from(Value::from(vec![]).as_object())); + assert!(Value::from(Value::from(vec![]).as_object()) < Value::from(vec![Value::from(1)])); } - + #[test] fn test_ord_obj() { // Object - Object - assert!(Value::from(Value::from(vec![ Value::from(1) ]).as_object()) == Value::from(Value::from(vec![ Value::from(1) ]).as_object())); - // - assert!(Value::from(Value::from(vec![ Value::from(1) ]).as_object()) != Value::from(Value::from(vec![]).as_object())); - assert!(Value::from(Value::from(vec![]).as_object()) != Value::from(Value::from(vec![ Value::from(1) ]).as_object())); - // - assert!(Value::from(Value::from(vec![ Value::from(1) ]).as_object()) > Value::from(Value::from(vec![]).as_object())); - assert!(Value::from(Value::from(vec![]).as_object()) < Value::from(Value::from(vec![ Value::from(1) ]).as_object())); + assert!( + Value::from(Value::from(vec![Value::from(1)]).as_object()) + == Value::from(Value::from(vec![Value::from(1)]).as_object()) + ); + // + assert!( + Value::from(Value::from(vec![Value::from(1)]).as_object()) + != Value::from(Value::from(vec![]).as_object()) + ); + assert!( + Value::from(Value::from(vec![]).as_object()) + != Value::from(Value::from(vec![Value::from(1)]).as_object()) + ); + // + assert!( + Value::from(Value::from(vec![Value::from(1)]).as_object()) + > Value::from(Value::from(vec![]).as_object()) + ); + assert!( + Value::from(Value::from(vec![]).as_object()) + < Value::from(Value::from(vec![Value::from(1)]).as_object()) + ); } -} \ No newline at end of file +}