From 23322aae58e64ba41e089c885c096e241557bc6e Mon Sep 17 00:00:00 2001 From: Felix Leitner <52215017+lixitrixi@users.noreply.github.com> Date: Tue, 21 Nov 2023 10:43:05 +0000 Subject: [PATCH] Model parsing from JSON (#32) * Add parsers directory * Initial parser code * Compact syntax and supporting Model constructor * Format code * Update crate structure and imports * Add Conjure Error and edit crate structure (broken) * Possible import fix (@niklasdewally) Co-authored-by: Niklas Dewally * Clearly separate packages and workspace * Add default run target * Merge refactoring of project structure * Resolve import conflicts * Update Error code to use thiserror * Parse JSON in main * Assert manual and parsed models are equal * Format model output strings * Update output formatting * Fix format fails * Use thiserror to create From<> impl * Update gitignore * Update error and clean parsing code * Parse variable domains * Add integration test foundation * Cargo format * Parser syntax tweaks * Complete integration tests * Attempt CI fix * Silence test failures (for now) * Cargo format * Fix i32/i64 discrepancy * Fix build dependency versions --------- Co-authored-by: Niklas Dewally --- .github/workflows/oxide.yml | 6 + .vscode/settings.json | 4 + Cargo.lock | 430 +++++++++++++++++- conjure_oxide/Cargo.toml | 8 +- conjure_oxide/build.rs | 30 ++ conjure_oxide/src/ast.rs | 56 ++- conjure_oxide/src/error.rs | 12 + conjure_oxide/src/lib.rs | 5 + conjure_oxide/src/main.rs | 6 +- conjure_oxide/src/parse.rs | 113 +++++ conjure_oxide/tests/gen_test_template | 4 + conjure_oxide/tests/generated_tests.rs | 37 ++ .../tests/integration/xyz/input.essence | 3 + .../xyz/input.serialised.expected.json | 97 ++++ examples/abc.essence | 6 + examples/abc.json | 62 +++ solvers/chuffed/Cargo.toml | 2 +- solvers/minion/Cargo.toml | 1 - src/lib.rs | 6 + 19 files changed, 856 insertions(+), 32 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 conjure_oxide/build.rs create mode 100644 conjure_oxide/src/error.rs create mode 100644 conjure_oxide/src/parse.rs create mode 100644 conjure_oxide/tests/gen_test_template create mode 100644 conjure_oxide/tests/generated_tests.rs create mode 100644 conjure_oxide/tests/integration/xyz/input.essence create mode 100644 conjure_oxide/tests/integration/xyz/input.serialised.expected.json create mode 100644 examples/abc.essence create mode 100644 examples/abc.json create mode 100644 src/lib.rs diff --git a/.github/workflows/oxide.yml b/.github/workflows/oxide.yml index fcb232256..687c5cfea 100644 --- a/.github/workflows/oxide.yml +++ b/.github/workflows/oxide.yml @@ -113,6 +113,12 @@ jobs: - working-directory: ./conjure_oxide run: rustup update stable && rustup default stable + + - working-directory: ./conjure_oxide + run: | + wget https://github.com/conjure-cp/conjure/releases/download/v2.5.1/conjure-v2.5.1-linux-with-solvers.zip + unzip conjure-v2.5.1-linux-with-solvers.zip + echo "$(pwd)/conjure-v2.5.1-linux-with-solvers" >> ${GITHUB_PATH} - working-directory: ./conjure_oxide run: cargo test diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..03de4a74d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +"rust": { + "editor.defaultFormatter": "rust-lang.rust-analyzer", + "editor.formatOnSave": true +} diff --git a/Cargo.lock b/Cargo.lock index 8ea4f70d3..573ed9a22 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,12 +11,50 @@ dependencies = [ "memchr", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "assert-json-diff" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.21.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" + [[package]] name = "anyhow" version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + [[package]] name = "bindgen" version = "0.64.0" @@ -58,7 +96,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.38", + "syn 2.0.39", "which", ] @@ -74,12 +112,19 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + [[package]] name = "cc" -version = "1.0.84" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f8e7c90afad890484a21653d08b6e209ae34770fb5ee298f9c699fcc1e5c856" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ + "jobserver", "libc", ] @@ -98,6 +143,19 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "serde", + "windows-targets", +] + [[package]] name = "chuffed_rs" version = "0.1.0" @@ -121,8 +179,63 @@ dependencies = [ name = "conjure_oxide" version = "0.0.1" dependencies = [ - "json", + "assert-json-diff", "minion_rs", + "serde", + "serde_json", + "serde_with", + "thiserror", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "darling" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.39", +] + +[[package]] +name = "darling_macro" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "deranged" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" +dependencies = [ + "powerfmt", + "serde", ] [[package]] @@ -142,22 +255,52 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" +checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8" dependencies = [ "libc", "windows-sys", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "glob" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "home" version = "0.5.5" @@ -168,10 +311,79 @@ dependencies = [ ] [[package]] -name = "json" -version = "0.12.4" +name = "iana-time-zone" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown 0.14.2", + "serde", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "jobserver" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" +checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" +dependencies = [ + "wasm-bindgen", +] [[package]] name = "kissat-rs" @@ -213,9 +425,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.149" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "libloading" @@ -229,9 +441,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" +checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" [[package]] name = "log" @@ -257,7 +469,6 @@ version = "0.0.1" dependencies = [ "anyhow", "bindgen 0.69.1", - "cc", "glob", "thiserror", ] @@ -272,6 +483,15 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", +] + [[package]] name = "once_cell" version = "1.18.0" @@ -284,6 +504,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "prettyplease" version = "0.2.15" @@ -291,7 +517,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" dependencies = [ "proc-macro2", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -349,9 +575,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.38.21" +version = "0.38.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" +checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e" dependencies = [ "bitflags 2.4.1", "errno", @@ -360,12 +586,84 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "serde" +version = "1.0.192" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.192" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "serde_json" +version = "1.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64cd236ccc1b7a29e7e2739f27c0b2dd199804abc4290e32f59f3b68d6405c23" +dependencies = [ + "base64", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.1.0", + "serde", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93634eb5f75a2323b16de4748022ac4297f9e76b6dced2be287a099f41b5e788" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.39", +] + [[package]] name = "shlex" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "syn" version = "1.0.109" @@ -379,9 +677,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.38" +version = "2.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" dependencies = [ "proc-macro2", "quote", @@ -405,7 +703,36 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", +] + +[[package]] +name = "time" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" +dependencies = [ + "deranged", + "itoa", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" +dependencies = [ + "time-core", ] [[package]] @@ -414,6 +741,60 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "wasm-bindgen" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.39", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" + [[package]] name = "which" version = "4.4.2" @@ -448,6 +829,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.48.0" diff --git a/conjure_oxide/Cargo.toml b/conjure_oxide/Cargo.toml index 1bb161df7..e4ea59526 100644 --- a/conjure_oxide/Cargo.toml +++ b/conjure_oxide/Cargo.toml @@ -2,11 +2,15 @@ name = "conjure_oxide" version = "0.0.1" edition = "2021" +default-run = "conjure_oxide" [dependencies] -json = "0.12.4" +assert-json-diff = "2.0.2" +serde = { version = "1.0.192", features = ["derive"] } +serde_json = "1.0.108" +serde_with = "3.4.0" +thiserror = "1.0.50" minion_rs = {path = "../solvers/minion" } [lints] workspace = true - diff --git a/conjure_oxide/build.rs b/conjure_oxide/build.rs new file mode 100644 index 000000000..350f42380 --- /dev/null +++ b/conjure_oxide/build.rs @@ -0,0 +1,30 @@ +use std::{ + env::var, + fs::File, + fs::{read_dir, DirEntry}, + io::Write, + path::Path, +}; + +fn main() { + let out_dir = var("OUT_DIR").unwrap(); + let dest = Path::new(&out_dir).join("gen_tests.rs"); + let mut f = File::create(&dest).unwrap(); + + let test_dir = "tests/integration"; + for dir in read_dir(test_dir).unwrap() { + write_test(&mut f, &dir.unwrap()); + } +} + +fn write_test(file: &mut File, dir: &DirEntry) { + let binding = dir.path(); + let path = binding.to_str().unwrap(); + write!( + file, + include_str!("./tests/gen_test_template"), + name = path.replace("./", "").replace("/", "_"), + path = path + ) + .unwrap(); +} diff --git a/conjure_oxide/src/ast.rs b/conjure_oxide/src/ast.rs index c1eac5b61..cbe95cbf4 100644 --- a/conjure_oxide/src/ast.rs +++ b/conjure_oxide/src/ast.rs @@ -1,44 +1,88 @@ +use serde::{Deserialize, Serialize}; +use serde_with::serde_as; use std::collections::HashMap; +use std::fmt::Display; -#[derive(Debug)] +#[serde_as] +#[derive(Debug, PartialEq, Serialize, Deserialize)] pub struct Model { + #[serde_as(as = "Vec<(_, _)>")] pub variables: HashMap, pub constraints: Vec, } impl Model { + pub fn new() -> Model { + Model { + variables: HashMap::new(), + constraints: Vec::new(), + } + } // Function to update a DecisionVariable based on its Name pub fn update_domain(&mut self, name: &Name, new_domain: Domain) { if let Some(decision_var) = self.variables.get_mut(name) { decision_var.domain = new_domain; } } + // Function to add a new DecisionVariable to the Model + pub fn add_variable(&mut self, name: Name, decision_var: DecisionVariable) { + self.variables.insert(name, decision_var); + } +} + +impl Default for Model { + fn default() -> Self { + Self::new() + } } -#[derive(Clone, Debug, Eq, PartialEq, Hash)] +#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] pub enum Name { UserName(String), MachineName(i32), } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Serialize, Deserialize)] pub struct DecisionVariable { pub domain: Domain, } -#[derive(Clone, Debug, PartialEq)] +impl Display for DecisionVariable { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self.domain { + Domain::BoolDomain => write!(f, "bool"), + Domain::IntDomain(ranges) => { + let mut first = true; + for r in ranges { + if first { + first = false; + } else { + write!(f, " or ")?; + } + match r { + Range::Single(i) => write!(f, "{}", i)?, + Range::Bounded(i, j) => write!(f, "{}..{}", i, j)?, + } + } + Ok(()) + } + } + } +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum Domain { BoolDomain, IntDomain(Vec>), } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum Range { Single(A), Bounded(A, A), } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[non_exhaustive] pub enum Expression { ConstantInt(i32), diff --git a/conjure_oxide/src/error.rs b/conjure_oxide/src/error.rs new file mode 100644 index 000000000..2d107bcce --- /dev/null +++ b/conjure_oxide/src/error.rs @@ -0,0 +1,12 @@ +use serde_json::Error as JsonError; +use thiserror::Error; + +pub type Result = std::result::Result; + +#[derive(Debug, Error)] +pub enum Error { + #[error("JSON parsing error: {0}")] + JsonError(#[from] JsonError), + #[error("Error constructing model: {0}")] + ModelConstructError(String), +} diff --git a/conjure_oxide/src/lib.rs b/conjure_oxide/src/lib.rs index cec1c6424..e5257242e 100644 --- a/conjure_oxide/src/lib.rs +++ b/conjure_oxide/src/lib.rs @@ -1,2 +1,7 @@ pub mod ast; +pub mod error; +pub mod parse; + +pub use ast::Model; +pub use error::Error; mod solvers; diff --git a/conjure_oxide/src/main.rs b/conjure_oxide/src/main.rs index 4b843517e..9d2b7d103 100644 --- a/conjure_oxide/src/main.rs +++ b/conjure_oxide/src/main.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::{collections::HashMap, fs::File, io::Read}; use conjure_oxide::ast::*; @@ -7,6 +7,8 @@ fn main() { // such that a + b + c = 4 // such that a >= b + println!("Manual model:"); + let a = Name::UserName(String::from("a")); let b = Name::UserName(String::from("b")); let c = Name::UserName(String::from("c")); @@ -49,5 +51,5 @@ fn main() { ], }; - println!("{:?}\n", m); + println!("\n{:?}\n", m); } diff --git a/conjure_oxide/src/parse.rs b/conjure_oxide/src/parse.rs new file mode 100644 index 000000000..d7095f404 --- /dev/null +++ b/conjure_oxide/src/parse.rs @@ -0,0 +1,113 @@ +use crate::ast::{DecisionVariable, Domain, Model, Name, Range}; +use crate::error::{Error, Result}; +use serde_json::Value as JsonValue; + +use Error::ModelConstructError as CError; + +pub fn parse_json(str: &String) -> Result { + let mut m = Model::new(); + let v: JsonValue = serde_json::from_str(str)?; + let constraints = v["mStatements"] + .as_array() + .ok_or(CError("mStatements is not an array".to_owned()))?; + + for con in constraints { + let entry = con + .as_object() + .ok_or(CError("mStatements contains a non-object".to_owned()))? + .iter() + .next() + .ok_or(CError("mStatements contains an empty object".to_owned()))?; + match entry.0.as_str() { + "Declaration" => { + let (name, var) = parse_variable(entry.1)?; + m.add_variable(name, var); + } + "SuchThat" => parse_constraint(entry.1)?, + _ => return Err(CError("mStatements contains an unknown object".to_owned())), + } + } + + Ok(m) +} + +fn parse_variable(v: &JsonValue) -> Result<(Name, DecisionVariable)> { + let arr = v + .as_object() + .ok_or(CError("Declaration is not an object".to_owned()))?["FindOrGiven"] + .as_array() + .ok_or(CError("FindOrGiven is not an array".to_owned()))?; + let name = arr[1] + .as_object() + .ok_or(CError("FindOrGiven[1] is not an object".to_owned()))?["Name"] + .as_str() + .ok_or(CError("FindOrGiven[1].Name is not a string".to_owned()))?; + let name = Name::UserName(name.to_owned()); + let domain = arr[2] + .as_object() + .ok_or(CError("FindOrGiven[2] is not an object".to_owned()))? + .iter() + .next() + .ok_or(CError("FindOrGiven[2] is an empty object".to_owned()))?; + let domain = match domain.0.as_str() { + "DomainInt" => Ok(parse_int_domain(domain.1)?), + "DomainBool" => Ok(Domain::BoolDomain), + _ => Err(CError("FindOrGiven[2] is an unknown object".to_owned())), + }?; + Ok((name, DecisionVariable { domain })) +} + +fn parse_int_domain(v: &JsonValue) -> Result { + let mut ranges = Vec::new(); + let arr = v + .as_array() + .ok_or(CError("DomainInt is not an array".to_owned()))?[1] + .as_array() + .ok_or(CError("DomainInt[1] is not an array".to_owned()))?; + for range in arr { + let range = range + .as_object() + .ok_or(CError("DomainInt[1] contains a non-object".to_owned()))? + .iter() + .next() + .ok_or(CError("DomainInt[1] contains an empty object".to_owned()))?; + match range.0.as_str() { + "RangeBounded" => { + let arr = range + .1 + .as_array() + .ok_or(CError("RangeBounded is not an array".to_owned()))?; + let mut nums = Vec::new(); + for i in 0..2 { + let num = &arr[i]["Constant"]["ConstantInt"][1] + .as_i64() + .ok_or(CError("Could not parse int domain constant".to_owned()))?; + let num32 = i32::try_from(*num) + .map_err(|_| CError("Could not parse int domain constant".to_owned()))?; + nums.push(num32); + } + ranges.push(Range::Bounded(nums[0], nums[1])); + } + "RangeSingle" => { + let num = &range.1["Constant"]["ConstantInt"][1] + .as_i64() + .ok_or(CError("Could not parse int domain constant".to_owned()))?; + let num32 = i32::try_from(*num) + .map_err(|_| CError("Could not parse int domain constant".to_owned()))?; + ranges.push(Range::Single(num32)); + } + _ => return Err(CError("DomainInt[1] contains an unknown object".to_owned())), + } + } + Ok(Domain::IntDomain(ranges)) +} + +fn parse_constraint(obj: &JsonValue) -> Result<()> { + Ok(()) +} + +impl Model { + pub fn from_json(str: &String) -> Result { + parse_json(str) + } +} diff --git a/conjure_oxide/tests/gen_test_template b/conjure_oxide/tests/gen_test_template new file mode 100644 index 000000000..337c98a2d --- /dev/null +++ b/conjure_oxide/tests/gen_test_template @@ -0,0 +1,4 @@ +#[test] +fn {name}() -> Result<(), Box> {{ + integration_test("{path}") +}} diff --git a/conjure_oxide/tests/generated_tests.rs b/conjure_oxide/tests/generated_tests.rs new file mode 100644 index 000000000..56895cff6 --- /dev/null +++ b/conjure_oxide/tests/generated_tests.rs @@ -0,0 +1,37 @@ +use assert_json_diff::assert_json_eq; +use std::error::Error; +use std::fs::File; +use std::io::prelude::*; + +use conjure_oxide::ast::Model; + +fn integration_test(path: &str) -> Result<(), Box> { + println!("Integration test for: {}", path); + let mut cmd = std::process::Command::new("conjure"); + let output = cmd + .arg("pretty") + .arg("--output-format=astjson") + .arg(format!("{path}/input.essence", path = path)) + .output()?; + let astjson = String::from_utf8(output.stdout)?; + let generated_mdl = Model::from_json(&astjson)?; + + let mut expected_str = String::new(); + let mut f = File::open(format!( + "{path}/input.serialised.expected.json", + path = path + ))?; + f.read_to_string(&mut expected_str)?; + let mut expected_mdl: Model = serde_json::from_str(&expected_str)?; + expected_mdl.constraints = Vec::new(); // TODO - remove this line once we parse constraints + + println!("Expected {:#?}", expected_mdl); + println!("\nActual {:#?}", generated_mdl); + + // assert_json_eq!(serde_json::to_string_pretty(&generated_mdl)?, expected_str); // TODO - replace line once we parse constraints + assert_eq!(generated_mdl, expected_mdl); + + Ok(()) +} + +include!(concat!(env!("OUT_DIR"), "/gen_tests.rs")); diff --git a/conjure_oxide/tests/integration/xyz/input.essence b/conjure_oxide/tests/integration/xyz/input.essence new file mode 100644 index 000000000..7e77f1889 --- /dev/null +++ b/conjure_oxide/tests/integration/xyz/input.essence @@ -0,0 +1,3 @@ +find a,b,c : int(1..3) +such that a + b + c = 4 +such that a >= b diff --git a/conjure_oxide/tests/integration/xyz/input.serialised.expected.json b/conjure_oxide/tests/integration/xyz/input.serialised.expected.json new file mode 100644 index 000000000..905e956aa --- /dev/null +++ b/conjure_oxide/tests/integration/xyz/input.serialised.expected.json @@ -0,0 +1,97 @@ +{ + "variables": [ + [ + { + "UserName": "b" + }, + { + "domain": { + "IntDomain": [ + { + "Bounded": [ + 1, + 3 + ] + } + ] + } + } + ], + [ + { + "UserName": "a" + }, + { + "domain": { + "IntDomain": [ + { + "Bounded": [ + 1, + 3 + ] + } + ] + } + } + ], + [ + { + "UserName": "c" + }, + { + "domain": { + "IntDomain": [ + { + "Bounded": [ + 1, + 3 + ] + } + ] + } + } + ] + ], + "constraints": [ + { + "Eq": [ + { + "Sum": [ + { + "Reference": { + "UserName": "a" + } + }, + { + "Reference": { + "UserName": "b" + } + }, + { + "Reference": { + "UserName": "c" + } + } + ] + }, + { + "ConstantInt": 4 + } + ] + }, + { + "Geq": [ + { + "Reference": { + "UserName": "a" + } + }, + { + "Reference": { + "UserName": "b" + } + } + ] + } + ] +} diff --git a/examples/abc.essence b/examples/abc.essence new file mode 100644 index 000000000..3624fc91f --- /dev/null +++ b/examples/abc.essence @@ -0,0 +1,6 @@ +find a,b : int(1..3) +find c : int(1,2,3) +such that a + b + c = 4 +such that a >= b + +$ conjure pretty --output-format=astjson examples/abc.essence > examples/abc.json diff --git a/examples/abc.json b/examples/abc.json new file mode 100644 index 000000000..30a6d503c --- /dev/null +++ b/examples/abc.json @@ -0,0 +1,62 @@ +{"mInfo": + {"finds": [], "givens": [], "enumGivens": [], "enumLettings": [], "lettings": [], "unnameds": [], + "strategyQ": {"Auto": {"Interactive": []}}, "strategyA": {"Auto": {"Interactive": []}}, "trailCompact": [], + "nameGenState": [], "nbExtraGivens": 0, "representations": [], "representationsTree": [], "originalDomains": [], + "trailGeneralised": [], "trailVerbose": [], "trailRewrites": []}, + "mLanguage": {"language": {"Name": "Essence"}, "version": [1, 3]}, + "mStatements": + [{"Declaration": + {"FindOrGiven": + ["Find", {"Name": "a"}, + {"DomainInt": + [{"TagInt": []}, + [{"RangeBounded": + [{"Constant": {"ConstantInt": [{"TagInt": []}, 1]}}, + {"Constant": {"ConstantInt": [{"TagInt": []}, 3]}}]}]]}]}}, + {"Declaration": + {"FindOrGiven": + ["Find", {"Name": "b"}, + {"DomainInt": + [{"TagInt": []}, + [{"RangeBounded": + [{"Constant": {"ConstantInt": [{"TagInt": []}, 1]}}, + {"Constant": {"ConstantInt": [{"TagInt": []}, 3]}}]}]]}]}}, + {"Declaration": + {"FindOrGiven": + ["Find", {"Name": "c"}, + {"DomainInt": + [{"TagInt": []}, + [{"RangeSingle": {"Constant": {"ConstantInt": [{"TagInt": []}, 1]}}}, + {"RangeSingle": {"Constant": {"ConstantInt": [{"TagInt": []}, 2]}}}, + {"RangeSingle": {"Constant": {"ConstantInt": [{"TagInt": []}, 3]}}}]]}]}}, + {"SuchThat": + [{"Op": + {"MkOpEq": + [{"Op": + {"MkOpSum": + {"AbstractLiteral": + {"AbsLitMatrix": + [{"DomainInt": + [{"TagInt": []}, + [{"RangeBounded": + [{"Constant": {"ConstantInt": [{"TagInt": []}, 1]}}, + {"Constant": {"ConstantInt": [{"TagInt": []}, 2]}}]}]]}, + [{"Op": + {"MkOpSum": + {"AbstractLiteral": + {"AbsLitMatrix": + [{"DomainInt": + [{"TagInt": []}, + [{"RangeBounded": + [{"Constant": + {"ConstantInt": + [{"TagInt": []}, 1]}}, + {"Constant": + {"ConstantInt": + [{"TagInt": []}, 2]}}]}]]}, + [{"Reference": [{"Name": "a"}, null]}, + {"Reference": [{"Name": "b"}, null]}]]}}}}, + {"Reference": [{"Name": "c"}, null]}]]}}}}, + {"Constant": {"ConstantInt": [{"TagInt": []}, 4]}}]}}]}, + {"SuchThat": + [{"Op": {"MkOpGeq": [{"Reference": [{"Name": "a"}, null]}, {"Reference": [{"Name": "b"}, null]}]}}]}]} diff --git a/solvers/chuffed/Cargo.toml b/solvers/chuffed/Cargo.toml index 53acaae18..0f3b4a50d 100644 --- a/solvers/chuffed/Cargo.toml +++ b/solvers/chuffed/Cargo.toml @@ -9,5 +9,5 @@ edition = "2021" [dependencies] [build-dependencies] -cc = { version = "1.0.84", features = ["parallel"] } +cc = { version = "1.0.83", features = ["parallel"] } bindgen = "0.69.1" diff --git a/solvers/minion/Cargo.toml b/solvers/minion/Cargo.toml index d1600f5c6..d814f97de 100644 --- a/solvers/minion/Cargo.toml +++ b/solvers/minion/Cargo.toml @@ -10,7 +10,6 @@ anyhow = "1.0.75" thiserror = "1.0.50" [build-dependencies] -cc = { version = "1.0.84", features = ["parallel"] } bindgen = "0.69.1" glob = "0.3.1" diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 000000000..d9559cebb --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,6 @@ +pub mod ast; +pub mod error; +pub mod parse; + +pub use ast::Model; +pub use error::Error;