diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index de303fa..5eb1784 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,7 @@ jobs: run: cargo fmt --check --all - name: Check Clippy - run: cargo clippy --workspace --all-targets --features wasm,lua54,bench + run: cargo clippy --workspace --all-targets --features wasm,lua54 build: runs-on: ${{ matrix.os }} @@ -76,7 +76,7 @@ jobs: run: cargo build --features wasm - name: Wasm-Pack Test - run: wasm-pack test --node + run: wasm-pack test --node --features wasm build-lua: runs-on: ubuntu-latest diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..f6c6716 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "test-suite"] + path = test-suite + url = https://github.com/corn-config/test-suite.git diff --git a/Cargo.lock b/Cargo.lock index e2a785d..fcc7e98 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,24 +4,24 @@ version = 4 [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] [[package]] -name = "anes" -version = "0.1.6" +name = "allocator-api2" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "anstream" -version = "0.6.18" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", "anstyle-parse", @@ -34,50 +34,91 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.7" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", - "once_cell", - "windows-sys", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "ascii-canvas" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1e3e699d84ab1b0911a1010c5c106aa34ae89aeac103be5ce0c3859db1e891" +dependencies = [ + "term", +] + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "beef" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" + +[[package]] +name = "bit-set" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "block-buffer" @@ -90,9 +131,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.11.3" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" dependencies = [ "memchr", "serde", @@ -100,9 +141,15 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.17.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + +[[package]] +name = "camino" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" [[package]] name = "cast" @@ -112,57 +159,25 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.16" +version = "1.2.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" +checksum = "9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c" dependencies = [ + "find-msvc-tools", "shlex", ] [[package]] name = "cfg-if" -version = "0.1.10" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "ciborium" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" -dependencies = [ - "ciborium-io", - "ciborium-ll", - "serde", -] - -[[package]] -name = "ciborium-io" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" - -[[package]] -name = "ciborium-ll" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" -dependencies = [ - "ciborium-io", - "half", -] +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "clap" -version = "4.5.31" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767" +checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" dependencies = [ "clap_builder", "clap_derive", @@ -170,9 +185,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.31" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863" +checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" dependencies = [ "anstream", "anstyle", @@ -182,9 +197,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.28" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" dependencies = [ "heck", "proc-macro2", @@ -194,15 +209,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "colored" @@ -210,7 +225,7 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" dependencies = [ - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -219,7 +234,7 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "wasm-bindgen", ] @@ -246,113 +261,109 @@ dependencies = [ ] [[package]] -name = "criterion" -version = "0.5.1" +name = "crypto-common" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ - "anes", - "cast", - "ciborium", - "clap", - "criterion-plot", - "is-terminal", - "itertools 0.10.5", - "num-traits", - "once_cell", - "oorandom", - "plotters", - "rayon", - "regex", - "serde", - "serde_derive", - "serde_json", - "tinytemplate", - "walkdir", + "generic-array", + "typenum", ] [[package]] -name = "criterion-plot" -version = "0.5.0" +name = "datatest-stable" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +checksum = "a867d7322eb69cf3a68a5426387a25b45cb3b9c5ee41023ee6cea92e2afadd82" dependencies = [ - "cast", - "itertools 0.10.5", + "camino", + "fancy-regex", + "libtest-mimic", + "walkdir", ] [[package]] -name = "crossbeam-deque" -version = "0.8.6" +name = "digest" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", + "block-buffer", + "crypto-common", ] [[package]] -name = "crossbeam-epoch" -version = "0.9.18" +name = "either" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] -name = "crossbeam-utils" -version = "0.8.21" +name = "ena" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" +dependencies = [ + "log", +] [[package]] -name = "crunchy" -version = "0.2.3" +name = "equivalent" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] -name = "crypto-common" -version = "0.1.6" +name = "erased-serde" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "89e8918065695684b2b0702da20382d5ae6065cf3327bc2d6436bd49a71ce9f3" dependencies = [ - "generic-array", - "typenum", + "serde", + "serde_core", + "typeid", ] [[package]] -name = "digest" -version = "0.10.7" +name = "escape8259" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +checksum = "5692dd7b5a1978a5aeb0ce83b7655c58ca8efdcb79d21036ea249da95afec2c6" + +[[package]] +name = "fancy-regex" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298" dependencies = [ - "block-buffer", - "crypto-common", + "bit-set", + "regex-automata", + "regex-syntax", ] [[package]] -name = "either" -version = "1.14.0" +name = "find-msvc-tools" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7914353092ddf589ad78f25c5c1c21b7f80b0ff8621e7c814c3485b5306da9d" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" [[package]] -name = "equivalent" -version = "1.0.2" +name = "fixedbitset" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] -name = "erased-serde" -version = "0.4.6" +name = "fnv" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7" -dependencies = [ - "serde", - "typeid", -] +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" [[package]] name = "generic-array" @@ -365,158 +376,210 @@ dependencies = [ ] [[package]] -name = "half" -version = "2.4.1" +name = "hashbrown" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ - "cfg-if 1.0.0", - "crunchy", + "allocator-api2", + "equivalent", + "foldhash", ] -[[package]] -name = "hashbrown" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" - [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" - [[package]] name = "indexmap" -version = "2.7.1" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", "hashbrown", "serde", + "serde_core", ] [[package]] -name = "is-terminal" -version = "0.4.15" +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" dependencies = [ - "hermit-abi", - "libc", - "windows-sys", + "either", ] [[package]] -name = "is_terminal_polyfill" -version = "1.70.1" +name = "itoa" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "7ee5b5339afb4c41626dde77b7a611bd4f2c202b897852b4bcf5d03eddc61010" [[package]] -name = "itertools" -version = "0.10.5" +name = "js-sys" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" dependencies = [ - "either", + "once_cell", + "wasm-bindgen", ] [[package]] -name = "itertools" -version = "0.13.0" +name = "keccak" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" dependencies = [ - "either", + "cpufeatures", ] [[package]] -name = "itoa" -version = "1.0.15" +name = "lalrpop" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "ba4ebbd48ce411c1d10fb35185f5a51a7bfa3d8b24b4e330d30c9e3a34129501" +dependencies = [ + "ascii-canvas", + "bit-set", + "ena", + "itertools", + "lalrpop-util", + "petgraph", + "pico-args", + "regex", + "regex-syntax", + "sha3", + "string_cache", + "term", + "unicode-xid", + "walkdir", +] [[package]] -name = "js-sys" -version = "0.3.77" +name = "lalrpop-util" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "b5baa5e9ff84f1aefd264e6869907646538a52147a755d494517a8007fb48733" dependencies = [ - "once_cell", - "wasm-bindgen", + "regex-automata", + "rustversion", ] [[package]] name = "libc" -version = "0.2.170" +version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "libcorn" version = "0.10.0" dependencies = [ - "cfg-if 1.0.0", "console_error_panic_hook", - "criterion", + "datatest-stable", + "hashbrown", "indexmap", + "itoa", + "lalrpop", + "lalrpop-util", + "logos", "mlua", - "paste", - "pest", - "pest_derive", - "serde", "serde-wasm-bindgen", - "serde_bytes", + "serde_core", "serde_json", - "serde_norway", - "thiserror", - "toml_edit", "wasm-bindgen", "wasm-bindgen-test", - "wee_alloc", +] + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "libtest-mimic" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5297962ef19edda4ce33aaa484386e0a5b3d7f2f4e037cbeee00503ef6b29d33" +dependencies = [ + "anstream", + "anstyle", + "clap", + "escape8259", ] [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.26" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] -name = "memchr" -version = "2.7.4" +name = "logos" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "a790d11254054e5dc83902dba85d253ff06ceb0cfafb12be8773435cb9dfb4f4" +dependencies = [ + "logos-derive", +] [[package]] -name = "memory_units" -version = "0.4.0" +name = "logos-codegen" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60337c43a38313b58871f8d5d76872b8e17aa9d51fad494b5e76092c0ce05f5" +dependencies = [ + "beef", + "fnv", + "proc-macro2", + "quote", + "regex-automata", + "regex-syntax", + "rustc_version", + "syn", +] + +[[package]] +name = "logos-derive" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d151b2ae667f69e10b8738f5cac0c746faa22b2e15ea7e83b55476afec3767dc" +dependencies = [ + "logos-codegen", +] + +[[package]] +name = "memchr" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "minicov" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f27fe9f1cc3c22e1687f9446c2083c4c5fc7f0bcf1c7a86bdbded14985895b4b" +checksum = "4869b6a491569605d66d3952bcdf03df789e5b536e5f0cf7758a7f08a55ae24d" dependencies = [ "cc", "walkdir", @@ -524,40 +587,42 @@ dependencies = [ [[package]] name = "mlua" -version = "0.10.3" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3f763c1041eff92ffb5d7169968a327e1ed2ebfe425dac0ee5a35f29082534b" +checksum = "935ac67539907efcd7198137eb7358e052555f77fe1b2916600a2249351f2b33" dependencies = [ "bstr", "either", "erased-serde", + "libc", "mlua-sys", "mlua_derive", "num-traits", "parking_lot", "rustc-hash", + "rustversion", "serde", "serde-value", ] [[package]] name = "mlua-sys" -version = "0.6.7" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1901c1a635a22fe9250ffcc4fcc937c16b47c2e9e71adba8784af8bca1f69594" +checksum = "8c968af21bf6b19fc9ca8e7b85ee16f86e4c9e3d0591de101a5608086bda0ad8" dependencies = [ "cc", - "cfg-if 1.0.0", + "cfg-if", "pkg-config", ] [[package]] name = "mlua_derive" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "870d71c172fcf491c6b5fb4c04160619a2ee3e5a42a1402269c66bcbf1dd4deb" +checksum = "465bddde514c4eb3b50b543250e97c1d4b284fa3ef7dc0ba2992c77545dbceb2" dependencies = [ - "itertools 0.13.0", + "itertools", "once_cell", "proc-macro-error2", "proc-macro2", @@ -566,6 +631,21 @@ dependencies = [ "syn", ] +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -573,19 +653,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] name = "once_cell" -version = "1.20.3" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "oorandom" -version = "11.1.4" +version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "ordered-float" @@ -598,9 +685,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -608,67 +695,41 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-link", ] [[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - -[[package]] -name = "pest" -version = "2.7.15" +name = "petgraph" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" dependencies = [ - "memchr", - "thiserror", - "ucd-trie", -] - -[[package]] -name = "pest_derive" -version = "2.7.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "816518421cfc6887a0d62bf441b6ffb4536fcc926395a69e1a85852d4363f57e" -dependencies = [ - "pest", - "pest_generator", + "fixedbitset", + "indexmap", ] [[package]] -name = "pest_generator" -version = "2.7.15" +name = "phf_shared" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d1396fd3a870fc7838768d171b4616d5c91f6cc25e377b673d714567d99377b" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn", + "siphasher", ] [[package]] -name = "pest_meta" -version = "2.7.15" +name = "pico-args" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e58089ea25d717bfd31fb534e4f3afcc2cc569c70de3e239778991ea3b7dea" -dependencies = [ - "once_cell", - "pest", - "sha2", -] +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" [[package]] name = "pkg-config" @@ -677,32 +738,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] -name = "plotters" -version = "0.3.7" +name = "precomputed-hash" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" -dependencies = [ - "num-traits", - "plotters-backend", - "plotters-svg", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "plotters-backend" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" - -[[package]] -name = "plotters-svg" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" -dependencies = [ - "plotters-backend", -] +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "proc-macro-error-attr2" @@ -728,56 +767,36 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.39" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] -[[package]] -name = "rayon" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - [[package]] name = "redox_syscall" -version = "0.5.10" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags", ] [[package]] name = "regex" -version = "1.11.1" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", @@ -787,9 +806,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", @@ -798,9 +817,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "rustc-hash" @@ -808,17 +827,26 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustversion" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "62049b2877bf12821e8f9ad256ee38fdc31db7387ec2d3b3f403024de2034aea" [[package]] name = "same-file" @@ -835,12 +863,19 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + [[package]] name = "serde" -version = "1.0.218" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ + "serde_core", "serde_derive", ] @@ -866,19 +901,19 @@ dependencies = [ ] [[package]] -name = "serde_bytes" -version = "0.11.16" +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "364fec0df39c49a083c9a8a18a23a6bcfd9af130fe9fe321d18520a0d113e09e" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ - "serde", + "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.218" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -887,14 +922,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.140" +version = "1.0.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "6af14725505314343e673e9ecb7cd7e8a36aa9791eb936235a3567cc31447ae4" dependencies = [ "itoa", "memchr", - "ryu", "serde", + "serde_core", + "zmij", ] [[package]] @@ -912,22 +948,21 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.8" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" dependencies = [ - "serde", + "serde_core", ] [[package]] -name = "sha2" +name = "sha3" version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" dependencies = [ - "cfg-if 1.0.0", - "cpufeatures", "digest", + "keccak", ] [[package]] @@ -937,80 +972,94 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] -name = "smallvec" -version = "1.14.0" +name = "siphasher" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] -name = "strsim" -version = "0.11.1" +name = "smallvec" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] -name = "syn" -version = "2.0.99" +name = "string_cache" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02e925281e18ffd9d640e234264753c43edc62d64b2d4cf898f1bc5e75f3fc2" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", ] [[package]] -name = "thiserror" -version = "2.0.12" +name = "strsim" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" -dependencies = [ - "thiserror-impl", -] +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] -name = "thiserror-impl" -version = "2.0.12" +name = "syn" +version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", - "syn", + "unicode-ident", ] [[package]] -name = "tinytemplate" +name = "term" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +checksum = "d8c27177b12a6399ffc08b98f76f7c9a1f4fe9fc967c784c5a071fa8d93cf7e1" dependencies = [ - "serde", - "serde_json", + "windows-sys 0.61.2", ] [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ - "serde", + "serde_core", ] [[package]] name = "toml_edit" -version = "0.22.24" +version = "0.23.10+spec-1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" dependencies = [ "indexmap", - "serde", + "serde_core", "serde_spanned", "toml_datetime", + "toml_parser", + "toml_writer", "winnow", ] +[[package]] +name = "toml_parser" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" + [[package]] name = "typeid" version = "1.0.3" @@ -1019,21 +1068,21 @@ checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" [[package]] name = "typenum" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] -name = "ucd-trie" -version = "0.1.7" +name = "unicode-ident" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] -name = "unicode-ident" -version = "1.0.18" +name = "unicode-xid" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "unsafe-libyaml-norway" @@ -1065,37 +1114,24 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.50" +version = "0.4.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "js-sys", "once_cell", "wasm-bindgen", @@ -1104,9 +1140,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1114,34 +1150,42 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" dependencies = [ "unicode-ident", ] [[package]] name = "wasm-bindgen-test" -version = "0.3.50" +version = "0.3.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66c8d5e33ca3b6d9fa3b4676d774c5778031d27a578c2b007f905acf816152c3" +checksum = "25e90e66d265d3a1efc0e72a54809ab90b9c0c515915c67cdf658689d2c22c6c" dependencies = [ + "async-trait", + "cast", "js-sys", + "libm", "minicov", + "nu-ansi-term", + "num-traits", + "oorandom", + "serde", + "serde_json", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test-macro", @@ -1149,9 +1193,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.50" +version = "0.3.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17d5042cc5fa009658f9a7333ef24291b1291a25b6382dd68862a7f3b969f69b" +checksum = "7150335716dce6028bead2b848e72f47b45e7b9422f64cccdc23bedca89affc1" dependencies = [ "proc-macro2", "quote", @@ -1160,64 +1204,45 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.77" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] -name = "wee_alloc" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" -dependencies = [ - "cfg-if 0.1.10", - "libc", - "memory_units", - "winapi", -] - -[[package]] -name = "winapi" -version = "0.3.9" +name = "winapi-util" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", + "windows-sys 0.61.2", ] [[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" +name = "windows-link" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] -name = "winapi-util" -version = "0.1.9" +name = "windows-sys" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-sys", + "windows-targets", ] -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "windows-sys" -version = "0.59.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-targets", + "windows-link", ] [[package]] @@ -1286,9 +1311,15 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.7.3" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" dependencies = [ "memchr", ] + +[[package]] +name = "zmij" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e404bcd8afdaf006e529269d3e85a743f9480c3cef60034d77860d02964f3ba" diff --git a/Cargo.toml b/Cargo.toml index 2725ac9..37c025b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,27 +4,32 @@ members = ["cli"] [package] name = "libcorn" version = "0.10.0" -edition = "2021" +edition = "2024" license = "MIT" description = "Parsing engine for Corn, a simple and pain-free configuration language." repository = "https://github.com/JakeStanger/corn" categories = ["config", "parsing"] -keywords = ["configuration", "language", "wasm", "pest", "peg"] -authors = ["Jake Stanger "] +keywords = ["configuration", "language", "wasm", "peg"] +authors = [ + "Jake Stanger ", + "Matilde Morrone ", +] homepage = "https://cornlang.dev/" documentation = "https://docs.rs/libcorn" readme = "README.md" [features] +default = ["std"] +std = ["serde_core/std", "indexmap/std", "logos/std"] + # WASM support wasm = [ "dep:wasm-bindgen", "dep:serde-wasm-bindgen", "dep:console_error_panic_hook", - "dep:wee_alloc", ] -# Lua version support +# Lua support lua51 = ["dep:mlua", "mlua/lua51"] lua52 = ["dep:mlua", "mlua/lua52"] lua53 = ["dep:mlua", "mlua/lua53"] @@ -32,56 +37,56 @@ lua54 = ["dep:mlua", "mlua/lua54"] luajit = ["dep:mlua", "mlua/luajit"] luajit52 = ["dep:mlua", "mlua/luajit52"] -# Internal features -bench = ["dep:criterion"] - [lib] name = "corn" crate-type = ["cdylib", "rlib"] -[dependencies] -# Core -pest = "2.7.15" -pest_derive = "2.7.15" -serde = { version = "1.0.218", features = ["derive"] } -indexmap = { version = "2.7.1", features = ["serde"] } +[[test]] +name = "test_suite" +harness = false -# Error handling -thiserror = "2.0.12" +[profile.release] +lto = true +codegen-units = 1 +strip = true -# Utilities -cfg-if = "1.0.0" +[dependencies] +# Core +serde_core = { version = "1.0.228", default-features = false, features = [ + "alloc", +] } +indexmap = { version = "2.12.1", features = [ + "serde", +], default-features = false } +logos = { version = "0.16.0", default-features = false, features = [ + "export_derive", +] } +lalrpop-util = { version = "0.22.2", features = ["lexer"] } +itoa = "1.0.16" +hashbrown = "0.16.1" # WASM support (optional) -wasm-bindgen = { version = "0.2.100", optional = true } +wasm-bindgen = { version = "0.2.106", optional = true } serde-wasm-bindgen = { version = "0.6.5", optional = true } console_error_panic_hook = { version = "0.1.7", optional = true } -wee_alloc = { version = "0.4.5", optional = true } # Lua support (optional) -mlua = { version = "0.10.3", features = [ +mlua = { version = "0.11.5", features = [ "module", "macros", "serialize", ], optional = true } -# Benchmarking (optional) -criterion = { version = "0.5.1", features = ["html_reports"], optional = true } - [dev-dependencies] -paste = "1.0.15" -wasm-bindgen-test = { version = "0.3.50" } -serde_json = "1.0.140" -serde_norway = "0.9.42" -serde_bytes = "0.11.16" -toml_edit = { version = "0.22.24", features = ["serde"] } +wasm-bindgen-test = { version = "0.3.56" } +serde_json = "1.0.147" +datatest-stable = "0.3.3" -[profile.release] -lto = true -codegen-units = 1 -strip = true +[build-dependencies] +lalrpop = "0.22.2" -[[bench]] -name = "serde" -harness = false -required-features = ["bench"] +[package.metadata.wasm-pack.profile.profiling] +wasm-opt = ['-O', '--enable-bulk-memory'] + +[package.metadata.wasm-pack.profile.release] +wasm-opt = ['-O', '--enable-bulk-memory'] diff --git a/assets/inputs/array.corn b/assets/inputs/array.corn deleted file mode 100644 index ea95311..0000000 --- a/assets/inputs/array.corn +++ /dev/null @@ -1,3 +0,0 @@ -{ - foo = [ 1 2 3 ] -} diff --git a/assets/inputs/basic.corn b/assets/inputs/basic.corn deleted file mode 100644 index 7524a1b..0000000 --- a/assets/inputs/basic.corn +++ /dev/null @@ -1,3 +0,0 @@ -{ - foo = "bar" -} diff --git a/assets/inputs/basic_empty_let.corn b/assets/inputs/basic_empty_let.corn deleted file mode 100644 index ea902d2..0000000 --- a/assets/inputs/basic_empty_let.corn +++ /dev/null @@ -1,3 +0,0 @@ -let { } in { - foo = "bar" -} diff --git a/assets/inputs/boolean.corn b/assets/inputs/boolean.corn deleted file mode 100644 index c1b7434..0000000 --- a/assets/inputs/boolean.corn +++ /dev/null @@ -1,4 +0,0 @@ -{ - foo = true - bar = false -} diff --git a/assets/inputs/chained.corn b/assets/inputs/chained.corn deleted file mode 100644 index d88ee32..0000000 --- a/assets/inputs/chained.corn +++ /dev/null @@ -1,3 +0,0 @@ -{ - foo.bar = "baz" -} diff --git a/assets/inputs/chained_complex.corn b/assets/inputs/chained_complex.corn deleted file mode 100644 index 6fce998..0000000 --- a/assets/inputs/chained_complex.corn +++ /dev/null @@ -1,8 +0,0 @@ -{ - foo = { - bar.baz = 42 - qux = true - } - - foo.quux = [ "green eggs" "ham" ] -} diff --git a/assets/inputs/char.corn b/assets/inputs/char.corn deleted file mode 100644 index d32b4a6..0000000 --- a/assets/inputs/char.corn +++ /dev/null @@ -1,3 +0,0 @@ -{ - foo = "a" -} diff --git a/assets/inputs/comment.corn b/assets/inputs/comment.corn deleted file mode 100644 index d1bd72f..0000000 --- a/assets/inputs/comment.corn +++ /dev/null @@ -1,4 +0,0 @@ -{ - // single line comment - foo = "bar" // mixed line comment -} diff --git a/assets/inputs/compact.corn b/assets/inputs/compact.corn deleted file mode 100644 index 49185de..0000000 --- a/assets/inputs/compact.corn +++ /dev/null @@ -1,15 +0,0 @@ -{ - one={foo="bar" bar="foo"} - two={foo=1 bar=2} - three={foo=1.0 bar=2.0} - four={foo=true bar=false} - five={foo=null bar=null} - six={foo={} bar={}} - seven={foo=[] bar=[]} - - eight=["foo""bar"] - nine=[truefalse] - ten=[1 2] - eleven=[[][]] - twelve=[{}{}] -} diff --git a/assets/inputs/complex.corn b/assets/inputs/complex.corn deleted file mode 100644 index 2d763d5..0000000 --- a/assets/inputs/complex.corn +++ /dev/null @@ -1,56 +0,0 @@ -let { - // inputs go here - // they should start with a $ and use alphanumeric characters only - $firstName = "John" - $lastName = "Smith" - $age = 32 - $employed = true - $jobName = "Postman" -} -in { - // here is another comment - name.first = $firstName - name.last = $lastName - name.full = "$firstName $lastName" - age = $age - - employment = { - employed = $employed - name = $jobName - } - - placeholder = null - - // invalid = not_valid - // invalid.var = $not_valid - - employment.sinceYear = 2019 - - parents.father.birthday = { - year = 1970 - month = 2 - day = 3 - } - - gender = "M" - - favourites = [ - "blue" - "fish" - "egg" - $jobName - 4.73753 - false - { - hello = "world" - food.favourite = "egg" - food.hated = "beef" - } - ] - - empty1 = {} - empty2 = [] - - negative.int = -34 - negative.float = -34.34 -} diff --git a/assets/inputs/complex_keys.corn b/assets/inputs/complex_keys.corn deleted file mode 100644 index cb74912..0000000 --- a/assets/inputs/complex_keys.corn +++ /dev/null @@ -1,9 +0,0 @@ -{ - with_underscore = 0 - with-dash = 1 - with_🌽 = 2 - !"£$%^&*()_ = 3 - j12345 = 4 - foo.bar-baz = "hello" - apple-pie.crust = "yum" -} diff --git a/assets/inputs/environment_variable.corn b/assets/inputs/environment_variable.corn deleted file mode 100644 index 492ed48..0000000 --- a/assets/inputs/environment_variable.corn +++ /dev/null @@ -1,3 +0,0 @@ -{ - foo = $env_CORN_TEST -} diff --git a/assets/inputs/float.corn b/assets/inputs/float.corn deleted file mode 100644 index 281a020..0000000 --- a/assets/inputs/float.corn +++ /dev/null @@ -1,4 +0,0 @@ -{ - foo = 3.14159 - bar = 1.01e+2 -} diff --git a/assets/inputs/input.corn b/assets/inputs/input.corn deleted file mode 100644 index b017482..0000000 --- a/assets/inputs/input.corn +++ /dev/null @@ -1,18 +0,0 @@ -let { - $firstName = "John" - $lastName = "Smith" - - $birthday = { - day = 1 - month = 1 - year = 1970 - } - -} in { - name = { - first = $firstName - last = $lastName - } - - dob = $birthday -} diff --git a/assets/inputs/input_references_input.corn b/assets/inputs/input_references_input.corn deleted file mode 100644 index f55d88b..0000000 --- a/assets/inputs/input_references_input.corn +++ /dev/null @@ -1,6 +0,0 @@ -let { - $foo = "bar" - $baz = $foo -} in { - foo = $baz -} diff --git a/assets/inputs/integer.corn b/assets/inputs/integer.corn deleted file mode 100644 index d02fb21..0000000 --- a/assets/inputs/integer.corn +++ /dev/null @@ -1,5 +0,0 @@ -{ - foo = 42 - bar = 0xfafafa - baz = 1_000_000 -} diff --git a/assets/inputs/invalid.corn b/assets/inputs/invalid.corn deleted file mode 100644 index cc354da..0000000 --- a/assets/inputs/invalid.corn +++ /dev/null @@ -1 +0,0 @@ -this is not valid corn config diff --git a/assets/inputs/invalid_input.corn b/assets/inputs/invalid_input.corn deleted file mode 100644 index 6160d94..0000000 --- a/assets/inputs/invalid_input.corn +++ /dev/null @@ -1,3 +0,0 @@ -{ - foo = $bar -} diff --git a/assets/inputs/invalid_nesting.corn b/assets/inputs/invalid_nesting.corn deleted file mode 100644 index 8726fef..0000000 --- a/assets/inputs/invalid_nesting.corn +++ /dev/null @@ -1,4 +0,0 @@ -{ - foo = 0 - foo.bar = 1 -} diff --git a/assets/inputs/invalid_spread.corn b/assets/inputs/invalid_spread.corn deleted file mode 100644 index 39b1ab1..0000000 --- a/assets/inputs/invalid_spread.corn +++ /dev/null @@ -1,6 +0,0 @@ -let { - $foo = 23 -} in { - ..$foo - bar = [ ..$foo ] -} diff --git a/assets/inputs/mixed_array.corn b/assets/inputs/mixed_array.corn deleted file mode 100644 index 67276de..0000000 --- a/assets/inputs/mixed_array.corn +++ /dev/null @@ -1,3 +0,0 @@ -{ - foo = [ 1 "two" false ] -} diff --git a/assets/inputs/null.corn b/assets/inputs/null.corn deleted file mode 100644 index 35256da..0000000 --- a/assets/inputs/null.corn +++ /dev/null @@ -1,3 +0,0 @@ -{ - foo = null -} diff --git a/assets/inputs/null_in_array.corn b/assets/inputs/null_in_array.corn deleted file mode 100644 index a2518b7..0000000 --- a/assets/inputs/null_in_array.corn +++ /dev/null @@ -1,3 +0,0 @@ -{ - foo = [ null ] -} \ No newline at end of file diff --git a/assets/inputs/object.corn b/assets/inputs/object.corn deleted file mode 100644 index b932350..0000000 --- a/assets/inputs/object.corn +++ /dev/null @@ -1,5 +0,0 @@ -{ - foo = { - bar = 42 - } -} diff --git a/assets/inputs/object_in_array.corn b/assets/inputs/object_in_array.corn deleted file mode 100644 index dfb5e61..0000000 --- a/assets/inputs/object_in_array.corn +++ /dev/null @@ -1,3 +0,0 @@ -{ - foo = [ { foo = 1 bar = 2 } ] -} diff --git a/assets/inputs/quoted_keys.corn b/assets/inputs/quoted_keys.corn deleted file mode 100644 index da61425..0000000 --- a/assets/inputs/quoted_keys.corn +++ /dev/null @@ -1,7 +0,0 @@ -{ - 'foo.bar' = 42 - 'green.eggs'.and.ham = "hello world" - 'with spaces' = true - 'escaped\'quote' = false - 'escaped=equals' = -3 -} \ No newline at end of file diff --git a/assets/inputs/readme_example.corn b/assets/inputs/readme_example.corn deleted file mode 100644 index 2ace342..0000000 --- a/assets/inputs/readme_example.corn +++ /dev/null @@ -1,28 +0,0 @@ -let { - $entry = "dist/index.js" - $author = { name = "John Smith" email = "mail@example.com" } -} in { - name = "example-package" - version = "1.0.0" - main = $entry - bin.filebrowser = $entry - private = false - - author = $author - author.url = "https://example.com" - - contributors = [ $author ] - - scripts.build = "tsc" - scripts.run = "node dist" - - dependencies = { - dotenv = "^8.2.0" - // put the rest of your deps here... - } - - devDependencies.typescript = "^4.5" - - config.port = 8080 - config.hostname = null -} diff --git a/assets/inputs/spread.corn b/assets/inputs/spread.corn deleted file mode 100644 index 966128c..0000000 --- a/assets/inputs/spread.corn +++ /dev/null @@ -1,10 +0,0 @@ -let { - $foo = { bar = "baz" } - - $nums_low = [ 1 2 ] - $num_high = [ 3 4 ] -} in { - hello = "world" - ..$foo - nums = [ ..$nums_low ..$num_high ] -} diff --git a/assets/inputs/string.corn b/assets/inputs/string.corn deleted file mode 100644 index 640240a..0000000 --- a/assets/inputs/string.corn +++ /dev/null @@ -1,6 +0,0 @@ -{ - foo = "bar" - bar = "\"\\\n\r\t" - baz = "\u0061" - qux = "" -} diff --git a/assets/inputs/string_interpolation.corn b/assets/inputs/string_interpolation.corn deleted file mode 100644 index 465426e..0000000 --- a/assets/inputs/string_interpolation.corn +++ /dev/null @@ -1,7 +0,0 @@ -let { - $greeting = "hello" - $subject = "world" -} in { - foo = "$greeting, $subject" - bar = "\$escaped" -} diff --git a/assets/inputs/string_multiline.corn b/assets/inputs/string_multiline.corn deleted file mode 100644 index c1d25a0..0000000 --- a/assets/inputs/string_multiline.corn +++ /dev/null @@ -1,6 +0,0 @@ -{ - foo = " - hello - world - " -} \ No newline at end of file diff --git a/assets/inputs/value_after_table.corn b/assets/inputs/value_after_table.corn deleted file mode 100644 index d9575ce..0000000 --- a/assets/inputs/value_after_table.corn +++ /dev/null @@ -1,4 +0,0 @@ -{ - foo = {} - qux = true -} diff --git a/assets/inputs/very_compact.corn b/assets/inputs/very_compact.corn deleted file mode 100644 index 3932084..0000000 --- a/assets/inputs/very_compact.corn +++ /dev/null @@ -1 +0,0 @@ -{one={foo="bar" bar="foo"} two={foo=1 bar=2} three={foo=1.0 bar=2.0} four={foo=true bar=false} five={foo=null bar=null} six={foo={} bar={}} seven={foo=[] bar=[]} eight=["foo""bar"] nine=[truefalse] ten=[1 2] eleven=[[][]] twelve=[{}{}]} diff --git a/assets/outputs/json/array.json b/assets/outputs/json/array.json deleted file mode 100644 index 4dcd9c9..0000000 --- a/assets/outputs/json/array.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "foo": [ - 1, - 2, - 3 - ] -} diff --git a/assets/outputs/json/basic.json b/assets/outputs/json/basic.json deleted file mode 100644 index c8c4105..0000000 --- a/assets/outputs/json/basic.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "foo": "bar" -} diff --git a/assets/outputs/json/basic_empty_let.json b/assets/outputs/json/basic_empty_let.json deleted file mode 100644 index c8c4105..0000000 --- a/assets/outputs/json/basic_empty_let.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "foo": "bar" -} diff --git a/assets/outputs/json/boolean.json b/assets/outputs/json/boolean.json deleted file mode 100644 index 5084f85..0000000 --- a/assets/outputs/json/boolean.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "foo": true, - "bar": false -} diff --git a/assets/outputs/json/chained.json b/assets/outputs/json/chained.json deleted file mode 100644 index faa9156..0000000 --- a/assets/outputs/json/chained.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "foo": { - "bar": "baz" - } -} diff --git a/assets/outputs/json/chained_complex.json b/assets/outputs/json/chained_complex.json deleted file mode 100644 index c87f092..0000000 --- a/assets/outputs/json/chained_complex.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "foo": { - "bar": { - "baz": 42 - }, - "qux": true, - "quux": [ - "green eggs", - "ham" - ] - } -} diff --git a/assets/outputs/json/char.json b/assets/outputs/json/char.json deleted file mode 100644 index 2cb7a47..0000000 --- a/assets/outputs/json/char.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "foo": "a" -} diff --git a/assets/outputs/json/comment.json b/assets/outputs/json/comment.json deleted file mode 100644 index c8c4105..0000000 --- a/assets/outputs/json/comment.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "foo": "bar" -} diff --git a/assets/outputs/json/compact.json b/assets/outputs/json/compact.json deleted file mode 100644 index 65ecfe0..0000000 --- a/assets/outputs/json/compact.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "one": { - "foo": "bar", - "bar": "foo" - }, - "two": { - "foo": 1, - "bar": 2 - }, - "three": { - "foo": 1.0, - "bar": 2.0 - }, - "four": { - "foo": true, - "bar": false - }, - "five": { - "foo": null, - "bar": null - }, - "six": { - "foo": {}, - "bar": {} - }, - "seven": { - "foo": [], - "bar": [] - }, - "eight": [ - "foo", - "bar" - ], - "nine": [ - true, - false - ], - "ten": [ - 1, - 2 - ], - "eleven": [ - [], - [] - ], - "twelve": [ - {}, - {} - ] -} diff --git a/assets/outputs/json/complex.json b/assets/outputs/json/complex.json deleted file mode 100644 index ae476ef..0000000 --- a/assets/outputs/json/complex.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "name": { - "first": "John", - "last": "Smith", - "full": "John Smith" - }, - "age": 32, - "placeholder": null, - "employment": { - "employed": true, - "name": "Postman", - "sinceYear": 2019 - }, - "parents": { - "father": { - "birthday": { - "year": 1970, - "month": 2, - "day": 3 - } - } - }, - "gender": "M", - "favourites": [ - "blue", - "fish", - "egg", - "Postman", - 4.73753, - false, - { - "hello": "world", - "food": { - "favourite": "egg", - "hated": "beef" - } - } - ], - "empty1": {}, - "empty2": [], - "negative": { - "int": -34, - "float": -34.34 - } -} diff --git a/assets/outputs/json/complex_keys.json b/assets/outputs/json/complex_keys.json deleted file mode 100644 index 3fac302..0000000 --- a/assets/outputs/json/complex_keys.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "with_underscore": 0, - "with-dash": 1, - "with_🌽": 2, - "!\"£$%^&*()_": 3, - "j12345": 4, - "foo": { - "bar-baz": "hello" - }, - "apple-pie": { - "crust": "yum" - } -} diff --git a/assets/outputs/json/environment_variable.json b/assets/outputs/json/environment_variable.json deleted file mode 100644 index c8c4105..0000000 --- a/assets/outputs/json/environment_variable.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "foo": "bar" -} diff --git a/assets/outputs/json/float.json b/assets/outputs/json/float.json deleted file mode 100644 index 1133dc4..0000000 --- a/assets/outputs/json/float.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "foo": 3.14159, - "bar": 101.0 -} diff --git a/assets/outputs/json/input.json b/assets/outputs/json/input.json deleted file mode 100644 index 0920b9d..0000000 --- a/assets/outputs/json/input.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": { - "first": "John", - "last": "Smith" - }, - "dob": { - "day": 1, - "month": 1, - "year": 1970 - } -} diff --git a/assets/outputs/json/input_references_input.json b/assets/outputs/json/input_references_input.json deleted file mode 100644 index c8c4105..0000000 --- a/assets/outputs/json/input_references_input.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "foo": "bar" -} diff --git a/assets/outputs/json/integer.json b/assets/outputs/json/integer.json deleted file mode 100644 index b5ceb63..0000000 --- a/assets/outputs/json/integer.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "foo": 42, - "bar": 16448250, - "baz": 1000000 -} diff --git a/assets/outputs/json/invalid.json b/assets/outputs/json/invalid.json deleted file mode 100644 index e69de29..0000000 diff --git a/assets/outputs/json/invalid_input.json b/assets/outputs/json/invalid_input.json deleted file mode 100644 index e69de29..0000000 diff --git a/assets/outputs/json/invalid_nesting.json b/assets/outputs/json/invalid_nesting.json deleted file mode 100644 index e69de29..0000000 diff --git a/assets/outputs/json/invalid_spread.json b/assets/outputs/json/invalid_spread.json deleted file mode 100644 index e69de29..0000000 diff --git a/assets/outputs/json/mixed_array.json b/assets/outputs/json/mixed_array.json deleted file mode 100644 index 349c955..0000000 --- a/assets/outputs/json/mixed_array.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "foo": [ - 1, - "two", - false - ] -} diff --git a/assets/outputs/json/null.json b/assets/outputs/json/null.json deleted file mode 100644 index 52f08cd..0000000 --- a/assets/outputs/json/null.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "foo": null -} diff --git a/assets/outputs/json/null_in_array.json b/assets/outputs/json/null_in_array.json deleted file mode 100644 index bfe673a..0000000 --- a/assets/outputs/json/null_in_array.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "foo": [ - null - ] -} diff --git a/assets/outputs/json/object.json b/assets/outputs/json/object.json deleted file mode 100644 index 2ff6c73..0000000 --- a/assets/outputs/json/object.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "foo": { - "bar": 42 - } -} diff --git a/assets/outputs/json/object_in_array.json b/assets/outputs/json/object_in_array.json deleted file mode 100644 index d40b409..0000000 --- a/assets/outputs/json/object_in_array.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "foo": [ - { - "foo": 1, - "bar": 2 - } - ] -} diff --git a/assets/outputs/json/quoted_keys.json b/assets/outputs/json/quoted_keys.json deleted file mode 100644 index c1bc3f5..0000000 --- a/assets/outputs/json/quoted_keys.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "foo.bar": 42, - "green.eggs": { - "and": { - "ham": "hello world" - } - }, - "with spaces": true, - "escaped'quote": false, - "escaped=equals": -3 -} diff --git a/assets/outputs/json/readme_example.json b/assets/outputs/json/readme_example.json deleted file mode 100644 index 74233df..0000000 --- a/assets/outputs/json/readme_example.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "example-package", - "version": "1.0.0", - "main": "dist/index.js", - "bin": { - "filebrowser": "dist/index.js" - }, - "private": false, - "author": { - "name": "John Smith", - "email": "mail@example.com", - "url": "https://example.com" - }, - "contributors": [ - { - "name": "John Smith", - "email": "mail@example.com" - } - ], - "scripts": { - "build": "tsc", - "run": "node dist" - }, - "dependencies": { - "dotenv": "^8.2.0" - }, - "devDependencies": { - "typescript": "^4.5" - }, - "config": { - "port": 8080, - "hostname": null - } -} diff --git a/assets/outputs/json/spread.json b/assets/outputs/json/spread.json deleted file mode 100644 index 632051c..0000000 --- a/assets/outputs/json/spread.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "hello": "world", - "bar": "baz", - "nums": [ - 1, - 2, - 3, - 4 - ] -} diff --git a/assets/outputs/json/string.json b/assets/outputs/json/string.json deleted file mode 100644 index 53e6ede..0000000 --- a/assets/outputs/json/string.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "foo": "bar", - "bar": "\"\\\n", - "baz": "a", - "qux": "" -} diff --git a/assets/outputs/json/string_interpolation.json b/assets/outputs/json/string_interpolation.json deleted file mode 100644 index 9068aec..0000000 --- a/assets/outputs/json/string_interpolation.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "foo": "hello, world", - "bar": "$escaped" -} diff --git a/assets/outputs/json/string_multiline.json b/assets/outputs/json/string_multiline.json deleted file mode 100644 index 888d65d..0000000 --- a/assets/outputs/json/string_multiline.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "foo": "hello\nworld\n" -} diff --git a/assets/outputs/json/value_after_table.json b/assets/outputs/json/value_after_table.json deleted file mode 100644 index 93e0fd7..0000000 --- a/assets/outputs/json/value_after_table.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "foo": {}, - "qux": true -} diff --git a/assets/outputs/json/very_compact.json b/assets/outputs/json/very_compact.json deleted file mode 100644 index 65ecfe0..0000000 --- a/assets/outputs/json/very_compact.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "one": { - "foo": "bar", - "bar": "foo" - }, - "two": { - "foo": 1, - "bar": 2 - }, - "three": { - "foo": 1.0, - "bar": 2.0 - }, - "four": { - "foo": true, - "bar": false - }, - "five": { - "foo": null, - "bar": null - }, - "six": { - "foo": {}, - "bar": {} - }, - "seven": { - "foo": [], - "bar": [] - }, - "eight": [ - "foo", - "bar" - ], - "nine": [ - true, - false - ], - "ten": [ - 1, - 2 - ], - "eleven": [ - [], - [] - ], - "twelve": [ - {}, - {} - ] -} diff --git a/assets/outputs/toml/array.toml b/assets/outputs/toml/array.toml deleted file mode 100644 index 93915c9..0000000 --- a/assets/outputs/toml/array.toml +++ /dev/null @@ -1,6 +0,0 @@ -foo = [ - 1, - 2, - 3, -] - diff --git a/assets/outputs/toml/basic.toml b/assets/outputs/toml/basic.toml deleted file mode 100644 index ff8cd1f..0000000 --- a/assets/outputs/toml/basic.toml +++ /dev/null @@ -1,2 +0,0 @@ -foo = "bar" - diff --git a/assets/outputs/toml/basic_empty_let.toml b/assets/outputs/toml/basic_empty_let.toml deleted file mode 100644 index ff8cd1f..0000000 --- a/assets/outputs/toml/basic_empty_let.toml +++ /dev/null @@ -1,2 +0,0 @@ -foo = "bar" - diff --git a/assets/outputs/toml/boolean.toml b/assets/outputs/toml/boolean.toml deleted file mode 100644 index f1e8d97..0000000 --- a/assets/outputs/toml/boolean.toml +++ /dev/null @@ -1,3 +0,0 @@ -foo = true -bar = false - diff --git a/assets/outputs/toml/chained.toml b/assets/outputs/toml/chained.toml deleted file mode 100644 index 541b786..0000000 --- a/assets/outputs/toml/chained.toml +++ /dev/null @@ -1,3 +0,0 @@ -[foo] -bar = "baz" - diff --git a/assets/outputs/toml/chained_complex.toml b/assets/outputs/toml/chained_complex.toml deleted file mode 100644 index e488a75..0000000 --- a/assets/outputs/toml/chained_complex.toml +++ /dev/null @@ -1,10 +0,0 @@ -[foo] -qux = true -quux = [ - "green eggs", - "ham", -] - -[foo.bar] -baz = 42 - diff --git a/assets/outputs/toml/char.toml b/assets/outputs/toml/char.toml deleted file mode 100644 index bf0fbaa..0000000 --- a/assets/outputs/toml/char.toml +++ /dev/null @@ -1,2 +0,0 @@ -foo = "a" - diff --git a/assets/outputs/toml/comment.toml b/assets/outputs/toml/comment.toml deleted file mode 100644 index ff8cd1f..0000000 --- a/assets/outputs/toml/comment.toml +++ /dev/null @@ -1,2 +0,0 @@ -foo = "bar" - diff --git a/assets/outputs/toml/compact.toml b/assets/outputs/toml/compact.toml deleted file mode 100644 index 0d9cd8d..0000000 --- a/assets/outputs/toml/compact.toml +++ /dev/null @@ -1,47 +0,0 @@ -eight = [ - "foo", - "bar", -] -nine = [ - true, - false, -] -ten = [ - 1, - 2, -] -eleven = [ - [], - [], -] - -[one] -foo = "bar" -bar = "foo" - -[two] -foo = 1 -bar = 2 - -[three] -foo = 1.0 -bar = 2.0 - -[four] -foo = true -bar = false - -[five] - -[six.foo] - -[six.bar] - -[seven] -foo = [] -bar = [] - -[[twelve]] - -[[twelve]] - diff --git a/assets/outputs/toml/complex.toml b/assets/outputs/toml/complex.toml deleted file mode 100644 index 3cf09b6..0000000 --- a/assets/outputs/toml/complex.toml +++ /dev/null @@ -1,34 +0,0 @@ -age = 32 -gender = "M" -favourites = [ - "blue", - "fish", - "egg", - "Postman", - 4.73753, - false, - { hello = "world" }, -] -empty2 = [] - -[name] -first = "John" -last = "Smith" -full = "John Smith" - -[employment] -employed = true -name = "Postman" -sinceYear = 2019 - -[parents.father.birthday] -year = 1970 -month = 2 -day = 3 - -[empty1] - -[negative] -int = -34 -float = -34.34 - diff --git a/assets/outputs/toml/complex_keys.toml b/assets/outputs/toml/complex_keys.toml deleted file mode 100644 index 072b05e..0000000 --- a/assets/outputs/toml/complex_keys.toml +++ /dev/null @@ -1,12 +0,0 @@ -with_underscore = 0 -with-dash = 1 -"with_🌽" = 2 -'!"£$%^&*()_' = 3 -j12345 = 4 - -[foo] -bar-baz = "hello" - -[apple-pie] -crust = "yum" - diff --git a/assets/outputs/toml/environment_variable.toml b/assets/outputs/toml/environment_variable.toml deleted file mode 100644 index ff8cd1f..0000000 --- a/assets/outputs/toml/environment_variable.toml +++ /dev/null @@ -1,2 +0,0 @@ -foo = "bar" - diff --git a/assets/outputs/toml/float.toml b/assets/outputs/toml/float.toml deleted file mode 100644 index a8152ce..0000000 --- a/assets/outputs/toml/float.toml +++ /dev/null @@ -1,3 +0,0 @@ -foo = 3.14159 -bar = 101.0 - diff --git a/assets/outputs/toml/input.toml b/assets/outputs/toml/input.toml deleted file mode 100644 index 8edd9bf..0000000 --- a/assets/outputs/toml/input.toml +++ /dev/null @@ -1,9 +0,0 @@ -[name] -first = "John" -last = "Smith" - -[dob] -day = 1 -month = 1 -year = 1970 - diff --git a/assets/outputs/toml/input_references_input.toml b/assets/outputs/toml/input_references_input.toml deleted file mode 100644 index ff8cd1f..0000000 --- a/assets/outputs/toml/input_references_input.toml +++ /dev/null @@ -1,2 +0,0 @@ -foo = "bar" - diff --git a/assets/outputs/toml/integer.toml b/assets/outputs/toml/integer.toml deleted file mode 100644 index 23bf8f5..0000000 --- a/assets/outputs/toml/integer.toml +++ /dev/null @@ -1,4 +0,0 @@ -foo = 42 -bar = 16448250 -baz = 1000000 - diff --git a/assets/outputs/toml/invalid.toml b/assets/outputs/toml/invalid.toml deleted file mode 100644 index e69de29..0000000 diff --git a/assets/outputs/toml/invalid_input.toml b/assets/outputs/toml/invalid_input.toml deleted file mode 100644 index e69de29..0000000 diff --git a/assets/outputs/toml/invalid_nesting.toml b/assets/outputs/toml/invalid_nesting.toml deleted file mode 100644 index e69de29..0000000 diff --git a/assets/outputs/toml/invalid_spread.toml b/assets/outputs/toml/invalid_spread.toml deleted file mode 100644 index e69de29..0000000 diff --git a/assets/outputs/toml/mixed_array.toml b/assets/outputs/toml/mixed_array.toml deleted file mode 100644 index 8ef2495..0000000 --- a/assets/outputs/toml/mixed_array.toml +++ /dev/null @@ -1,6 +0,0 @@ -foo = [ - 1, - "two", - false, -] - diff --git a/assets/outputs/toml/null.toml b/assets/outputs/toml/null.toml deleted file mode 100644 index 8b13789..0000000 --- a/assets/outputs/toml/null.toml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/assets/outputs/toml/null_in_array.toml b/assets/outputs/toml/null_in_array.toml deleted file mode 100644 index e69de29..0000000 diff --git a/assets/outputs/toml/object.toml b/assets/outputs/toml/object.toml deleted file mode 100644 index cb098b5..0000000 --- a/assets/outputs/toml/object.toml +++ /dev/null @@ -1,3 +0,0 @@ -[foo] -bar = 42 - diff --git a/assets/outputs/toml/object_in_array.toml b/assets/outputs/toml/object_in_array.toml deleted file mode 100644 index 0bdcc24..0000000 --- a/assets/outputs/toml/object_in_array.toml +++ /dev/null @@ -1,4 +0,0 @@ -[[foo]] -foo = 1 -bar = 2 - diff --git a/assets/outputs/toml/quoted_keys.toml b/assets/outputs/toml/quoted_keys.toml deleted file mode 100644 index 06bc629..0000000 --- a/assets/outputs/toml/quoted_keys.toml +++ /dev/null @@ -1,8 +0,0 @@ -"foo.bar" = 42 -"with spaces" = true -"escaped'quote" = false -"escaped=equals" = -3 - -["green.eggs".and] -ham = "hello world" - diff --git a/assets/outputs/toml/readme_example.toml b/assets/outputs/toml/readme_example.toml deleted file mode 100644 index 04bcc5d..0000000 --- a/assets/outputs/toml/readme_example.toml +++ /dev/null @@ -1,30 +0,0 @@ -name = "example-package" -version = "1.0.0" -main = "dist/index.js" -private = false - -[bin] -filebrowser = "dist/index.js" - -[author] -name = "John Smith" -email = "mail@example.com" -url = "https://example.com" - -[[contributors]] -name = "John Smith" -email = "mail@example.com" - -[scripts] -build = "tsc" -run = "node dist" - -[dependencies] -dotenv = "^8.2.0" - -[devDependencies] -typescript = "^4.5" - -[config] -port = 8080 - diff --git a/assets/outputs/toml/spread.toml b/assets/outputs/toml/spread.toml deleted file mode 100644 index c7fd933..0000000 --- a/assets/outputs/toml/spread.toml +++ /dev/null @@ -1,9 +0,0 @@ -hello = "world" -bar = "baz" -nums = [ - 1, - 2, - 3, - 4, -] - diff --git a/assets/outputs/toml/string.toml b/assets/outputs/toml/string.toml deleted file mode 100644 index f4cc605..0000000 --- a/assets/outputs/toml/string.toml +++ /dev/null @@ -1,7 +0,0 @@ -foo = "bar" -bar = ''' -"\ -''' -baz = "a" -qux = "" - diff --git a/assets/outputs/toml/string_interpolation.toml b/assets/outputs/toml/string_interpolation.toml deleted file mode 100644 index f359285..0000000 --- a/assets/outputs/toml/string_interpolation.toml +++ /dev/null @@ -1,3 +0,0 @@ -foo = "hello, world" -bar = "$escaped" - diff --git a/assets/outputs/toml/string_multiline.toml b/assets/outputs/toml/string_multiline.toml deleted file mode 100644 index d2a49da..0000000 --- a/assets/outputs/toml/string_multiline.toml +++ /dev/null @@ -1,5 +0,0 @@ -foo = """ -hello -world -""" - diff --git a/assets/outputs/toml/value_after_table.toml b/assets/outputs/toml/value_after_table.toml deleted file mode 100644 index 26b3e99..0000000 --- a/assets/outputs/toml/value_after_table.toml +++ /dev/null @@ -1,4 +0,0 @@ -qux = true - -[foo] - diff --git a/assets/outputs/toml/very_compact.toml b/assets/outputs/toml/very_compact.toml deleted file mode 100644 index 0d9cd8d..0000000 --- a/assets/outputs/toml/very_compact.toml +++ /dev/null @@ -1,47 +0,0 @@ -eight = [ - "foo", - "bar", -] -nine = [ - true, - false, -] -ten = [ - 1, - 2, -] -eleven = [ - [], - [], -] - -[one] -foo = "bar" -bar = "foo" - -[two] -foo = 1 -bar = 2 - -[three] -foo = 1.0 -bar = 2.0 - -[four] -foo = true -bar = false - -[five] - -[six.foo] - -[six.bar] - -[seven] -foo = [] -bar = [] - -[[twelve]] - -[[twelve]] - diff --git a/assets/outputs/yaml/array.yml b/assets/outputs/yaml/array.yml deleted file mode 100644 index bd735ce..0000000 --- a/assets/outputs/yaml/array.yml +++ /dev/null @@ -1,5 +0,0 @@ -foo: -- 1 -- 2 -- 3 - diff --git a/assets/outputs/yaml/basic.yml b/assets/outputs/yaml/basic.yml deleted file mode 100644 index 8218b15..0000000 --- a/assets/outputs/yaml/basic.yml +++ /dev/null @@ -1,2 +0,0 @@ -foo: bar - diff --git a/assets/outputs/yaml/basic_empty_let.yml b/assets/outputs/yaml/basic_empty_let.yml deleted file mode 100644 index 8218b15..0000000 --- a/assets/outputs/yaml/basic_empty_let.yml +++ /dev/null @@ -1,2 +0,0 @@ -foo: bar - diff --git a/assets/outputs/yaml/boolean.yml b/assets/outputs/yaml/boolean.yml deleted file mode 100644 index 51803fe..0000000 --- a/assets/outputs/yaml/boolean.yml +++ /dev/null @@ -1,3 +0,0 @@ -foo: true -bar: false - diff --git a/assets/outputs/yaml/chained.yml b/assets/outputs/yaml/chained.yml deleted file mode 100644 index 72e0412..0000000 --- a/assets/outputs/yaml/chained.yml +++ /dev/null @@ -1,3 +0,0 @@ -foo: - bar: baz - diff --git a/assets/outputs/yaml/chained_complex.yml b/assets/outputs/yaml/chained_complex.yml deleted file mode 100644 index 60d173c..0000000 --- a/assets/outputs/yaml/chained_complex.yml +++ /dev/null @@ -1,8 +0,0 @@ -foo: - bar: - baz: 42 - qux: true - quux: - - green eggs - - ham - diff --git a/assets/outputs/yaml/char.yml b/assets/outputs/yaml/char.yml deleted file mode 100644 index 8445b0e..0000000 --- a/assets/outputs/yaml/char.yml +++ /dev/null @@ -1,2 +0,0 @@ -foo: a - diff --git a/assets/outputs/yaml/comment.yml b/assets/outputs/yaml/comment.yml deleted file mode 100644 index 8218b15..0000000 --- a/assets/outputs/yaml/comment.yml +++ /dev/null @@ -1,2 +0,0 @@ -foo: bar - diff --git a/assets/outputs/yaml/compact.yml b/assets/outputs/yaml/compact.yml deleted file mode 100644 index ada8415..0000000 --- a/assets/outputs/yaml/compact.yml +++ /dev/null @@ -1,37 +0,0 @@ -one: - foo: bar - bar: foo -two: - foo: 1 - bar: 2 -three: - foo: 1.0 - bar: 2.0 -four: - foo: true - bar: false -five: - foo: null - bar: null -six: - foo: {} - bar: {} -seven: - foo: [] - bar: [] -eight: -- foo -- bar -nine: -- true -- false -ten: -- 1 -- 2 -eleven: -- [] -- [] -twelve: -- {} -- {} - diff --git a/assets/outputs/yaml/complex.yml b/assets/outputs/yaml/complex.yml deleted file mode 100644 index 8fbbc46..0000000 --- a/assets/outputs/yaml/complex.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: - first: John - last: Smith - full: John Smith -age: 32 -placeholder: null -employment: - employed: true - name: Postman - sinceYear: 2019 -parents: - father: - birthday: - year: 1970 - month: 2 - day: 3 -gender: M -favourites: -- blue -- fish -- egg -- Postman -- 4.73753 -- false -- hello: world - food: - favourite: egg - hated: beef -empty1: {} -empty2: [] -negative: - int: -34 - float: -34.34 - diff --git a/assets/outputs/yaml/complex_keys.yml b/assets/outputs/yaml/complex_keys.yml deleted file mode 100644 index cb4e419..0000000 --- a/assets/outputs/yaml/complex_keys.yml +++ /dev/null @@ -1,10 +0,0 @@ -with_underscore: 0 -with-dash: 1 -with_🌽: 2 -'!"£$%^&*()_': 3 -j12345: 4 -foo: - bar-baz: hello -apple-pie: - crust: yum - diff --git a/assets/outputs/yaml/environment_variable.yml b/assets/outputs/yaml/environment_variable.yml deleted file mode 100644 index 8218b15..0000000 --- a/assets/outputs/yaml/environment_variable.yml +++ /dev/null @@ -1,2 +0,0 @@ -foo: bar - diff --git a/assets/outputs/yaml/float.yml b/assets/outputs/yaml/float.yml deleted file mode 100644 index fc333c1..0000000 --- a/assets/outputs/yaml/float.yml +++ /dev/null @@ -1,3 +0,0 @@ -foo: 3.14159 -bar: 101.0 - diff --git a/assets/outputs/yaml/input.yml b/assets/outputs/yaml/input.yml deleted file mode 100644 index ae8d162..0000000 --- a/assets/outputs/yaml/input.yml +++ /dev/null @@ -1,8 +0,0 @@ -name: - first: John - last: Smith -dob: - day: 1 - month: 1 - year: 1970 - diff --git a/assets/outputs/yaml/input_references_input.yml b/assets/outputs/yaml/input_references_input.yml deleted file mode 100644 index 8218b15..0000000 --- a/assets/outputs/yaml/input_references_input.yml +++ /dev/null @@ -1,2 +0,0 @@ -foo: bar - diff --git a/assets/outputs/yaml/integer.yml b/assets/outputs/yaml/integer.yml deleted file mode 100644 index e76f2c1..0000000 --- a/assets/outputs/yaml/integer.yml +++ /dev/null @@ -1,4 +0,0 @@ -foo: 42 -bar: 16448250 -baz: 1000000 - diff --git a/assets/outputs/yaml/invalid.yml b/assets/outputs/yaml/invalid.yml deleted file mode 100644 index e69de29..0000000 diff --git a/assets/outputs/yaml/invalid_input.yml b/assets/outputs/yaml/invalid_input.yml deleted file mode 100644 index e69de29..0000000 diff --git a/assets/outputs/yaml/invalid_nesting.yml b/assets/outputs/yaml/invalid_nesting.yml deleted file mode 100644 index e69de29..0000000 diff --git a/assets/outputs/yaml/invalid_spread.yml b/assets/outputs/yaml/invalid_spread.yml deleted file mode 100644 index e69de29..0000000 diff --git a/assets/outputs/yaml/mixed_array.yml b/assets/outputs/yaml/mixed_array.yml deleted file mode 100644 index 4f6aead..0000000 --- a/assets/outputs/yaml/mixed_array.yml +++ /dev/null @@ -1,5 +0,0 @@ -foo: -- 1 -- two -- false - diff --git a/assets/outputs/yaml/null.yml b/assets/outputs/yaml/null.yml deleted file mode 100644 index 1e30655..0000000 --- a/assets/outputs/yaml/null.yml +++ /dev/null @@ -1,2 +0,0 @@ -foo: null - diff --git a/assets/outputs/yaml/null_in_array.yml b/assets/outputs/yaml/null_in_array.yml deleted file mode 100644 index 74c12c9..0000000 --- a/assets/outputs/yaml/null_in_array.yml +++ /dev/null @@ -1,3 +0,0 @@ -foo: -- null - diff --git a/assets/outputs/yaml/object.yml b/assets/outputs/yaml/object.yml deleted file mode 100644 index 5c3430e..0000000 --- a/assets/outputs/yaml/object.yml +++ /dev/null @@ -1,3 +0,0 @@ -foo: - bar: 42 - diff --git a/assets/outputs/yaml/object_in_array.yml b/assets/outputs/yaml/object_in_array.yml deleted file mode 100644 index 59808f2..0000000 --- a/assets/outputs/yaml/object_in_array.yml +++ /dev/null @@ -1,4 +0,0 @@ -foo: -- foo: 1 - bar: 2 - diff --git a/assets/outputs/yaml/quoted_keys.yml b/assets/outputs/yaml/quoted_keys.yml deleted file mode 100644 index da5aced..0000000 --- a/assets/outputs/yaml/quoted_keys.yml +++ /dev/null @@ -1,8 +0,0 @@ -foo.bar: 42 -green.eggs: - and: - ham: hello world -with spaces: true -escaped'quote: false -escaped=equals: -3 - diff --git a/assets/outputs/yaml/readme_example.yml b/assets/outputs/yaml/readme_example.yml deleted file mode 100644 index 423d613..0000000 --- a/assets/outputs/yaml/readme_example.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: example-package -version: 1.0.0 -main: dist/index.js -bin: - filebrowser: dist/index.js -private: false -author: - name: John Smith - email: mail@example.com - url: https://example.com -contributors: -- name: John Smith - email: mail@example.com -scripts: - build: tsc - run: node dist -dependencies: - dotenv: ^8.2.0 -devDependencies: - typescript: ^4.5 -config: - port: 8080 - hostname: null - diff --git a/assets/outputs/yaml/spread.yml b/assets/outputs/yaml/spread.yml deleted file mode 100644 index ca39fc1..0000000 --- a/assets/outputs/yaml/spread.yml +++ /dev/null @@ -1,8 +0,0 @@ -hello: world -bar: baz -nums: -- 1 -- 2 -- 3 -- 4 - diff --git a/assets/outputs/yaml/string.yml b/assets/outputs/yaml/string.yml deleted file mode 100644 index f1649d4..0000000 --- a/assets/outputs/yaml/string.yml +++ /dev/null @@ -1,6 +0,0 @@ -foo: bar -bar: | - "\ -baz: a -qux: '' - diff --git a/assets/outputs/yaml/string_interpolation.yml b/assets/outputs/yaml/string_interpolation.yml deleted file mode 100644 index 85e84ed..0000000 --- a/assets/outputs/yaml/string_interpolation.yml +++ /dev/null @@ -1,3 +0,0 @@ -foo: hello, world -bar: $escaped - diff --git a/assets/outputs/yaml/string_multiline.yml b/assets/outputs/yaml/string_multiline.yml deleted file mode 100644 index 58f9fac..0000000 --- a/assets/outputs/yaml/string_multiline.yml +++ /dev/null @@ -1,4 +0,0 @@ -foo: | - hello - world - diff --git a/assets/outputs/yaml/value_after_table.yml b/assets/outputs/yaml/value_after_table.yml deleted file mode 100644 index d8760a5..0000000 --- a/assets/outputs/yaml/value_after_table.yml +++ /dev/null @@ -1,3 +0,0 @@ -foo: {} -qux: true - diff --git a/assets/outputs/yaml/very_compact.yml b/assets/outputs/yaml/very_compact.yml deleted file mode 100644 index ada8415..0000000 --- a/assets/outputs/yaml/very_compact.yml +++ /dev/null @@ -1,37 +0,0 @@ -one: - foo: bar - bar: foo -two: - foo: 1 - bar: 2 -three: - foo: 1.0 - bar: 2.0 -four: - foo: true - bar: false -five: - foo: null - bar: null -six: - foo: {} - bar: {} -seven: - foo: [] - bar: [] -eight: -- foo -- bar -nine: -- true -- false -ten: -- 1 -- 2 -eleven: -- [] -- [] -twelve: -- {} -- {} - diff --git a/benches/serde.rs b/benches/serde.rs deleted file mode 100644 index b37a030..0000000 --- a/benches/serde.rs +++ /dev/null @@ -1,453 +0,0 @@ -use criterion::{criterion_group, criterion_main, Criterion, Throughput}; -use serde::Deserialize; -use std::hint::black_box; - -#[derive(Deserialize, Debug, PartialEq)] -struct Empty {} - -#[derive(Deserialize, Debug, PartialEq)] -struct Array { - foo: Vec, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct Basic { - foo: String, -} - -#[derive(Deserialize, Debug, PartialEq)] -#[serde(rename_all = "snake_case")] -enum BasicNewTypeEnum { - Foo(String), -} - -#[derive(Deserialize, Debug, PartialEq)] -struct BasicUnitEnum { - foo: BasicUnitEnumInner, -} - -#[derive(Deserialize, Debug, PartialEq)] -#[serde(rename_all = "snake_case")] -enum BasicUnitEnumInner { - Bar, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct BasicNewType { - foo: BasicNewTypeInner, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct BasicNewTypeInner(String); - -#[derive(Deserialize, Debug, PartialEq)] -struct Boolean { - foo: bool, - bar: bool, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct Bytes<'a> { - foo: &'a [u8], -} - -#[derive(Deserialize, Debug, PartialEq)] -struct Chained { - foo: ChainedInner, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct ChainedInner { - bar: String, -} - -#[derive(Deserialize, Debug, PartialEq)] -#[serde(rename_all = "snake_case")] -enum ChainedEnum { - Foo { bar: String }, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct ChainedComplex { - foo: ChainedComplexInner, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct ChainedComplexInner { - bar: ChainedComplexInnerInner, - qux: bool, - quux: Vec, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct ChainedComplexInnerInner { - baz: i64, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct Char { - foo: char, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct Compact { - one: CompactOne, - two: CompactTwo, - three: CompactThree, - four: CompactFour, - five: CompactFive, - six: CompactSix, - seven: CompactSeven, - eight: Vec, - nine: Vec, - ten: Vec<()>, - eleven: Vec>, - twelve: Vec, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct CompactOne { - foo: String, - bar: String, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct CompactTwo { - foo: i64, - bar: i64, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct CompactThree { - foo: f64, - bar: f64, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct CompactFour { - foo: bool, - bar: bool, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct CompactFive { - foo: (), - bar: (), -} - -#[derive(Deserialize, Debug, PartialEq)] -struct CompactSix { - foo: Empty, - bar: Empty, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct CompactSeven { - foo: Vec, - bar: Vec, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct Complex { - age: i64, - employment: ComplexEmployment, - empty1: Empty, - empty2: Vec, - favourites: (String, String, String, String, f64, bool, Favourites), - gender: String, - name: ComplexName, - negative: ComplexNegative, - parents: ComplexParents, - placeholder: (), -} - -#[derive(Deserialize, Debug, PartialEq)] -#[serde(rename_all = "camelCase")] -struct ComplexEmployment { - employed: bool, - name: String, - since_year: i64, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct Favourites { - food: ComplexFavouritesFood, - hello: String, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct ComplexFavouritesFood { - favourite: String, - hated: String, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct ComplexName { - first: String, - full: String, - last: String, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct ComplexNegative { - float: f64, - int: i64, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct ComplexParents { - father: ComplexParentsFather, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct ComplexParentsFather { - birthday: ComplexParentsFatherBirthday, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct ComplexParentsFatherBirthday { - day: i64, - month: i64, - year: i64, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct ComplexKeys { - #[serde(rename = "!\"£$%^&*()_")] - symbols: i64, - #[serde(rename = "apple-pie")] - apple_pie: ComplexKeysApplePie, - foo: ComplexKeysFoo, - j12345: i64, - #[serde(rename = "with-dash")] - with_dash: i64, - #[serde(rename = "with_underscore")] - with_underscore: i64, - #[serde(rename = "with_🌽")] - with_corn_emoji: i64, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct ComplexKeysApplePie { - crust: String, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct ComplexKeysFoo { - #[serde(rename = "bar-baz")] - bar_baz: String, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct Float { - foo: f64, - bar: f64, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct Input { - name: InputName, - dob: InputDob, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct InputName { - first: String, - last: String, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct InputDob { - day: u8, - month: u8, - year: u16, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct Integer { - foo: i64, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct MixedArray { - foo: (u8, String, bool), -} - -#[derive(Deserialize, Debug, PartialEq)] -#[serde(rename_all = "snake_case")] -enum MixedArrayEnum { - Foo(u8, String, bool), -} - -#[derive(Deserialize, Debug, PartialEq)] -struct Null { - foo: (), -} - -#[derive(Deserialize, Debug, PartialEq)] -struct NullOption { - foo: Option, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct NullUnit { - foo: NullUnitInner, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct NullUnitInner; - -#[derive(Deserialize, Debug, PartialEq)] -struct Object { - foo: SubObject, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct SubObject { - bar: i64, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct ObjectInArray { - foo: Vec, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct ObjectInArrayFoo { - bar: i64, - foo: i64, -} - -#[derive(Deserialize, Debug, PartialEq)] -#[serde(rename_all = "camelCase")] -struct ReadmeExample { - author: ReadmeExampleAuthor, - bin: ReadmeExampleBin, - config: ReadmeExampleConfig, - contributors: Vec, - dependencies: ReadmeExampleDependencies, - dev_dependencies: ReadmeExampleDevDependencies, - main: String, - name: String, - private: bool, - scripts: ReadmeExampleScripts, - version: String, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct String_ { - foo: String, - bar: String, - baz: String, - qux: String, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct ReadmeExampleAuthor { - email: String, - name: String, - url: String, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct ReadmeExampleBin { - filebrowser: String, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct ReadmeExampleConfig { - hostname: Option, - port: i64, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct ReadmeExampleContributor { - email: String, - name: String, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct ReadmeExampleDependencies { - dotenv: String, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct ReadmeExampleDevDependencies { - typescript: String, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct ReadmeExampleScripts { - build: String, - run: String, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct Str<'a> { - foo: &'a str, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct ValueAfterTable { - foo: Empty, - qux: bool, -} - -macro_rules! bench_case { - ($group:expr, $name:literal, $input_file:literal, $output_type:ty) => { - let input = include_str!(concat!("../assets/inputs/", $input_file, ".corn")); - $group.throughput(Throughput::Bytes(input.len() as u64)); - $group.bench_function($name, |b| { - b.iter(|| { - let result: Result<$output_type, _> = corn::from_str(black_box(input)); - black_box(result.unwrap()) - }) - }); - }; -} - -fn criterion_benchmark(c: &mut Criterion) { - let mut group = c.benchmark_group("corn_parsing"); - group.sample_size(500); - - bench_case!(group, "array", "array", Array); - bench_case!(group, "basic", "basic", Basic); - bench_case!(group, "basic_empty_let", "basic_empty_let", Basic); - bench_case!(group, "boolean", "boolean", Boolean); - bench_case!(group, "chained", "chained", Chained); - bench_case!(group, "chained_complex", "chained_complex", ChainedComplex); - bench_case!(group, "char", "char", Char); - bench_case!(group, "comment", "comment", Basic); - bench_case!(group, "compact", "compact", Compact); - bench_case!(group, "complex", "complex", Complex); - bench_case!(group, "complex_keys", "complex_keys", ComplexKeys); - bench_case!(group, "environment_variable", "environment_variable", Basic); - bench_case!(group, "float", "float", Float); - bench_case!(group, "input", "input", Input); - bench_case!( - group, - "input_references_input", - "input_references_input", - Basic - ); - bench_case!(group, "integer", "integer", Integer); - bench_case!(group, "mixed_array", "mixed_array", MixedArray); - bench_case!(group, "null", "null", Null); - bench_case!(group, "object", "object", Object); - bench_case!(group, "object_in_array", "object_in_array", ObjectInArray); - bench_case!(group, "readme_example", "readme_example", ReadmeExample); - bench_case!(group, "string", "string", String_); - bench_case!(group, "string_interpolation", "string_interpolation", Basic); - bench_case!( - group, - "value_after_table", - "value_after_table", - ValueAfterTable - ); - bench_case!(group, "very_compact", "very_compact", Compact); - - group.finish(); -} - -criterion_group!(benches, criterion_benchmark); -criterion_main!(benches); diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..ab1424e --- /dev/null +++ b/build.rs @@ -0,0 +1,5 @@ +fn main() { + println!("cargo::rerun-if-changed=src/corn.lalrpop"); + + lalrpop::process_src().expect("Failed to parse grammar"); +} diff --git a/cli/Cargo.toml b/cli/Cargo.toml index a3b3906..230096a 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "corn-cli" version = "0.10.0" -edition = "2021" +edition = "2024" license = "MIT" description = "CLI for Corn. A simple and pain-free configuration language." repository = "https://github.com/corn-config/corn" @@ -14,12 +14,12 @@ documentation = "https://docs.rs/corn-cli" [dependencies] # CLI dependencies -clap = { version = "4.5.31", features = ["derive", "cargo"] } +clap = { version = "4.5.46", features = ["derive", "cargo"] } colored = "3.0.0" # (De)serialization -serde = { version = "1.0.218", features = ["derive"] } -serde_json = "1.0.140" +serde = { version = "1.0.219", features = ["derive"] } +serde_json = "1.0.143" serde_norway = "0.9.42" libcorn = { version = "0.10.0", path = ".." } -toml_edit = { version = "0.22.24", features = ["serde"] } +toml_edit = { version = "0.23.4", features = ["serde"] } diff --git a/cli/src/error.rs b/cli/src/error.rs index 58f213b..8aba108 100644 --- a/cli/src/error.rs +++ b/cli/src/error.rs @@ -1,5 +1,5 @@ use colored::Colorize; -use corn::error::Error as CornError; +use corn::Error as CornError; use std::fmt::{Display, Formatter}; use std::io; @@ -20,12 +20,10 @@ pub trait ExitCode { impl ExitCode for CornError { fn get_exit_code(&self) -> i32 { match self { - CornError::Io(_) => 3, - CornError::ParserError(_) => 1, + CornError::ParseError(_) => 1, CornError::InputResolveError(_) => 2, - CornError::InvalidPathError(_) => 6, - CornError::InvalidSpreadError(_) => 7, - CornError::InvalidInterpolationError(_) => 8, + CornError::InvalidSpreadError => 7, + CornError::InvalidInterpolationError => 8, CornError::DeserializationError(_) => 5, } } diff --git a/cli/src/main.rs b/cli/src/main.rs index 71e40cb..d240d8e 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,4 +1,4 @@ -use corn::{parse, Value}; +use corn::{BorrowedValue, parse}; use std::io::Read; use std::process::exit; use std::{fs, io}; @@ -7,7 +7,7 @@ use clap::{Parser, ValueEnum}; use colored::Colorize; mod error; -use error::{print_err, Error, ExitCode}; +use error::{Error, ExitCode, print_err}; #[derive(ValueEnum, Clone, Copy, Debug)] enum OutputType { @@ -48,7 +48,7 @@ fn main() { let output_type = get_output_type(args.output_type); match parse(&unparsed_file) { - Ok(config) => match serialize(&config, output_type) { + Ok(config) => match serialize(config, output_type) { Ok(serialized) => println!("{serialized}"), Err(err) => handle_err(&err), }, @@ -81,7 +81,7 @@ fn get_output_type(arg: Option) -> OutputType { OutputType::Json } -fn serialize(config: &Value, output_type: OutputType) -> Result { +fn serialize(config: BorrowedValue, output_type: OutputType) -> Result { match output_type { OutputType::Json => serde_json::to_string_pretty(&config).map_err(Error::from), OutputType::Yaml => serde_norway::to_string(&config).map_err(Error::from), diff --git a/scripts/generate-tests.sh b/scripts/generate-tests.sh index 53cfe96..a63236c 100755 --- a/scripts/generate-tests.sh +++ b/scripts/generate-tests.sh @@ -10,7 +10,7 @@ for file in assets/inputs/*; do echo "$basename" - cargo run --bin corn -- "$file" -t json > assets/outputs/json/"$basename".json - cargo run --bin corn -- "$file" -t yaml > assets/outputs/yaml/"$basename".yml - cargo run --bin corn -- "$file" -t toml > assets/outputs/toml/"$basename".toml + cargo run --package corn-cli -- "$file" -t json > assets/outputs/json/"$basename".json + cargo run --package corn-cli -- "$file" -t yaml > assets/outputs/yaml/"$basename".yml + cargo run --package corn-cli -- "$file" -t toml > assets/outputs/toml/"$basename".toml done \ No newline at end of file diff --git a/src/ast.rs b/src/ast.rs new file mode 100644 index 0000000..035919a --- /dev/null +++ b/src/ast.rs @@ -0,0 +1,72 @@ +use crate::{Integer, lexer::StringPart}; +use alloc::vec::Vec; + +#[cfg(not(feature = "std"))] +use hashbrown::HashMap; +#[cfg(feature = "std")] +use std::collections::HashMap; + +/// Store for input declarations +pub type Inputs<'input> = HashMap<&'input str, Entry<'input>>; + +/// Top level ast object +#[derive(Debug, Clone)] +pub struct Root<'input> { + /// Raw inputs + pub inputs: Inputs<'input>, + /// Top level object, values aren't interpolated + pub object: Object<'input>, +} + +/// Represents an object in the AST +#[derive(Debug, Clone)] +pub struct Object<'input> { + // /// The pairs or spread operations in the object + pub pairs: Vec>, +} + +/// Either a key-value pair or a spread operation +#[derive(Debug, Clone)] +pub enum PairOrSpread<'input> { + /// A key-value pair in an object + Pair(ChainedKey<'input>, Entry<'input>), + /// A spread operation in an object + Spread(&'input str), +} + +/// Represents a chained key like "foo.bar.baz" +#[derive(Debug, Clone)] +pub struct ChainedKey<'input> { + /// The segments of the key path + pub segments: Vec<&'input str>, +} + +/// An entry can be of various types as defined in the spec +#[derive(Debug, Clone)] +pub enum Entry<'input> { + /// String literal + String(Vec>), + /// Integer value + Integer(Integer), + /// Float value + Float(f64), + /// Boolean value + Boolean(bool), + /// Nested object + Object(Object<'input>), + /// Array of entries + Array(Vec>), + /// Null value + Null, + /// Reference to an input variable + Input(&'input str), +} + +/// Either a key or an array spread operation +#[derive(Debug, Clone)] +pub enum EntryOrSpread<'input> { + /// An entry can be of various types as defined in the spec + Entry(Entry<'input>), + /// Array spread operation + Spread(&'input str), +} diff --git a/src/corn.lalrpop b/src/corn.lalrpop new file mode 100644 index 0000000..d52c4ad --- /dev/null +++ b/src/corn.lalrpop @@ -0,0 +1,89 @@ +use crate::ast::{Root, Entry, Object, Inputs, PairOrSpread, ChainedKey, EntryOrSpread}; +use crate::lexer::{LexicalError, Token, StringPart}; +use crate::Integer; +use alloc::vec::Vec; +use alloc::vec; + +grammar<'input>(input: &'input str); + +extern { + type Location = usize; + type Error = LexicalError; + + enum Token<'input> { + "let" => Token::Let, + "in" => Token::In, + "null" => Token::Null, + "=" => Token::Equals, + "{" => Token::OpenBrace, + "}" => Token::CloseBrace, + "[" => Token::OpenBracket, + "]" => Token::CloseBracket, + "." => Token::Chain, + ".." => Token::Spread, + "literal" => Token::Literal(>>), + "int" => Token::Integer(), + "float" => Token::Float(), + "bool" => Token::Boolean(), + "input" => Token::InputName(<&'input str>), + "key" => Token::Key(<&'input str>), + } +} + +pub Root: Root<'input> = { + => { + Root { + inputs: inputs.unwrap_or_default(), + object, + } + } +} + +LetBlock: Inputs<'input> = { + "let" "{" )*> "}" "in" => inputs.into_iter().collect() +}; + +InputDeclaration: (&'input str, Entry<'input>) = { + "=" => (name, entry), +}; + +Entry: Entry<'input> = { + "literal" => Entry::String(<>), + "int" => Entry::Integer(<>), + "float" => Entry::Float(<>), + "bool" => Entry::Boolean(<>), + Object => Entry::Object(<>), + Array => Entry::Array(<>), + "null" => Entry::Null, + "input" => Entry::Input(<>), +} + +Object: Object<'input> = { + "{" )*> "}" => { + Object { + pairs, + } + } +}; + +Pair: PairOrSpread<'input> = { + "=" => PairOrSpread::Pair(k, v), + ".." => PairOrSpread::Spread(name), +}; + +KeyPath: ChainedKey<'input> = { + )*> => { + ChainedKey { + segments: [vec![first], rest].concat() + } + } +}; + +Array: Vec> = { + "[" "]" => entries, +}; + +ArrayEntry: EntryOrSpread<'input> = { + Entry => EntryOrSpread::Entry(<>), + ".." => EntryOrSpread::Spread(name), +}; \ No newline at end of file diff --git a/src/de.rs b/src/de.rs index 8cade85..a9b2302 100644 --- a/src/de.rs +++ b/src/de.rs @@ -1,261 +1,428 @@ -use std::collections::VecDeque; - -use serde::de::{self, DeserializeSeed, EnumAccess, IntoDeserializer, VariantAccess, Visitor}; - -use crate::error::{Error, Result}; -use crate::parse; -use crate::Value; - -#[derive(Debug)] +use alloc::{ + borrow::Cow, + format, + string::{String, ToString}, + vec::Vec, +}; +use serde_core::de::{self, IntoDeserializer}; + +use crate::{ + BorrowedObject, BorrowedValue, Error, IndexMap, Result, + ast::{Entry, EntryOrSpread, Inputs, PairOrSpread, Root}, + lexer::{Lexer, StringPart}, + parser::RootParser, +}; + +/// A structure that deserializes Corn configuration values. +#[derive(Clone)] pub struct Deserializer<'de> { - value: Option>, + value: BorrowedValue<'de>, +} + +/// Parse a Corn configuration string into a borrowed value. +pub fn parse(input: &str) -> Result> { + Deserializer::parse(input) } impl<'de> Deserializer<'de> { - pub fn from_str(input: &'de str) -> Result { - let parsed = parse(input)?; + /// Parse a Corn configuration string into a borrowed value. + pub fn parse(input: &str) -> Result> { + let mut lexer = Lexer::new(input); + let parser = RootParser::new(); + let Root { inputs, object } = parser + .parse(input, &mut lexer) + .map_err(|err| Error::ParseError(err.to_string()))?; - Ok(Self::from_value(parsed)) + Self::resolve_entry(&Entry::Object(object), &inputs) } - fn from_value(value: Value<'de>) -> Self { - Self { value: Some(value) } + /// Create a deserializer from a Corn configuration string. + #[allow(clippy::should_implement_trait)] + pub fn from_str(input: &'de str) -> Result { + Self::parse(input).map(|value| Self { value }) } -} - -/// Attempts to deserialize the config from a string slice. -/// -/// # Errors -/// -/// Will return a `DeserializationError` if the config is invalid. -pub fn from_str(s: &str) -> Result -where - T: de::DeserializeOwned, -{ - let mut deserializer = Deserializer::from_str(s)?; - T::deserialize(&mut deserializer) -} -/// Attempts to deserialize the config from a byte slice. -/// -/// # Errors -/// -/// Will return a `DeserializationError` if the config is invalid. -pub fn from_slice(bytes: &[u8]) -> Result -where - T: de::DeserializeOwned, -{ - match std::str::from_utf8(bytes) { - Ok(s) => from_str(s), - Err(e) => Err(Error::DeserializationError(e.to_string())), + fn with_value(value: BorrowedValue<'de>) -> Self { + Self { value } } -} -macro_rules! get_value { - ($self:ident) => { - match $self.value.take() { - Some(val) => Ok(val), - None => Err(Error::DeserializationError(String::from( - "Deserializer value unexpectedly `None`", - ))), - }? - }; -} + fn resolve_entry<'input>( + entry: &Entry<'input>, + inputs: &Inputs<'input>, + ) -> Result> { + match entry { + Entry::String(parts) => { + if parts.is_empty() { + return Ok(BorrowedValue::String(Cow::Borrowed(""))); + } -macro_rules! err_expected { - ($expected:literal, $got:expr) => { - Err(Error::DeserializationError(format!( - "Expected {}, found '{:?}'", - $expected, $got - ))) - }; -} + let mut base = String::new(); -macro_rules! match_value { - ($self:ident, $name:literal, $($pat:pat => $expr:expr)+) => {{ - let value = get_value!($self); - match value { - $($pat => $expr, )+ - _ => err_expected!($name, value) - } - }}; -} + for part in parts { + match part { + StringPart::Literal(lit) => base.push_str(lit), + StringPart::Input(input) => { + let input = Self::resolve_input(input, inputs)?; -impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { - type Error = Error; + match input { + BorrowedValue::String(string) => base.push_str(&string), + _ => return Err(Error::InvalidInterpolationError), + } + } + } + } - fn deserialize_any(self, visitor: V) -> std::result::Result - where - V: Visitor<'de>, - { - let value = get_value!(self); - match value { - Value::Object(_) => { - let map = Map::new(value); - visitor.visit_map(map) + Ok(BorrowedValue::String(Cow::Owned( + Self::process_multiline_string(&base), + ))) } - Value::Array(_) => { - let seq = Seq::new(value); - visitor.visit_seq(seq) + Entry::Integer(integer) => Ok(BorrowedValue::Integer(*integer)), + Entry::Float(float) => Ok(BorrowedValue::Float(*float)), + Entry::Boolean(boolean) => Ok(BorrowedValue::Boolean(*boolean)), + Entry::Object(obj) => { + let mut resolved_object = IndexMap::default(); + + for pair_or_spread in &obj.pairs { + match pair_or_spread { + PairOrSpread::Pair(key, value) => { + fn unescape_key(key: &str) -> Cow<'_, str> { + if key.contains("\\'") { + Cow::Owned(key.replace("\\'", "'")) + } else { + Cow::Borrowed(key) + } + } + + let processed_segments: Vec> = key + .segments + .iter() + .map(|segment| unescape_key(segment)) + .collect(); + + resolved_object.reserve_exact(processed_segments.len()); + + Self::insert_at_path( + &mut resolved_object, + &processed_segments, + Self::resolve_entry(value, inputs)?, + )?; + } + PairOrSpread::Spread(name) => { + if let Some(spread_entry) = inputs.get(name) { + match Self::resolve_entry(spread_entry, inputs)? { + BorrowedValue::Object(spread_obj) => { + resolved_object.extend(spread_obj); + } + _ => return Err(Error::InvalidSpreadError), + } + } else { + return Err(Error::InputResolveError(name.to_string())); + } + } + } + } + + resolved_object.shrink_to_fit(); + + Ok(BorrowedValue::Object(resolved_object)) } - Value::String(val) => visitor.visit_str(&val), - Value::Integer(val) => visitor.visit_i64(val), - Value::Float(val) => visitor.visit_f64(val), - Value::Boolean(val) => visitor.visit_bool(val), - Value::Null(_) => visitor.visit_unit(), + Entry::Array(items) => { + let mut resolved_array = Vec::with_capacity(items.len()); // We need at least the same amount of items + + for entry in items { + match entry { + EntryOrSpread::Entry(entry) => { + resolved_array.push(Self::resolve_entry(entry, inputs)?) + } + EntryOrSpread::Spread(spread) => match Self::resolve_input(spread, inputs)? + { + BorrowedValue::Array(array) => { + resolved_array.extend(array); + } + _ => return Err(Error::InvalidSpreadError), + }, + } + } + + Ok(BorrowedValue::Array(resolved_array)) + } + Entry::Null => Ok(BorrowedValue::Null), + Entry::Input(input) => Self::resolve_input(input, inputs), } } - fn deserialize_bool(self, visitor: V) -> std::result::Result - where - V: Visitor<'de>, - { - match_value!(self, "boolean", Value::Boolean(val) => visitor.visit_bool(val)) - } + fn insert_at_path<'input>( + obj: &mut BorrowedObject<'input>, + path: &[Cow<'input, str>], + value: BorrowedValue<'input>, + ) -> Result<(), Error> { + if path.is_empty() { + return Err(Error::DeserializationError("Empty path".to_string())); + } - fn deserialize_i8(self, visitor: V) -> std::result::Result - where - V: Visitor<'de>, - { - match_value!(self, "integer (i8)", Value::Integer(val) => visitor.visit_i8(val as i8)) - } + if path.len() == 1 { + obj.insert(path[0].clone(), value); + return Ok(()); + } - fn deserialize_i16(self, visitor: V) -> std::result::Result - where - V: Visitor<'de>, - { - match_value!(self, "integer (i16)", Value::Integer(val) => visitor.visit_i16(val as i16)) - } + let (first, rest) = path.split_first().expect("Internal splitting error"); + let entry = obj + .entry(first.clone()) + .or_insert_with(|| BorrowedValue::Object(IndexMap::default())); - fn deserialize_i32(self, visitor: V) -> std::result::Result - where - V: Visitor<'de>, - { - match_value!(self, "integer (i32)", Value::Integer(val) => visitor.visit_i32(val as i32)) - } + match entry { + BorrowedValue::Object(nested_obj) => { + Self::insert_at_path(nested_obj, rest, value)?; + } + _ => { + return Err(Error::DeserializationError(format!( + "Cannot index into non-object at key: {first}" + ))); + } + } - fn deserialize_i64(self, visitor: V) -> std::result::Result - where - V: Visitor<'de>, - { - match_value!(self, "integer (i64)", Value::Integer(val) => visitor.visit_i64(val)) + Ok(()) } - fn deserialize_u8(self, visitor: V) -> std::result::Result - where - V: Visitor<'de>, - { - match_value!(self, "integer (u8)", Value::Integer(val) => visitor.visit_u8(val as u8)) + fn process_multiline_string(input: &str) -> String { + if !input.starts_with('\n') { + return input.to_string(); + } + + let lines: Vec<&str> = input.lines().collect(); + if lines.len() < 3 { + // Need at least: empty, content, empty/content + return input.to_string(); + } + + // Skip first empty line and handle last line (may be empty or just whitespace) + let mut content_lines: Vec<&str> = lines.iter().skip(1).copied().collect(); + + // Remove trailing lines that are empty or only whitespace + while let Some(&last) = content_lines.last() { + if last.trim().is_empty() { + content_lines.pop(); + } else { + break; + } + } + + if content_lines.is_empty() { + return String::new(); + } + + // Find minimum indentation of non-empty lines + let min_indent = content_lines + .iter() + .filter(|line| !line.trim().is_empty()) + .map(|line| line.len() - line.trim_start().len()) + .min() + .unwrap_or(0); + + // Remove minimum indentation and join with newlines + let result_lines: Vec<&str> = content_lines + .iter() + .map(|line| { + if line.trim().is_empty() { + "" + } else if line.len() >= min_indent { + &line[min_indent..] + } else { + line + } + }) + .collect(); + + let mut result = result_lines.join("\n"); + + if !result.is_empty() { + result.push('\n'); + } + + result } - fn deserialize_u16(self, visitor: V) -> std::result::Result - where - V: Visitor<'de>, - { - match_value!(self, "integer (u16)", Value::Integer(val) => visitor.visit_u16(val as u16)) + fn resolve_input<'input>( + input: &str, + inputs: &Inputs<'input>, + ) -> Result> { + #[cfg(feature = "std")] + if let Some(env) = input.strip_prefix("env_") + && let Ok(env) = std::env::var(env) + { + return Ok(BorrowedValue::String(Cow::Owned(env))); + } + + if let Some(entry) = inputs.get(input) { + return Self::resolve_entry(entry, inputs); + } + + Err(Error::InputResolveError(input.to_string())) } +} + +/// Deserialize a Corn configuration string into a Rust data structure. +pub fn from_str<'a, T>(s: &'a str) -> Result +where + T: de::Deserialize<'a>, +{ + let mut deserializer = Deserializer::from_str(s)?; - fn deserialize_u32(self, visitor: V) -> std::result::Result + T::deserialize(&mut deserializer) +} + +macro_rules! deserialize_number { + ($method:ident) => { + fn $method(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + match self.value { + BorrowedValue::Integer(integer) => integer.deserialize_any(visitor), + ref value => Err(value.invalid_type("Integer")), + } + } + }; +} + +impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { + type Error = Error; + + fn deserialize_any(self, visitor: V) -> Result where - V: Visitor<'de>, + V: de::Visitor<'de>, { - match_value!(self, "integer (u32)", Value::Integer(val) => visitor.visit_u32(val as u32)) + match self.value { + BorrowedValue::String(ref string) => match string { + Cow::Borrowed(s) => visitor.visit_borrowed_str(s), + Cow::Owned(s) => visitor.visit_str(s), + }, + BorrowedValue::Integer(integer) => integer.deserialize_any(visitor), + BorrowedValue::Float(float) => visitor.visit_f64(float), + BorrowedValue::Boolean(boolean) => visitor.visit_bool(boolean), + BorrowedValue::Null => visitor.visit_unit(), + BorrowedValue::Array(ref mut items) => { + let mut seq = Vec::new(); + core::mem::swap(items, &mut seq); + + visitor.visit_seq(SeqAccess::new(seq)) + } + BorrowedValue::Object(ref mut object) => { + let mut map = IndexMap::default(); + core::mem::swap(object, &mut map); + + visitor.visit_map(MapAccess::new(map)) + } + } } - fn deserialize_u64(self, visitor: V) -> std::result::Result + fn deserialize_bool(self, visitor: V) -> Result where - V: Visitor<'de>, + V: de::Visitor<'de>, { - match_value!(self, "integer (u64)", Value::Integer(val) => visitor.visit_u64(val as u64)) + match self.value { + BorrowedValue::Boolean(boolean) => visitor.visit_bool(boolean), + ref value => Err(value.invalid_type("Boolean")), + } } - fn deserialize_f32(self, visitor: V) -> std::result::Result + deserialize_number!(deserialize_i8); + deserialize_number!(deserialize_i16); + deserialize_number!(deserialize_i32); + deserialize_number!(deserialize_i64); + deserialize_number!(deserialize_u8); + deserialize_number!(deserialize_u16); + deserialize_number!(deserialize_u32); + deserialize_number!(deserialize_u64); + + fn deserialize_f32(self, visitor: V) -> Result where - V: Visitor<'de>, + V: de::Visitor<'de>, { - match_value!(self, "float (f32)", Value::Float(val) => visitor.visit_f32(val as f32)) + self.deserialize_f64(visitor) } - fn deserialize_f64(self, visitor: V) -> std::result::Result + fn deserialize_f64(self, visitor: V) -> Result where - V: Visitor<'de>, + V: de::Visitor<'de>, { - match_value!(self, "float (f64)", Value::Float(val) => visitor.visit_f64(val)) + match self.value { + BorrowedValue::Float(float) => visitor.visit_f64(float), + ref value => Err(value.invalid_type("Float")), + } } - fn deserialize_char(self, visitor: V) -> std::result::Result + fn deserialize_char(self, visitor: V) -> Result where - V: Visitor<'de>, + V: de::Visitor<'de>, { - let value = get_value!(self); - let char = match value { - Value::String(value) => value.chars().next(), - _ => return err_expected!("char", value), - }; - - match char { - Some(char) => visitor.visit_char(char), - None => err_expected!("char", "empty string"), - } + self.deserialize_str(visitor) } - fn deserialize_str(self, visitor: V) -> std::result::Result + fn deserialize_str(self, visitor: V) -> Result where - V: Visitor<'de>, + V: de::Visitor<'de>, { - match_value!(self, "string", - Value::String(val) => visitor.visit_str(&val) - ) + match self.value { + BorrowedValue::String(ref string) => match string { + Cow::Borrowed(s) => visitor.visit_borrowed_str(s), + Cow::Owned(s) => visitor.visit_str(s), + }, + ref value => Err(value.invalid_type("String")), + } } - fn deserialize_string(self, visitor: V) -> std::result::Result + fn deserialize_string(self, visitor: V) -> Result where - V: Visitor<'de>, + V: de::Visitor<'de>, { self.deserialize_str(visitor) } - fn deserialize_bytes(self, visitor: V) -> std::result::Result + fn deserialize_bytes(self, visitor: V) -> Result where - V: Visitor<'de>, + V: de::Visitor<'de>, { - match_value!(self, "bytes array", - Value::String(val) => visitor.visit_bytes(val.as_bytes()) - ) + match self.value { + BorrowedValue::String(ref string) => visitor.visit_bytes(string.as_bytes()), + ref value => Err(value.invalid_type("Byte String")), + } } - fn deserialize_byte_buf(self, visitor: V) -> std::result::Result + fn deserialize_byte_buf(self, visitor: V) -> Result where - V: Visitor<'de>, + V: de::Visitor<'de>, { self.deserialize_bytes(visitor) } - fn deserialize_option(self, visitor: V) -> std::result::Result + fn deserialize_option(self, visitor: V) -> Result where - V: Visitor<'de>, + V: de::Visitor<'de>, { - let value = get_value!(self); - match value { - Value::Null(_) => visitor.visit_none(), - _ => visitor.visit_some(&mut Deserializer::from_value(value)), + match self.value { + BorrowedValue::Null => visitor.visit_none(), + _ => visitor.visit_some(self), } } - fn deserialize_unit(self, visitor: V) -> std::result::Result + fn deserialize_unit(self, visitor: V) -> Result where - V: Visitor<'de>, + V: de::Visitor<'de>, { - visitor.visit_unit() + match self.value { + BorrowedValue::Null => visitor.visit_unit(), + ref value => Err(value.invalid_type("Null")), + } } fn deserialize_unit_struct( self, _name: &'static str, visitor: V, - ) -> std::result::Result + ) -> Result where - V: Visitor<'de>, + V: de::Visitor<'de>, { self.deserialize_unit(visitor) } @@ -264,31 +431,31 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { self, _name: &'static str, visitor: V, - ) -> std::result::Result + ) -> Result where - V: Visitor<'de>, + V: de::Visitor<'de>, { visitor.visit_newtype_struct(self) } - fn deserialize_seq(self, visitor: V) -> std::result::Result + fn deserialize_seq(self, visitor: V) -> Result where - V: Visitor<'de>, + V: de::Visitor<'de>, { - let value = get_value!(self); - match value { - Value::Array(_) => visitor.visit_seq(Seq::new(value)), - _ => err_expected!("array", value), + match self.value { + BorrowedValue::Array(ref mut items) => { + let mut seq = Vec::new(); + core::mem::swap(items, &mut seq); + + visitor.visit_seq(SeqAccess::new(seq)) + } + ref value => Err(value.invalid_type("Array")), } } - fn deserialize_tuple( - self, - _len: usize, - visitor: V, - ) -> std::result::Result + fn deserialize_tuple(self, _len: usize, visitor: V) -> Result where - V: Visitor<'de>, + V: de::Visitor<'de>, { self.deserialize_seq(visitor) } @@ -298,21 +465,25 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { _name: &'static str, _len: usize, visitor: V, - ) -> std::result::Result + ) -> Result where - V: Visitor<'de>, + V: de::Visitor<'de>, { self.deserialize_seq(visitor) } - fn deserialize_map(self, visitor: V) -> std::result::Result + fn deserialize_map(self, visitor: V) -> Result where - V: Visitor<'de>, + V: de::Visitor<'de>, { - let value = get_value!(self); - match value { - Value::Object(_) => visitor.visit_map(Map::new(value)), - _ => err_expected!("object", value), + match self.value { + BorrowedValue::Object(ref mut object) => { + let mut map = IndexMap::default(); + core::mem::swap(object, &mut map); + + visitor.visit_map(MapAccess::new(map)) + } + ref value => Err(value.invalid_type("Object")), } } @@ -321,9 +492,9 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { _name: &'static str, _fields: &'static [&'static str], visitor: V, - ) -> std::result::Result + ) -> Result where - V: Visitor<'de>, + V: de::Visitor<'de>, { self.deserialize_map(visitor) } @@ -333,197 +504,193 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { _name: &'static str, _variants: &'static [&'static str], visitor: V, - ) -> std::result::Result + ) -> Result where - V: Visitor<'de>, + V: de::Visitor<'de>, { - let value = get_value!(self); - match value { - Value::Object(_) => visitor.visit_enum(Enum::new(value)), - Value::String(val) => visitor.visit_enum(val.into_deserializer()), - _ => err_expected!("object or string (enum variant)", value), + match self.value { + BorrowedValue::String(ref string) => { + visitor.visit_enum(string.as_ref().into_deserializer()) + } + BorrowedValue::Object(ref mut object) => { + let mut map = IndexMap::default(); + core::mem::swap(object, &mut map); + + visitor.visit_enum(EnumAccess::new(map)) + } + ref value => Err(value.invalid_type("String or Object")), } } - fn deserialize_identifier(self, visitor: V) -> std::result::Result + fn deserialize_identifier(self, visitor: V) -> Result where - V: Visitor<'de>, + V: de::Visitor<'de>, { self.deserialize_str(visitor) } - fn deserialize_ignored_any(self, visitor: V) -> std::result::Result + fn deserialize_ignored_any(self, visitor: V) -> Result where - V: Visitor<'de>, + V: de::Visitor<'de>, { self.deserialize_any(visitor) } } -struct Map<'de> { - values: VecDeque>, +struct SeqAccess<'de> { + items: alloc::vec::IntoIter>, } -impl<'de> Map<'de> { - fn new(value: Value<'de>) -> Self { - match value { - Value::Object(values) => Self { - values: values - .into_iter() - .flat_map(|(key, value)| vec![Value::String(key), value]) - .collect(), - }, - _ => unreachable!(), +impl<'de> SeqAccess<'de> { + pub fn new(items: Vec>) -> Self { + Self { + items: items.into_iter(), } } } -impl<'de> de::MapAccess<'de> for Map<'de> { +impl<'de> de::SeqAccess<'de> for SeqAccess<'de> { type Error = Error; - fn next_key_seed(&mut self, seed: K) -> std::result::Result, Self::Error> + fn next_element_seed(&mut self, seed: T) -> Result, Self::Error> where - K: DeserializeSeed<'de>, + T: de::DeserializeSeed<'de>, { - if let Some(value) = self.values.pop_front() { - seed.deserialize(&mut Deserializer::from_value(value)) - .map(Some) - } else { - Ok(None) - } - } - - fn next_value_seed(&mut self, seed: V) -> std::result::Result - where - V: DeserializeSeed<'de>, - { - match self.values.pop_front() { - Some(value) => seed.deserialize(&mut Deserializer::from_value(value)), - None => Err(Error::DeserializationError( - "Expected value to exist".to_string(), - )), + match self.items.next() { + Some(item) => { + let mut deserializer = Deserializer::with_value(item); + seed.deserialize(&mut deserializer).map(Some) + } + None => Ok(None), } } - - fn size_hint(&self) -> Option { - Some(self.values.len() / 2) - } } -struct Seq<'de> { - values: VecDeque>, +struct MapAccess<'de> { + items: indexmap::map::IntoIter, BorrowedValue<'de>>, + current_value: Option>, } -impl<'de> Seq<'de> { - fn new(value: Value<'de>) -> Self { - match value { - Value::Array(values) => Self { - values: VecDeque::from(values), - }, - _ => unreachable!(), +impl<'de> MapAccess<'de> { + fn new(items: IndexMap, BorrowedValue<'de>>) -> Self { + Self { + items: items.into_iter(), + current_value: None, } } } -impl<'de> de::SeqAccess<'de> for Seq<'de> { +impl<'de> de::MapAccess<'de> for MapAccess<'de> { type Error = Error; - fn next_element_seed( - &mut self, - seed: T, - ) -> std::result::Result, Self::Error> + fn next_key_seed(&mut self, seed: K) -> Result, Self::Error> where - T: DeserializeSeed<'de>, + K: de::DeserializeSeed<'de>, { - if let Some(value) = self.values.pop_front() { - seed.deserialize(&mut Deserializer::from_value(value)) - .map(Some) - } else { - Ok(None) + match self.items.next() { + Some((key, value)) => { + self.current_value = Some(value); + let mut key_deserializer = Deserializer::with_value(BorrowedValue::String(key)); + seed.deserialize(&mut key_deserializer).map(Some) + } + None => Ok(None), } } - fn size_hint(&self) -> Option { - Some(self.values.len()) + fn next_value_seed(&mut self, seed: V) -> Result + where + V: de::DeserializeSeed<'de>, + { + match self.current_value.take() { + Some(value) => { + let mut deserializer = Deserializer::with_value(value); + seed.deserialize(&mut deserializer) + } + None => Err(Error::DeserializationError( + "No value available".to_string(), + )), + } } } - -struct Enum<'de> { - value: Value<'de>, +struct EnumAccess<'de> { + object: BorrowedObject<'de>, } -impl<'de> Enum<'de> { - fn new(value: Value<'de>) -> Self { - Self { value } +impl<'de> EnumAccess<'de> { + fn new(object: BorrowedObject<'de>) -> Self { + Self { object } } } -impl<'de> EnumAccess<'de> for Enum<'de> { +impl<'de> de::EnumAccess<'de> for EnumAccess<'de> { type Error = Error; - type Variant = Variant<'de>; + type Variant = VariantAccess<'de>; - fn variant_seed(self, seed: V) -> std::result::Result<(V::Value, Self::Variant), Self::Error> + fn variant_seed( + self, + seed: V, + ) -> core::result::Result<(V::Value, Self::Variant), Self::Error> where - V: DeserializeSeed<'de>, + V: de::DeserializeSeed<'de>, { - match self.value { - Value::String(_) => { - let value = seed.deserialize(&mut Deserializer::from_value(self.value))?; - Ok((value, Variant::new(None))) - } - Value::Object(obj) => { - let first_pair = obj.into_iter().next(); - if let Some(first_pair) = first_pair { - let value = Value::String(first_pair.0); - let tag = seed.deserialize(&mut Deserializer::from_value(value))?; - Ok((tag, Variant::new(Some(first_pair.1)))) - } else { - Err(Error::DeserializationError( - "Cannot deserialize empty object into enum".to_string(), - )) - } - } - _ => unreachable!(), + if self.object.len() != 1 { + return Err(Error::DeserializationError(format!( + "Expected enum object with exactly one key, found {}", + self.object.len() + ))); } + + let (key, value) = self + .object + .into_iter() + .next() + .expect("Internal variant error"); + + let mut key_deserializer = Deserializer::with_value(BorrowedValue::String(key)); + let variant = seed.deserialize(&mut key_deserializer)?; + + Ok((variant, VariantAccess::new(value))) } } -struct Variant<'de> { - value: Option>, +struct VariantAccess<'de> { + value: BorrowedValue<'de>, } -impl<'de> Variant<'de> { - fn new(value: Option>) -> Self { +impl<'de> VariantAccess<'de> { + fn new(value: BorrowedValue<'de>) -> Self { Self { value } } } -impl<'de> VariantAccess<'de> for Variant<'de> { +impl<'de> de::VariantAccess<'de> for VariantAccess<'de> { type Error = Error; - fn unit_variant(self) -> std::result::Result<(), Self::Error> { - Ok(()) + fn unit_variant(self) -> core::result::Result<(), Self::Error> { + match self.value { + BorrowedValue::Null => Ok(()), + ref value => Err(value.invalid_type("unit variant (null)")), + } } - fn newtype_variant_seed(self, seed: T) -> std::result::Result + fn newtype_variant_seed(self, seed: T) -> core::result::Result where - T: DeserializeSeed<'de>, + T: de::DeserializeSeed<'de>, { - match self.value { - Some(value) => seed.deserialize(&mut Deserializer::from_value(value)), - None => Err(Error::DeserializationError( - "Expected value to exist".to_string(), - )), - } + seed.deserialize(&mut Deserializer::with_value(self.value)) } - fn tuple_variant(self, _len: usize, visitor: V) -> std::result::Result + fn tuple_variant( + self, + _len: usize, + visitor: V, + ) -> core::result::Result where - V: Visitor<'de>, + V: de::Visitor<'de>, { match self.value { - Some(value) if matches!(value, Value::Array(_)) => visitor.visit_seq(Seq::new(value)), - _ => unreachable!(), + BorrowedValue::Array(items) => visitor.visit_seq(SeqAccess::new(items)), + ref value => Err(value.invalid_type("tuple variant (array)")), } } @@ -531,13 +698,13 @@ impl<'de> VariantAccess<'de> for Variant<'de> { self, _fields: &'static [&'static str], visitor: V, - ) -> std::result::Result + ) -> core::result::Result where - V: Visitor<'de>, + V: de::Visitor<'de>, { match self.value { - Some(value) if matches!(value, Value::Object(_)) => visitor.visit_map(Map::new(value)), - _ => unreachable!(), + BorrowedValue::Object(object) => visitor.visit_map(MapAccess::new(object)), + ref value => Err(value.invalid_type("struct variant (object)")), } } } diff --git a/src/error.rs b/src/error.rs index cf0e631..3a10fdc 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,35 +1,40 @@ -use std::fmt::{Debug, Display}; -use thiserror::Error; +use alloc::string::{String, ToString}; +use core::fmt::{Display, Formatter}; -use crate::Rule; +pub type Result = core::result::Result; -pub type Result = std::result::Result; - -#[derive(Error, Debug)] +#[derive(Debug)] pub enum Error { - #[error(transparent)] - Io(#[from] std::io::Error), - - #[error(transparent)] - ParserError(#[from] Box>), - - #[error("failed to resolve referenced input `{0}`")] InputResolveError(String), - - #[error("attempted to use dot-notation on non-object value at `{0}`")] - InvalidPathError(String), - - #[error("attempted to spread a type that differs from its containing type at `{0}`")] - InvalidSpreadError(String), - - #[error("attempted to interpolate a non-string type into a string at `{0}`")] - InvalidInterpolationError(String), - - #[error("failed to deserialize input: {0}")] + InvalidSpreadError, + InvalidInterpolationError, DeserializationError(String), + ParseError(String), } -impl serde::de::Error for Error { +impl Display for Error { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + match self { + Self::InputResolveError(input) => { + write!(f, "failed to resolve referenced input `{input}`") + } + Self::InvalidSpreadError => write!( + f, + "attempted to spread a type that differs from its containing type" + ), + Self::InvalidInterpolationError => write!( + f, + "attempted to interpolate a non-string type into a string" + ), + Self::DeserializationError(msg) => write!(f, "failed to deserialize input: {msg}"), + Self::ParseError(msg) => write!(f, "failed to parse input: {msg}"), + } + } +} + +impl core::error::Error for Error {} + +impl serde_core::de::Error for Error { fn custom(msg: T) -> Self where T: Display, diff --git a/src/grammar.pest b/src/grammar.pest deleted file mode 100644 index 2cbe2c4..0000000 --- a/src/grammar.pest +++ /dev/null @@ -1,93 +0,0 @@ -WHITESPACE = _{ " " | "\t" | "\r" | "\n" } -COMMENT = _{ "//" ~ (!"\n" ~ ANY)* } - -object = { - "{" - ~ object_value* - ~ "}" -} - -object_value = _{ - pair | spread -} - -spread = { - ".." ~ input -} - -array = { - "[" - ~ array_value* - ~ "]" -} - -array_value = _{ - value | spread -} - -pair = { path ~ "=" ~ value } - -path = ${ - path_seg - ~ ( "." ~ path_seg )* -} - -path_seg = _{ - quoted_path_seg | regular_path_seg -} - -quoted_path_seg = ${ "'" ~ quoted_path_val ~ "'" } -quoted_path_val = ${ quoted_path_char + } -quoted_path_char = _{ - !("'" | "\\") ~ ANY - | "\\" ~ "'" -} - -regular_path_seg = ${ path_char + } - -path_char = _{ !( WHITESPACE | "=" | "." ) ~ ANY } - -value = _{ object | array | input | string | float | integer | boolean | null } - -boolean = { "true" | "false" } -null = { "null" } - -string = ${ - "\"" ~ string_val ~ "\"" -} - -string_val = ${ (input | char)* } - -char = { - !("\"" | "\\") ~ ANY - | "\\" ~ ("\"" | "\\" | "n" | "r" | "t" | "$") - | "\\" ~ ("u" ~ ASCII_HEX_DIGIT{4}) -} - -integer = ${ - hex_integer | decimal_integer -} - -decimal_integer = @{ - "-"? - ~ ("0" | ASCII_NONZERO_DIGIT ~ ("_"? ~ ASCII_DIGIT)*) -} - -hex_integer = @{ - "0x" ~ ASCII_HEX_DIGIT+ -} - -float = @{ - "-"? - ~ ("0" | ASCII_NONZERO_DIGIT ~ ASCII_DIGIT*) - ~ ("." ~ ASCII_DIGIT*) - ~ (^"e" ~ ("+" | "-")? ~ ASCII_DIGIT+)? -} - -input = ${ !"\\" ~ "$" ~ (ASCII_ALPHA | "_") ~ (ASCII_ALPHANUMERIC | "_")+ } - -assignment = { input ~ "=" ~ value } - -assign_block = { "let" ~ "{" ~ assignment* ~ "}" ~ "in" } - -config = _{ SOI ~ assign_block? ~ object ~ EOI } diff --git a/src/lexer.rs b/src/lexer.rs new file mode 100644 index 0000000..a17d853 --- /dev/null +++ b/src/lexer.rs @@ -0,0 +1,295 @@ +use alloc::{borrow::Cow, string::String, vec::Vec}; +use core::{ + fmt::{Display, Formatter}, + num::ParseIntError, + str::FromStr, +}; +use logos::{Logos, SpannedIter}; + +use crate::Integer; + +pub type Spanned = Result<(Loc, Tok, Loc), Error>; + +pub struct Lexer<'input> { + // instead of an iterator over characters, we have a token iterator + token_stream: SpannedIter<'input, Token<'input>>, +} + +impl<'input> Lexer<'input> { + pub fn new(input: &'input str) -> Self { + Self { + token_stream: Token::lexer(input).spanned(), + } + } +} + +impl<'input> Iterator for Lexer<'input> { + type Item = Spanned, usize, LexicalError>; + + fn next(&mut self) -> Option { + self.token_stream + .next() + .map(|(token, span)| Ok((span.start, token?, span.end))) + } +} + +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub enum LexicalError { + InvalidInteger(ParseIntError), + InvalidFloat(core::num::ParseFloatError), + #[default] + InvalidToken, +} + +impl Display for LexicalError { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + match self { + Self::InvalidInteger(err) => write!(f, "Integer parsing error: {err}"), + Self::InvalidFloat(err) => write!(f, "Float parsing error: {err}"), + Self::InvalidToken => write!(f, "Encountered invalid token"), + } + } +} + +impl core::error::Error for LexicalError {} + +impl From for LexicalError { + fn from(err: ParseIntError) -> Self { + Self::InvalidInteger(err) + } +} + +impl From for LexicalError { + fn from(err: core::num::ParseFloatError) -> Self { + Self::InvalidFloat(err) + } +} + +#[derive(Logos, Debug, Clone, PartialEq)] +#[logos(error = LexicalError)] +#[logos(skip r"[\s\t\r\n\f]+")] // Whitespace +#[logos(skip r"//[^\n\r]*[\n\r]*")] // Inline comments +#[logos(skip r"/\*([^*/]|\*[^/]|/[^*])*\*/")] // Multiline comments +pub enum Token<'input> { + #[token("let")] + Let, + + #[token("in")] + In, + + #[token("null")] + Null, + + #[token("=")] + Equals, + + #[token("{")] + OpenBrace, + + #[token("}")] + CloseBrace, + + #[token("[")] + OpenBracket, + + #[token("]")] + CloseBracket, + + #[token(".")] + Chain, + + #[token("..")] + Spread, + + #[token("false", |_| false)] + #[token("true", |_| true)] + Boolean(bool), + + #[regex(r"-?0x[0-9a-fA-F]+(_[0-9a-fA-F]+)*", |lex| parse_radix_integer(lex, 16))] + #[regex(r"-?0o[0-7]+(_[0-7]+)*", |lex| parse_radix_integer(lex, 8))] + #[regex(r"-?0b[01]+(_[01]+)*", |lex| parse_radix_integer(lex, 2))] + #[regex(r"-[0-9]+(_[0-9]+)*", parse_decimal::)] + #[regex(r"[0-9]+(_[0-9]+)*", parse_decimal::)] + Integer(Integer), + + #[regex(r"-?[0-9]+\.[0-9]*([eE][+-]?[0-9]+)?", |lex| lex.slice().parse::())] + Float(f64), + + #[regex(r"\$[a-zA-Z_][a-zA-Z0-9_]*", |lex| lex.slice().trim_start_matches('$'))] + InputName(&'input str), + + #[token("\"", parse_literal)] + Literal(Vec>), + + #[regex(r#"'(?:[^'\\]|\\.)*'|[^\s.=0-9\[\]{}"'-][^\s.=\[\]{}"']*"#, |lex| lex.slice().trim_matches('\''))] + Key(&'input str), +} + +/// Parse normal decimal integer, removes underscores +fn parse_decimal<'input, N>( + lex: &mut logos::Lexer<'input, Token<'input>>, +) -> Result +where + N: Into + FromStr, +{ + lex.slice().replace("_", "").parse().map(N::into) +} + +/// Parse integer with specified radix, handling negative values and underscores +fn parse_radix_integer<'input>( + lex: &mut logos::Lexer<'input, Token<'input>>, + radix: u32, +) -> Result { + let input = lex.slice().replace("_", ""); + let is_negative = input.starts_with('-'); + let prefix_len = if is_negative { 3 } else { 2 }; // Skip "-0x"/"0x" etc. + let number_part = &input[prefix_len..]; + + if is_negative { + i64::from_str_radix(number_part, radix).map(|n| Integer::from(-n)) + } else { + u64::from_str_radix(number_part, radix).map(Integer::from) + } +} + +#[derive(Logos, Debug, PartialEq, Clone)] +enum StringContext<'input> { + #[token("\"")] + Quote, + #[regex(r#"[^\"$\\{]+"#)] + Content, + + #[token("\\n")] + NewlineEscape, + #[token("\\r")] + CarriageReturnEscape, + #[token("\\t")] + TabEscape, + #[token("\\\\")] + BackslashEscape, + #[token("\\\"")] + QuoteEscape, + #[token("\\$")] + DollarEscape, + #[token("\\{")] + OpenBraceEscape, + #[token("\\}")] + CloseBraceEscape, + #[regex(r"\\u\{[0-9a-fA-F]{4,6}\}")] + UnicodeEscape, + + #[regex(r"\$\{[a-zA-Z_][a-zA-Z0-9_]*\}", |lex| lex.slice())] + Interpolation(&'input str), +} + +#[derive(Debug, Clone, PartialEq)] +pub enum StringPart<'input> { + Literal(Cow<'input, str>), + Input(&'input str), +} + +fn parse_literal<'input>( + lex: &mut logos::Lexer<'input, Token<'input>>, +) -> Option>> { + let mut string_lex = lex.clone().morph::(); + + let mut parts = Vec::new(); + let mut current_literal = String::new(); + + while let Some(Ok(token)) = string_lex.next() { + match token { + StringContext::Quote => break, + StringContext::Content => current_literal.push_str(string_lex.slice()), + StringContext::Interpolation(input) => { + if !current_literal.is_empty() { + parts.push(StringPart::Literal(Cow::Owned(core::mem::take( + &mut current_literal, + )))); + } + + parts.push(StringPart::Input( + input.trim_start_matches("${").trim_end_matches('}'), + )) + } + StringContext::NewlineEscape => { + current_literal.push('\n'); + } + StringContext::CarriageReturnEscape => { + current_literal.push('\r'); + } + StringContext::TabEscape => { + current_literal.push('\t'); + } + StringContext::BackslashEscape => { + current_literal.push('\\'); + } + StringContext::QuoteEscape => { + current_literal.push('"'); + } + StringContext::DollarEscape => { + current_literal.push('$'); + } + StringContext::OpenBraceEscape => { + current_literal.push('{'); + } + StringContext::CloseBraceEscape => { + current_literal.push('}'); + } + StringContext::UnicodeEscape => { + let slice = string_lex.slice(); + let hex_part = &slice[3..slice.len() - 1]; + + if let Ok(code) = u32::from_str_radix(hex_part, 16) + && let Some(unicode_char) = char::from_u32(code) + { + current_literal.push(unicode_char); + continue; + } + + current_literal.push('\u{FFFD}'); + } + } + } + + if !current_literal.is_empty() { + parts.push(StringPart::Literal(Cow::Owned(core::mem::take( + &mut current_literal, + )))); + } + + *lex = string_lex.morph(); + + Some(parts) +} + +impl core::fmt::Display for Token<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Let => write!(f, "let"), + Self::In => write!(f, "in"), + Self::Null => write!(f, "null"), + Self::Equals => write!(f, "="), + Self::OpenBrace => write!(f, "{{"), + Self::CloseBrace => write!(f, "}}"), + Self::OpenBracket => write!(f, "["), + Self::CloseBracket => write!(f, "]"), + Self::Chain => write!(f, "."), + Self::Spread => write!(f, ".."), + Self::Literal(parts) => { + for part in parts { + match part { + StringPart::Literal(lit) => write!(f, "{lit}")?, + StringPart::Input(input) => write!(f, "{input}")?, + } + } + + Ok(()) + } + Self::Integer(int) => int.fmt(f), + Self::Float(float) => float.fmt(f), + Self::Boolean(bool) => bool.fmt(f), + Self::InputName(name) => name.fmt(f), + Self::Key(key) => key.fmt(f), + } + } +} diff --git a/src/lib.rs b/src/lib.rs index f06dfb9..163f8c5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,14 +1,11 @@ -use indexmap::IndexMap; -use serde::Serialize; -use std::borrow::Cow; -use std::collections::HashMap; -use std::fmt::{Display, Formatter}; +#![cfg_attr(not(feature = "std"), no_std)] -pub use crate::de::{from_slice, from_str}; -pub use crate::parser::{parse, Rule}; +extern crate alloc; -pub mod error; -mod parser; +#[cfg(feature = "std")] +pub(crate) use indexmap::IndexMap; +#[cfg(not(feature = "std"))] +pub(crate) type IndexMap = indexmap::IndexMap; mod de; #[cfg(any( @@ -23,50 +20,13 @@ mod lua; #[cfg(feature = "wasm")] mod wasm; -/// A map of input names and values. -/// The names include their `$` prefix. -pub type Inputs<'a> = HashMap<&'a str, Value<'a>>; +mod error; +pub(crate) mod value; -/// A map of keys to their values. -pub type Object<'a> = IndexMap, Value<'a>>; +pub mod ast; +pub mod lexer; +lalrpop_util::lalrpop_mod!(pub parser, "/corn.rs"); -#[derive(Serialize, Debug, Clone)] -#[serde(untagged)] -pub enum Value<'a> { - /// Key/value map. Values can be mixed types. - Object(Object<'a>), - /// Array of values, can be mixed types. - Array(Vec>), - /// UTF-8 string - String(Cow<'a, str>), - /// 64-bit signed integer. - Integer(i64), - /// 64-bit (double precision) floating point number. - Float(f64), - /// true or false - Boolean(bool), - /// `null` literal. - /// - /// Takes an optional unit type as the `toml` crate - /// errors when encountering unit types, - /// but can handle `None` types. - Null(Option<()>), -} - -impl Display for Value<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}", - match self { - Value::Object(_) => "object", - Value::Array(_) => "array", - Value::String(_) => "string", - Value::Integer(_) => "integer", - Value::Float(_) => "float", - Value::Boolean(_) => "boolean", - Value::Null(_) => "null", - } - ) - } -} +pub use de::{Deserializer, from_str, parse}; +pub use error::{Error, Result}; +pub use value::{BorrowedObject, BorrowedValue, Integer, Object, Value}; diff --git a/src/lua.rs b/src/lua.rs index 539b992..c6ae350 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -1,14 +1,14 @@ use crate::Value; use mlua::prelude::*; -impl IntoLua for Value<'_> { +impl IntoLua for Value { fn into_lua(self, lua: &Lua) -> LuaResult { lua.to_value(&self) } } fn lua_parse(lua: &Lua, config: String) -> LuaResult { - let res = crate::parse(&config); + let res = crate::from_str::(&config); match res { Ok(v) => Ok(lua.to_value(&v)?), Err(e) => Err(LuaError::RuntimeError(e.to_string())), diff --git a/src/parser.rs b/src/parser.rs deleted file mode 100644 index 248743f..0000000 --- a/src/parser.rs +++ /dev/null @@ -1,425 +0,0 @@ -use indexmap::IndexMap; -use std::borrow::Cow; -use std::collections::HashMap; -use std::env::var; -use std::fmt::Formatter; - -use pest::iterators::Pair; -use pest::Parser; - -use crate::error::{Error, Result}; -use crate::{Inputs, Object, Value}; - -#[derive(pest_derive::Parser)] -#[grammar = "grammar.pest"] -pub struct AstParser; - -impl std::fmt::Display for Rule { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{self:?}") - } -} - -struct CornParser<'a> { - input_block: Option>, - inputs: Inputs<'a>, -} - -impl<'a> CornParser<'a> { - pub fn new(input_block: Option>) -> Self { - let inputs = HashMap::new(); - Self { - input_block, - inputs, - } - } - - pub fn parse(mut self, object_block: Pair<'a, Rule>) -> Result> { - if let Some(input_block) = self.input_block.take() { - self.parse_assign_block(input_block)?; - } - - let value_block = self.parse_object(object_block)?; - Ok(Value::Object(value_block)) - } - - /// Parses a pair of tokens (marked as a `Rule`) into a `Value`. - fn parse_value(&self, pair: Pair<'a, Rule>) -> Result> { - match pair.as_rule() { - Rule::object => Ok(Value::Object(self.parse_object(pair)?)), - Rule::array => Ok(Value::Array(self.parse_array(pair)?)), - Rule::string => Ok(Value::String(self.parse_string(pair)?)), - Rule::integer => Ok(Value::Integer(Self::parse_integer(pair))), - Rule::float => Ok(Value::Float(Self::parse_float(&pair))), - Rule::boolean => Ok(Value::Boolean(Self::parse_bool(&pair))), - Rule::null => Ok(Value::Null(None)), - Rule::input => { - let key = pair.as_str(); - self.get_input(key) - } - _ => unreachable!(), - } - } - - fn parse_bool(pair: &Pair<'_, Rule>) -> bool { - assert_eq!(pair.as_rule(), Rule::boolean); - match pair.as_str() { - "true" => true, - "false" => false, - _ => unreachable!(), - } - } - - fn parse_integer(pair: Pair<'_, Rule>) -> i64 { - assert_eq!(pair.as_rule(), Rule::integer); - let sub_pair = pair - .into_inner() - .next() - .expect("integers should contain a sub-rule of their type"); - - match sub_pair.as_rule() { - Rule::decimal_integer => sub_pair - .as_str() - .replace('_', "") - .parse() - .expect("decimal integer rules should match valid rust integers"), - Rule::hex_integer => i64::from_str_radix(&sub_pair.as_str()[2..], 16) - .expect("hex integer rules contain valid hex values"), - _ => unreachable!(), - } - } - - fn parse_float(pair: &Pair<'_, Rule>) -> f64 { - assert_eq!(pair.as_rule(), Rule::float); - pair.as_str() - .parse() - .expect("float rules should match valid rust floats") - } - - /// Collects each `char` in a `Rule::string` - /// to form a single `String`. - fn parse_string(&self, pair: Pair<'a, Rule>) -> Result> { - assert_eq!(pair.as_rule(), Rule::string); - - let mut full_string = String::new(); - - let pairs = pair - .into_inner() - .next() - .expect("string rules should contain a valid string value") - .into_inner(); - - for pair in pairs { - match pair.as_rule() { - Rule::char => full_string.push(Self::parse_char(&pair)), - Rule::input => { - let input_name = pair.as_str(); - let value = self.get_input(input_name)?; - match value { - Value::String(value) => full_string.push_str(&value), - _ => return Err(Error::InvalidInterpolationError(input_name.to_string())), - } - } - _ => unreachable!(), - }; - } - - let full_string = if full_string.contains('\n') { - trim_multiline_string(&full_string) - } else { - full_string - }; - - Ok(Cow::Owned(full_string)) - } - - fn parse_char(pair: &Pair<'a, Rule>) -> char { - let str = pair.as_str(); - let mut chars = str.chars(); - - let first_char = chars.next().expect("character to exist"); - if first_char != '\\' { - return first_char; - } - - let second_char = chars.next().expect("character to exist"); - if second_char != 'u' { - return match second_char { - 'n' => '\n', - 'r' => '\r', - 't' => '\t', - '"' => '\"', - '$' => '$', - '\\' => '\\', - _ => unreachable!(), - }; - } - - let num = - u32::from_str_radix(&str[3..], 16).expect("valid hex characters to exist after \\u"); - char::from_u32(num).unwrap_or('\u{FFFD}') - } - - /// Parses each rule in a `Rule::array` - /// to form a vector of `Value`s. - fn parse_array(&self, block: Pair<'a, Rule>) -> Result>> { - assert_eq!(block.as_rule(), Rule::array); - - let mut arr = vec![]; - - for pair in block.into_inner() { - match pair.as_rule() { - Rule::spread => { - let input = pair - .into_inner() - .next() - .expect("spread operators should contain an input"); - - let input_name = input.as_str(); - let value = self.parse_value(input)?; - - match value { - Value::Array(other) => arr.extend(other), - _ => return Err(Error::InvalidSpreadError(input_name.to_string())), - } - } - _ => arr.push(self.parse_value(pair)?), - }; - } - - Ok(arr) - } - - /// Parses each key/value pair in a `Rule::object` - /// to form a `IndexMap` of Values. - /// - /// An `IndexMap` is used to ensure keys - /// always output in the same order. - fn parse_object(&self, block: Pair<'a, Rule>) -> Result> { - assert_eq!(block.as_rule(), Rule::object); - - let mut obj = IndexMap::new(); - - for pair in block.into_inner() { - match pair.as_rule() { - Rule::pair => { - let mut path_rules = pair.into_inner(); - - let path = path_rules - .next() - .expect("object pairs should contain a key"); - - let paths = Self::parse_path(path); - - let value = self.parse_value( - path_rules - .next() - .expect("object pairs should contain a value"), - )?; - - obj = Self::add_at_path(obj, &paths, value)?; - } - Rule::spread => { - let input = pair - .into_inner() - .next() - .expect("spread operators should contain an input"); - - let input_name = input.as_str(); - let value = self.parse_value(input)?; - - match value { - Value::Object(other) => obj.extend(other), - _ => return Err(Error::InvalidSpreadError(input_name.to_string())), - } - } - _ => unreachable!(), - } - } - - Ok(obj) - } - - fn parse_path(path: Pair) -> Vec> { - path.into_inner() - .map(|pair| match pair.as_rule() { - Rule::regular_path_seg => Cow::Borrowed(pair.as_str()), - Rule::quoted_path_seg => Cow::Owned( - pair.into_inner() - .next() - .expect("quoted paths should contain an inner value") - .as_str() - .replace('\\', ""), - ), - _ => unreachable!(), - }) - .collect::>() - } - - /// Adds `Value` at the `path` in `obj`. - /// - /// `path` is an array where each entry represents another object key, - /// for example `foo.bar` is represented as `["foo", "bar"]`. - /// - /// Objects are created up to the required depth recursively. - fn add_at_path( - mut obj: Object<'a>, - path: &[Cow<'a, str>], - value: Value<'a>, - ) -> Result> { - let (part, path_rest) = path - .split_first() - .expect("paths should contain at least 1 segment"); - - if path_rest.is_empty() { - obj.insert(part.clone(), value); - return Ok(obj); - } - - let child_obj = obj - .shift_remove(part) - .unwrap_or_else(|| Value::Object(IndexMap::new())); - - match child_obj { - Value::Object(map) => { - obj.insert( - part.clone(), - Value::Object(Self::add_at_path(map, path_rest, value)?), - ); - - Ok(obj) - } - _ => Err(Error::InvalidPathError(path.join("."))), - } - } - - /// Parses the `let { } in` block at the start of files. - /// Each input is inserted into into `self.inputs`. - fn parse_assign_block(&mut self, block: Pair<'a, Rule>) -> Result<()> { - assert_eq!(block.as_rule(), Rule::assign_block); - - for pair in block.into_inner() { - let mut assign_rules = pair.into_inner(); - let name = assign_rules - .next() - .expect("input assignments should have a name") - .as_str(); - - let value = self.parse_value( - assign_rules - .next() - .expect("input assignments should have a value"), - )?; - - self.inputs.insert(name, value); - } - - Ok(()) - } - - /// Attempts to get an input value from the `inputs` map. - /// If the `key` starts with `$env_` the system environment variables will be consulted first. - fn get_input(&self, key: &'a str) -> Result> { - if let Some(env_name) = key.strip_prefix("$env_") { - let var = var(env_name); - - if let Ok(var) = var { - return Ok(Value::String(Cow::Owned(var))); - } - } - - if let Some(value) = self.inputs.get(key) { - Ok(value.clone()) - } else { - Err(Error::InputResolveError(key.to_string())) - } - } -} - -/// Takes a multiline string and trims the maximum amount of -/// whitespace at the start of each line -/// while preserving formatting. -/// -/// Based on code from `indoc` crate: -/// -fn trim_multiline_string(string: &str) -> String { - let ignore_first_line = string.starts_with('\n') || string.starts_with("\r\n"); - - let spaces = string - .lines() - .skip(1) - .map(|line| line.chars().take_while(char::is_ascii_whitespace).count()) - .min() - .unwrap_or_default(); - - let mut result = String::with_capacity(string.len()); - for (i, line) in string.lines().enumerate() { - if i > 1 || (i == 1 && !ignore_first_line) { - result.push('\n'); - } - if i == 0 { - // Do not un-indent anything on same line as opening quote - result.push_str(line); - } else if line.len() > spaces { - // Whitespace-only lines may have fewer than the number of spaces - // being removed - result.push_str(&line[spaces..]); - } - } - result -} - -/// Parses the input string into a `Config` -/// containing the resolved inputs -/// and a map of values representing the top-level object. -/// -/// # Examples -/// -/// ```rust -/// use corn::parse; -/// -/// let corn = "{foo = 42}"; -/// -/// let config = parse(corn).unwrap(); -/// let json = serde_json::to_string(&config).unwrap(); -/// -/// assert_eq!(json, "{\"foo\":42}"); -/// ``` -/// -/// # Errors -/// -/// Will fail if the input contains a syntax error. -/// Will fail if the input contains invalid Corn for another reason, -/// including references to undefined inputs or dot-notation for non-object values. -/// Will fail if the input cannot be deserialized for any reaon. -/// -/// Any of the above will return a specific error type with details. -/// -/// # Panics -/// -/// If the internal AST parser produces a tree in an invalid structure, -/// the function will panic. -/// This indicates a severe error in the library and should never occur. -pub fn parse(file: &str) -> Result { - let rules = AstParser::parse(Rule::config, file); - - match rules { - Ok(mut rules) => { - let first_block = rules.next().expect("should be at least 1 rule"); - - match first_block.as_rule() { - Rule::assign_block => { - let parser = CornParser::new(Some(first_block)); - let object_block = rules.next().expect("should always be an object block"); - parser.parse(object_block) - } - Rule::object => { - let parser = CornParser::new(None); - parser.parse(first_block) - } - _ => unreachable!(), - } - } - Err(error) => Err(Error::ParserError(Box::new(error))), - } -} diff --git a/src/value/de.rs b/src/value/de.rs new file mode 100644 index 0000000..d2a2c7b --- /dev/null +++ b/src/value/de.rs @@ -0,0 +1,115 @@ +use alloc::{string::String, vec::Vec}; +use serde_core::{Deserialize, de::Visitor}; + +use crate::{Object, Value}; + +impl<'de> Deserialize<'de> for Value { + fn deserialize(deserializer: D) -> Result + where + D: serde_core::Deserializer<'de>, + { + struct ValueVisitor; + + impl<'de> Visitor<'de> for ValueVisitor { + type Value = Value; + + fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result { + formatter.write_str("any valid Corn value") + } + + #[inline] + fn visit_bool(self, v: bool) -> Result + where + E: serde_core::de::Error, + { + Ok(Value::Boolean(v)) + } + + #[inline] + fn visit_i64(self, v: i64) -> Result + where + E: serde_core::de::Error, + { + Ok(Value::Integer(v.into())) + } + + #[inline] + fn visit_u64(self, v: u64) -> Result + where + E: serde_core::de::Error, + { + Ok(Value::Integer(v.into())) + } + + fn visit_f64(self, v: f64) -> Result + where + E: serde_core::de::Error, + { + Ok(Value::Float(v)) + } + + fn visit_str(self, v: &str) -> Result + where + E: serde_core::de::Error, + { + self.visit_string(String::from(v)) + } + + fn visit_string(self, v: String) -> Result + where + E: serde_core::de::Error, + { + Ok(Value::String(v)) + } + + fn visit_none(self) -> Result + where + E: serde_core::de::Error, + { + Ok(Value::Null) + } + + fn visit_some(self, deserializer: D) -> Result + where + D: serde_core::Deserializer<'de>, + { + Deserialize::deserialize(deserializer) + } + + fn visit_unit(self) -> Result + where + E: serde_core::de::Error, + { + Ok(Value::Null) + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: serde_core::de::SeqAccess<'de>, + { + let mut vec = Vec::new(); + + while let Some(elem) = seq.next_element()? { + vec.push(elem); + } + + Ok(Value::Array(vec)) + } + + fn visit_map(self, mut map: A) -> Result + where + A: serde_core::de::MapAccess<'de>, + { + let mut dict = Object::default(); + + while let Some((key, value)) = map.next_entry()? { + dict.insert(key, value); + } + + Ok(Value::Object(dict)) + } + } + + deserializer.deserialize_any(ValueVisitor) + } +} diff --git a/src/value/integer.rs b/src/value/integer.rs new file mode 100644 index 0000000..89ccd6a --- /dev/null +++ b/src/value/integer.rs @@ -0,0 +1,144 @@ +use core::fmt::{Debug, Display}; + +use serde_core::{ + Deserialize, Serialize, + de::{self, Visitor}, + forward_to_deserialize_any, +}; + +use crate::Error; + +#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] +#[repr(transparent)] +pub struct Integer { + inner: IntegerType, +} + +impl Debug for Integer { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "Integer({self})") + } +} + +impl Display for Integer { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self.inner { + IntegerType::Signed(n) => f.write_str(itoa::Buffer::new().format(n)), + IntegerType::Unsigned(n) => f.write_str(itoa::Buffer::new().format(n)), + } + } +} + +#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] +enum IntegerType { + Signed(i64), + Unsigned(u64), +} + +impl From for Integer { + fn from(value: i64) -> Self { + Self { + inner: IntegerType::Signed(value), + } + } +} + +impl From for Integer { + fn from(value: u64) -> Self { + Self { + inner: IntegerType::Unsigned(value), + } + } +} + +impl Integer { + pub const fn is_i64(&self) -> bool { + match self.inner { + IntegerType::Unsigned(n) => n <= i64::MAX as u64, + IntegerType::Signed(_) => true, + } + } + + pub const fn is_u64(&self) -> bool { + matches!(self.inner, IntegerType::Unsigned(_)) + } + + pub const fn as_i64(&self) -> Option { + match self.inner { + IntegerType::Signed(n) => Some(n), + IntegerType::Unsigned(n) => { + if n <= i64::MAX as u64 { + Some(n as i64) + } else { + None + } + } + } + } + + pub const fn as_u64(&self) -> Option { + match self.inner { + IntegerType::Unsigned(n) => Some(n), + IntegerType::Signed(_) => None, + } + } +} + +impl Serialize for Integer { + fn serialize(&self, serializer: S) -> Result + where + S: serde_core::Serializer, + { + match self.inner { + IntegerType::Signed(integer) => serializer.serialize_i64(integer), + IntegerType::Unsigned(integer) => serializer.serialize_u64(integer), + } + } +} + +impl<'de> Deserialize<'de> for Integer { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + struct IntegerVisitor; + + impl<'de> Visitor<'de> for IntegerVisitor { + type Value = Integer; + + fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result { + formatter.write_str("an Integer") + } + + fn visit_i64(self, value: i64) -> Result { + Ok(value.into()) + } + + fn visit_u64(self, value: u64) -> Result { + Ok(value.into()) + } + } + + deserializer.deserialize_any(IntegerVisitor) + } +} + +impl<'de> de::Deserializer<'de> for Integer { + type Error = Error; + + fn deserialize_any(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + match self.inner { + IntegerType::Signed(integer) => visitor.visit_i64(integer), + IntegerType::Unsigned(integer) => visitor.visit_u64(integer), + } + } + + forward_to_deserialize_any! { + bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string + bytes byte_buf option unit unit_struct newtype_struct seq tuple + tuple_struct map struct enum identifier ignored_any + } +} diff --git a/src/value/mod.rs b/src/value/mod.rs new file mode 100644 index 0000000..b4500e9 --- /dev/null +++ b/src/value/mod.rs @@ -0,0 +1,311 @@ +use alloc::{borrow::Cow, format, string::String, vec::Vec}; + +mod de; +mod integer; +mod ser; + +pub use integer::Integer; + +use crate::{Error, IndexMap}; + +/// Object: Key-value collection that preserves insertion order +pub type Object = IndexMap; +pub type BorrowedObject<'input> = IndexMap, BorrowedValue<'input>>; + +/// Represents a Corn configuration value. +/// +/// This enum encompasses all possible value types in the Corn language specification +#[derive(Debug, Clone, PartialEq)] +pub enum Value { + /// A UTF-8 string value + String(String), + /// A 64-bit signed integer + Integer(Integer), + /// A 64-bit floating point number + Float(f64), + /// A boolean value (true or false) + Boolean(bool), + /// A key-value collection that preserves insertion order + Object(Object), + /// An ordered collection of values + Array(Vec), + /// Represents the absence of a value + Null, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum BorrowedValue<'input> { + String(Cow<'input, str>), + Integer(Integer), + Float(f64), + Boolean(bool), + Null, + Array(Vec>), + Object(BorrowedObject<'input>), +} + +impl BorrowedValue<'_> { + pub fn into_value(self) -> Value { + match self { + BorrowedValue::String(string) => Value::String(string.into_owned()), + BorrowedValue::Integer(integer) => Value::Integer(integer), + BorrowedValue::Float(float) => Value::Float(float), + BorrowedValue::Boolean(boolean) => Value::Boolean(boolean), + BorrowedValue::Null => Value::Null, + BorrowedValue::Array(array) => { + Value::Array(array.into_iter().map(Value::from).collect()) + } + BorrowedValue::Object(object) => Value::Object( + object + .into_iter() + .map(|(k, v)| (k.into_owned(), Value::from(v))) + .collect(), + ), + } + } + + pub const fn as_type(&self) -> &'static str { + match self { + Self::String(_) => "String", + Self::Integer(_) => "Integer", + Self::Float(_) => "Float", + Self::Boolean(_) => "Boolean", + Self::Null => "Null", + Self::Array(_) => "Array", + Self::Object(_) => "Object", + } + } + + pub(crate) fn invalid_type(&self, exp: &'static str) -> Error { + Error::DeserializationError(format!("Invalid type: {}, expected {exp}", self.as_type())) + } +} + +impl From> for Value { + fn from(entry: BorrowedValue<'_>) -> Self { + entry.into_value() + } +} + +impl Value { + /// Returns true if the value is a String. + pub const fn is_string(&self) -> bool { + matches!(self, Self::String(_)) + } + + /// Returns true if the value is an Integer. + pub const fn is_integer(&self) -> bool { + matches!(self, Self::Integer(_)) + } + + /// Returns true if the value is a Float. + pub const fn is_float(&self) -> bool { + matches!(self, Self::Float(_)) + } + + /// Returns true if the value is a Boolean. + pub const fn is_boolean(&self) -> bool { + matches!(self, Self::Boolean(_)) + } + + /// Returns true if the value is an Object. + pub const fn is_object(&self) -> bool { + matches!(self, Self::Object(_)) + } + + /// Returns true if the value is an Array. + pub const fn is_array(&self) -> bool { + matches!(self, Self::Array(_)) + } + + /// Returns true if the value is Null. + pub const fn is_null(&self) -> bool { + matches!(self, Self::Null) + } + + /// Returns the inner String if this value is a String, otherwise None. + pub fn as_string(&self) -> Option<&String> { + match self { + Self::String(s) => Some(s), + _ => None, + } + } + + /// Returns the inner Integer if this value is an Integer, otherwise None. + pub const fn as_integer(&self) -> Option<&Integer> { + match self { + Self::Integer(integer) => Some(integer), + _ => None, + } + } + + /// Returns the inner Float if this value is a Float, otherwise None. + pub const fn as_float(&self) -> Option<&f64> { + match self { + Self::Float(f) => Some(f), + _ => None, + } + } + + /// Returns the inner Boolean if this value is a Boolean, otherwise None. + pub const fn as_boolean(&self) -> Option<&bool> { + match self { + Self::Boolean(b) => Some(b), + _ => None, + } + } + + /// Returns the inner Object if this value is an Object, otherwise None. + pub fn as_object(&self) -> Option<&Object> { + match self { + Self::Object(obj) => Some(obj), + _ => None, + } + } + + /// Returns the inner Array if this value is an Array, otherwise None. + pub fn as_array(&self) -> Option<&Vec> { + match self { + Self::Array(arr) => Some(arr), + _ => None, + } + } + + /// Returns a mutable reference to the inner String if this value is a String, otherwise None. + pub fn as_string_mut(&mut self) -> Option<&mut String> { + match self { + Self::String(s) => Some(s), + _ => None, + } + } + + /// Returns a mutable reference to the inner Integer if this value is an Integer, otherwise None. + pub fn as_integer_mut(&mut self) -> Option<&mut Integer> { + match self { + Self::Integer(i) => Some(i), + _ => None, + } + } + + /// Returns a mutable reference to the inner Float if this value is a Float, otherwise None. + pub fn as_float_mut(&mut self) -> Option<&mut f64> { + match self { + Self::Float(f) => Some(f), + _ => None, + } + } + + /// Returns a mutable reference to the inner Boolean if this value is a Boolean, otherwise None. + pub fn as_boolean_mut(&mut self) -> Option<&mut bool> { + match self { + Self::Boolean(b) => Some(b), + _ => None, + } + } + + /// Returns a mutable reference to the inner Object if this value is an Object, otherwise None. + pub fn as_object_mut(&mut self) -> Option<&mut Object> { + match self { + Self::Object(obj) => Some(obj), + _ => None, + } + } + + /// Returns a mutable reference to the inner Array if this value is an Array, otherwise None. + pub fn as_array_mut(&mut self) -> Option<&mut Vec> { + match self { + Self::Array(arr) => Some(arr), + _ => None, + } + } + + /// Takes the inner String if this value is a String, otherwise None. + pub fn take_string(self) -> Option { + match self { + Self::String(s) => Some(s), + _ => None, + } + } + + /// Takes the inner Integer if this value is an Integer, otherwise None. + pub fn take_integer(self) -> Option { + match self { + Self::Integer(i) => Some(i), + _ => None, + } + } + + /// Takes the inner Float if this value is a Float, otherwise None. + pub fn take_float(self) -> Option { + match self { + Self::Float(f) => Some(f), + _ => None, + } + } + + /// Takes the inner Boolean if this value is a Boolean, otherwise None. + pub fn take_boolean(self) -> Option { + match self { + Self::Boolean(b) => Some(b), + _ => None, + } + } + + /// Takes the inner Object if this value is an Object, otherwise None. + pub fn take_object(self) -> Option { + match self { + Self::Object(obj) => Some(obj), + _ => None, + } + } + + /// Takes the inner Array if this value is an Array, otherwise None. + pub fn take_array(self) -> Option> { + match self { + Self::Array(arr) => Some(arr), + _ => None, + } + } + + /// Returns true if the value is empty. + /// An empty value is an empty String, empty Object, empty Array, or Null. + pub fn is_empty(&self) -> bool { + match self { + Self::String(s) => s.is_empty(), + Self::Object(obj) => obj.is_empty(), + Self::Array(arr) => arr.is_empty(), + Self::Null => true, + _ => false, + } + } + + /// Returns the number of elements in this Value. + /// For objects this is the number of key-value pairs, for arrays it's the number of elements, + /// for strings it's the string length, and for other types it's 0. + pub fn len(&self) -> usize { + match self { + Self::String(s) => s.len(), + Self::Object(obj) => obj.len(), + Self::Array(arr) => arr.len(), + _ => 0, + } + } + + /// Get a reference to a value in an object by key. + /// Returns None if the value is not an object or if the key doesn't exist. + pub fn get(&self, key: &str) -> Option<&Value> { + match self { + Self::Object(obj) => obj.get(key), + _ => None, + } + } + + /// Get a reference to a value in an array by index. + /// Returns None if the value is not an array or if the index is out of bounds. + pub fn get_index(&self, index: usize) -> Option<&Value> { + match self { + Self::Array(arr) => arr.get(index), + _ => None, + } + } +} diff --git a/src/value/ser.rs b/src/value/ser.rs new file mode 100644 index 0000000..8e1ed0d --- /dev/null +++ b/src/value/ser.rs @@ -0,0 +1,57 @@ +use serde_core::Serialize; + +use crate::{BorrowedValue, Value}; + +impl Serialize for Value { + #[inline] + fn serialize(&self, serializer: S) -> Result + where + S: serde_core::Serializer, + { + match self { + Self::String(s) => serializer.serialize_str(s), + Self::Integer(i) => i.serialize(serializer), + Self::Float(f) => f.serialize(serializer), + Self::Boolean(v) => serializer.serialize_bool(*v), + Self::Object(obj) => { + use serde_core::ser::SerializeMap; + let mut map = serializer.serialize_map(Some(obj.len()))?; + + for (k, v) in obj { + map.serialize_entry(k, v)?; + } + + map.end() + } + Self::Array(v) => v.serialize(serializer), + Self::Null => serializer.serialize_none(), + } + } +} + +impl Serialize for BorrowedValue<'_> { + #[inline] + fn serialize(&self, serializer: S) -> Result + where + S: serde_core::Serializer, + { + match self { + Self::String(s) => serializer.serialize_str(s), + Self::Integer(i) => i.serialize(serializer), + Self::Float(f) => f.serialize(serializer), + Self::Boolean(v) => serializer.serialize_bool(*v), + Self::Object(obj) => { + use serde_core::ser::SerializeMap; + let mut map = serializer.serialize_map(Some(obj.len()))?; + + for (k, v) in obj { + map.serialize_entry(k, v)?; + } + + map.end() + } + Self::Array(v) => v.serialize(serializer), + Self::Null => serializer.serialize_none(), + } + } +} diff --git a/src/wasm.rs b/src/wasm.rs index b688bbf..863f226 100644 --- a/src/wasm.rs +++ b/src/wasm.rs @@ -1,8 +1,8 @@ #![allow(dead_code)] use serde_wasm_bindgen::to_value; -use wasm_bindgen::prelude::*; use wasm_bindgen::JsValue; +use wasm_bindgen::prelude::*; #[cfg(test)] use wasm_bindgen_test::*; diff --git a/test-suite b/test-suite new file mode 160000 index 0000000..1e2662b --- /dev/null +++ b/test-suite @@ -0,0 +1 @@ +Subproject commit 1e2662bfec18e6a4c1ef495995ac915defa55f02 diff --git a/tests/de_tests.rs b/tests/de_tests.rs deleted file mode 100644 index b039970..0000000 --- a/tests/de_tests.rs +++ /dev/null @@ -1,596 +0,0 @@ -use corn::from_str; -use paste::paste; -use serde::Deserialize; -use std::fs; - -macro_rules! generate_eq_tests { - ($(($test_name:ident, $test_type:ty)),+) => { - $( - paste! { - #[test] - fn $test_name() { - let test_name = stringify!($test_name); - let root_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); - - let input = fs::read_to_string(format!("{root_dir}/assets/inputs/{test_name}.corn")).unwrap(); - let config = from_str::<$test_type>(&input).unwrap(); - - let json_input = fs::read_to_string(format!("{root_dir}/assets/outputs/json/{test_name}.json")).unwrap(); - let json_config = serde_json::from_str(&json_input).unwrap(); - - assert_eq!(config, json_config); - } - } - )+ - }; -} - -#[derive(Deserialize, Debug, PartialEq)] -struct Empty {} - -#[derive(Deserialize, Debug, PartialEq)] -struct Array { - foo: Vec, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct Basic { - foo: String, -} - -#[derive(Deserialize, Debug, PartialEq)] -#[serde(rename_all = "snake_case")] -enum BasicNewTypeEnum { - Foo(String), -} - -#[derive(Deserialize, Debug, PartialEq)] -struct BasicUnitEnum { - foo: BasicUnitEnumInner, -} - -#[derive(Deserialize, Debug, PartialEq)] -#[serde(rename_all = "snake_case")] -enum BasicUnitEnumInner { - Bar, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct BasicNewType { - foo: BasicNewTypeInner, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct BasicNewTypeInner(String); - -#[derive(Deserialize, Debug, PartialEq)] -struct Boolean { - foo: bool, - bar: bool, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct Bytes { - #[serde(with = "serde_bytes")] - foo: Vec, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct Chained { - foo: ChainedInner, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct ChainedInner { - bar: String, -} - -#[derive(Deserialize, Debug, PartialEq)] -#[serde(rename_all = "snake_case")] -enum ChainedEnum { - Foo { bar: String }, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct ChainedComplex { - foo: ChainedComplexInner, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct ChainedComplexInner { - bar: ChainedComplexInnerInner, - qux: bool, - quux: Vec, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct ChainedComplexInnerInner { - baz: i64, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct Char { - foo: char, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct Compact { - one: CompactOne, - two: CompactTwo, - three: CompactThree, - four: CompactFour, - five: CompactFive, - six: CompactSix, - seven: CompactSeven, - eight: Vec, - nine: Vec, - ten: (u8, u8), - eleven: Vec>, - twelve: Vec, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct CompactOne { - foo: String, - bar: String, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct CompactTwo { - foo: i64, - bar: i64, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct CompactThree { - foo: f64, - bar: f64, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct CompactFour { - foo: bool, - bar: bool, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct CompactFive { - foo: (), - bar: (), -} - -#[derive(Deserialize, Debug, PartialEq)] -struct CompactSix { - foo: Empty, - bar: Empty, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct CompactSeven { - foo: Vec, - bar: Vec, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct Complex { - age: i64, - employment: ComplexEmployment, - empty1: Empty, - empty2: Vec, - favourites: (String, String, String, String, f64, bool, Favourites), - gender: String, - name: ComplexName, - negative: ComplexNegative, - parents: ComplexParents, - placeholder: (), -} - -#[derive(Deserialize, Debug, PartialEq)] -#[serde(rename_all = "camelCase")] -struct ComplexEmployment { - employed: bool, - name: String, - since_year: i64, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct Favourites { - food: ComplexFavouritesFood, - hello: String, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct ComplexFavouritesFood { - favourite: String, - hated: String, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct ComplexName { - first: String, - full: String, - last: String, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct ComplexNegative { - float: f64, - int: i64, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct ComplexParents { - father: ComplexParentsFather, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct ComplexParentsFather { - birthday: ComplexParentsFatherBirthday, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct ComplexParentsFatherBirthday { - day: i64, - month: i64, - year: i64, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct ComplexKeys { - #[serde(rename = "!\"£$%^&*()_")] - symbols: i64, - #[serde(rename = "apple-pie")] - apple_pie: ComplexKeysApplePie, - foo: ComplexKeysFoo, - j12345: i64, - #[serde(rename = "with-dash")] - with_dash: i64, - #[serde(rename = "with_underscore")] - with_underscore: i64, - #[serde(rename = "with_🌽")] - with_corn_emoji: i64, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct ComplexKeysApplePie { - crust: String, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct ComplexKeysFoo { - #[serde(rename = "bar-baz")] - bar_baz: String, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct Float { - foo: f64, - bar: f64, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct Input { - name: InputName, - dob: InputDob, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct InputName { - first: String, - last: String, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct InputDob { - day: u8, - month: u8, - year: u16, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct Integer { - foo: i64, - bar: i64, - baz: i64, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct MixedArray { - foo: (u8, String, bool), -} - -#[derive(Deserialize, Debug, PartialEq)] -#[serde(rename_all = "snake_case")] -enum MixedArrayEnum { - Foo(u8, String, bool), -} - -#[derive(Deserialize, Debug, PartialEq)] -struct Null { - foo: (), -} - -#[derive(Deserialize, Debug, PartialEq)] -struct NullOption { - foo: Option, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct NullUnit { - foo: NullUnitInner, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct NullUnitInner; - -#[derive(Deserialize, Debug, PartialEq)] -struct NullInArray { - foo: Vec>, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct Object { - foo: SubObject, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct SubObject { - bar: i64, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct ObjectInArray { - foo: Vec, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct ObjectInArrayFoo { - bar: i64, - foo: i64, -} - -#[derive(Deserialize, Debug, PartialEq)] -#[serde(rename_all = "camelCase")] -struct ReadmeExample { - author: ReadmeExampleAuthor, - bin: ReadmeExampleBin, - config: ReadmeExampleConfig, - contributors: Vec, - dependencies: ReadmeExampleDependencies, - dev_dependencies: ReadmeExampleDevDependencies, - main: String, - name: String, - private: bool, - scripts: ReadmeExampleScripts, - version: String, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct QuotedKeys { - #[serde(rename = "foo.bar")] - foo_bar: u8, - #[serde(rename = "green.eggs")] - green_eggs: GreenEggs, - #[serde(rename = "with spaces")] - with_spaces: bool, - #[serde(rename = "escaped'quote")] - escaped_quote: bool, - #[serde(rename = "escaped=equals")] - escaped_equals: i8, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct GreenEggs { - and: And, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct And { - ham: String, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct String_ { - foo: String, - bar: String, - baz: String, - qux: String, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct ReadmeExampleAuthor { - email: String, - name: String, - url: String, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct ReadmeExampleBin { - filebrowser: String, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct ReadmeExampleConfig { - hostname: Option, - port: i64, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct ReadmeExampleContributor { - email: String, - name: String, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct ReadmeExampleDependencies { - dotenv: String, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct ReadmeExampleDevDependencies { - typescript: String, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct ReadmeExampleScripts { - build: String, - run: String, -} - -#[derive(Deserialize, Debug, PartialEq)] -struct ValueAfterTable { - foo: Empty, - qux: bool, -} - -generate_eq_tests!( - (array, Array), - (basic, Basic), - (basic_empty_let, Basic), - (boolean, Boolean), - (chained, Chained), - (chained_complex, ChainedComplex), - (char, Char), - (comment, Basic), - (compact, Compact), - (complex, Complex), - (complex_keys, ComplexKeys), - (environment_variable, Basic), - (float, Float), - (input, Input), - (input_references_input, Basic), - (integer, Integer), - (mixed_array, MixedArray), - (null, Null), - (null_in_array, NullInArray), - (object, Object), - (object_in_array, ObjectInArray), - (readme_example, ReadmeExample), - (quoted_keys, QuotedKeys), - (string, String_), - (string_interpolation, Basic), - (value_after_table, ValueAfterTable), - (very_compact, Compact) -); - -// TODO: Several of these can use the macro, tidy - -#[test] -fn basic_new_type_enum() { - let test_name = "basic"; - let root_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); - - let input = fs::read_to_string(format!("{root_dir}/assets/inputs/{test_name}.corn")).unwrap(); - let config = from_str::(&input).unwrap(); - - let json_input = - fs::read_to_string(format!("{root_dir}/assets/outputs/json/{test_name}.json")).unwrap(); - let json_config = serde_json::from_str(&json_input).unwrap(); - - assert_eq!(config, json_config); -} - -#[test] -fn basic_unit_enum() { - let test_name = "basic"; - let root_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); - - let input = fs::read_to_string(format!("{root_dir}/assets/inputs/{test_name}.corn")).unwrap(); - let config = from_str::(&input).unwrap(); - - let json_input = - fs::read_to_string(format!("{root_dir}/assets/outputs/json/{test_name}.json")).unwrap(); - let json_config = serde_json::from_str(&json_input).unwrap(); - - assert_eq!(config, json_config); -} - -#[test] -fn basic_new_type() { - let test_name = "basic"; - let root_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); - - let input = fs::read_to_string(format!("{root_dir}/assets/inputs/{test_name}.corn")).unwrap(); - let config = from_str::(&input).unwrap(); - - let json_input = - fs::read_to_string(format!("{root_dir}/assets/outputs/json/{test_name}.json")).unwrap(); - let json_config = serde_json::from_str(&json_input).unwrap(); - - assert_eq!(config, json_config); -} - -#[test] -fn bytes() { - let test_name = "basic"; - let root_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); - - let input = fs::read_to_string(format!("{root_dir}/assets/inputs/{test_name}.corn")).unwrap(); - let config = from_str::(&input).unwrap(); - - let json_input = - fs::read_to_string(format!("{root_dir}/assets/outputs/json/{test_name}.json")).unwrap(); - let json_config = serde_json::from_str(&json_input).unwrap(); - - assert_eq!(config, json_config); -} - -#[test] -fn chained_enum() { - let test_name = "chained"; - let root_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); - - let input = fs::read_to_string(format!("{root_dir}/assets/inputs/{test_name}.corn")).unwrap(); - let config = from_str::(&input).unwrap(); - - let json_input = - fs::read_to_string(format!("{root_dir}/assets/outputs/json/{test_name}.json")).unwrap(); - let json_config = serde_json::from_str(&json_input).unwrap(); - - assert_eq!(config, json_config); -} - -#[test] -fn mixed_array_enum() { - let test_name = "mixed_array"; - let root_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); - - let input = fs::read_to_string(format!("{root_dir}/assets/inputs/{test_name}.corn")).unwrap(); - let config = from_str::(&input).unwrap(); - - let json_input = - fs::read_to_string(format!("{root_dir}/assets/outputs/json/{test_name}.json")).unwrap(); - let json_config = serde_json::from_str(&json_input).unwrap(); - - assert_eq!(config, json_config); -} - -#[test] -fn null_option() { - let test_name = "null"; - let root_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); - - let input = fs::read_to_string(format!("{root_dir}/assets/inputs/{test_name}.corn")).unwrap(); - let config = from_str::(&input).unwrap(); - - let json_input = - fs::read_to_string(format!("{root_dir}/assets/outputs/json/{test_name}.json")).unwrap(); - let json_config = serde_json::from_str(&json_input).unwrap(); - - assert_eq!(config, json_config); -} - -#[test] -fn null_unit() { - let test_name = "null"; - let root_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); - - let input = fs::read_to_string(format!("{root_dir}/assets/inputs/{test_name}.corn")).unwrap(); - let config = from_str::(&input).unwrap(); - - let json_input = - fs::read_to_string(format!("{root_dir}/assets/outputs/json/{test_name}.json")).unwrap(); - let json_config = serde_json::from_str(&json_input).unwrap(); - - assert_eq!(config, json_config); -} diff --git a/tests/parser_tests.rs b/tests/parser_tests.rs deleted file mode 100644 index 7b6d8ff..0000000 --- a/tests/parser_tests.rs +++ /dev/null @@ -1,107 +0,0 @@ -extern crate core; - -use corn::parse; -use paste::paste; -use std::fs; - -macro_rules! generate_eq_tests { - ($($test_name:ident),+) => { - $( - paste!{ - #[test] - fn []() { - let test_name = stringify!($test_name); - let root_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); - - let input = fs::read_to_string(format!("{root_dir}/assets/inputs/{test_name}.corn")).unwrap(); - let valid = fs::read_to_string(format!("{root_dir}/assets/outputs/json/{test_name}.json")).unwrap().replace("\r", ""); - - let config = parse(input.as_str()).unwrap(); - let serialized = serde_json::to_string_pretty(&config).unwrap().replace("\r", ""); - - assert_eq!(serialized.trim(), valid.trim()); - } - - #[test] - fn []() { - let test_name = stringify!($test_name); - let root_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); - - let input = fs::read_to_string(format!("{root_dir}/assets/inputs/{test_name}.corn")).unwrap(); - let valid = fs::read_to_string(format!("{root_dir}/assets/outputs/yaml/{test_name}.yml")).unwrap().replace("\r", ""); - - let config = parse(input.as_str()).unwrap(); - let serialized = serde_norway::to_string(&config).unwrap().replace("\r", ""); - - assert_eq!(serialized.trim(), valid.trim()); - } - - #[test] - fn []() { - let test_name = stringify!($test_name); - let root_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); - - let input = fs::read_to_string(format!("{root_dir}/assets/inputs/{test_name}.corn")).unwrap(); - let valid = fs::read_to_string(format!("{root_dir}/assets/outputs/toml/{test_name}.toml")).unwrap().replace("\r", ""); - - let config = parse(input.as_str()).unwrap(); - // fall back to default as toml can fail due to no null - let serialized = toml_edit::ser::to_string_pretty(&config).unwrap_or_default().replace("\r", ""); - - assert_eq!(serialized.trim(), valid.trim()); - } - } - - )+ - } -} - -macro_rules! generate_invalid_tests { - ($($test_name:ident),+) => { - $( - #[test] - fn $test_name() { - let test_name = stringify!($test_name); - let root_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); - - let input = fs::read_to_string(format!("{root_dir}/assets/inputs/{}.corn", test_name)).unwrap(); - - let config = parse(input.as_str()); - assert!(config.is_err()); - } - )+ - } -} - -generate_eq_tests!( - array, - basic, - basic_empty_let, - boolean, - chained, - chained_complex, - char, - comment, - compact, - complex, - complex_keys, - environment_variable, - float, - input, - input_references_input, - integer, - mixed_array, - null, - null_in_array, - object, - object_in_array, - quoted_keys, - readme_example, - spread, - string, - string_interpolation, - value_after_table, - very_compact -); - -generate_invalid_tests!(invalid, invalid_input, invalid_nesting, invalid_spread); diff --git a/tests/test_suite.rs b/tests/test_suite.rs new file mode 100644 index 0000000..ee2f181 --- /dev/null +++ b/tests/test_suite.rs @@ -0,0 +1,32 @@ +use corn::Value as CornValue; +use std::{fs, path::Path}; + +fn positive(path: &Path, content: String) -> datatest_stable::Result<()> { + let json: CornValue = { + let mut path = path.to_path_buf(); + path.set_extension(""); + path.set_extension(""); + path.set_extension("json"); + + let path = Path::new("test-suite/json").join(path.strip_prefix("test-suite/corn/")?); + + serde_json::from_str(&fs::read_to_string(path)?)? + }; + + let corn: CornValue = corn::from_str(&content)?; + + assert_eq!(json, corn); + + Ok(()) +} + +fn negative(_path: &Path, content: String) -> datatest_stable::Result<()> { + assert!(corn::from_str::(&content).is_err()); + + Ok(()) +} + +datatest_stable::harness! { + { test = positive, root = "test-suite", pattern = r"^.*\.pos\.corn$" }, + { test = negative, root = "test-suite", pattern = r"^.*\.neg\.corn$" }, +}