diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 93e19bb3..fb9fad0a 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -17,6 +17,20 @@ jobs: - name: Build run: make web + # Upload the built website as an artifact, so that runs which are not deployed + # (i.e. other branches and PRs) to Github Pages can be be downloaded + # (https://docs.github.com/en/actions/managing-workflow-runs/downloading-workflow-artifacts) + # and viewed locally. + # + # When Github adds support for PR Github Pages previews + # (https://github.com/orgs/community/discussions/7730) + # this can be removed. + - name: Upload web artifact + uses: actions/upload-artifact@v3 + with: + name: www + path: target/www + - name: Deploy uses: peaceiris/actions-gh-pages@v3 # only actually deploy if pushed to main branch @@ -24,4 +38,4 @@ jobs: with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: target/www - force_orphan: true \ No newline at end of file + force_orphan: true diff --git a/.gitignore b/.gitignore index 2ec2b6a0..9f318ed2 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,9 @@ profile.json _scratch.egg /*.egg *.log +*.dot +*.svg +*.DS_Store # racket -scripts/compiled \ No newline at end of file +scripts/compiled diff --git a/Cargo.lock b/Cargo.lock index b2d8ef8a..cb023f2e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -130,6 +130,15 @@ version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "bumpalo" version = "3.13.0" @@ -186,7 +195,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.25", ] [[package]] @@ -211,18 +220,47 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "cpufeatures" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +dependencies = [ + "libc", +] + [[package]] name = "crunchy" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "diff" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "dirs-next" version = "2.0.0" @@ -244,6 +282,21 @@ dependencies = [ "winapi", ] +[[package]] +name = "dot-generator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aaac7ada45f71873ebce336491d1c1bc4a7c8042c7cea978168ad59e805b871" +dependencies = [ + "dot-structures", +] + +[[package]] +name = "dot-structures" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "675e35c02a51bb4d4618cb4885b3839ce6d1787c97b664474d9208d074742e20" + [[package]] name = "egglog" version = "0.1.0" @@ -251,6 +304,7 @@ dependencies = [ "clap", "egraph-serialize", "env_logger", + "getrandom", "glob", "hashbrown 0.14.0", "indexmap 2.0.0", @@ -276,8 +330,9 @@ dependencies = [ [[package]] name = "egraph-serialize" version = "0.1.0" -source = "git+https://github.com/egraphs-good/egraph-serialize?rev=54b1a4f1e2f2135846b084edcb495cd159839540#54b1a4f1e2f2135846b084edcb495cd159839540" +source = "git+https://github.com/egraphs-good/egraph-serialize?rev=e406ffcec8c6e841089fd3e4f9b76c35ce448950#e406ffcec8c6e841089fd3e4f9b76c35ce448950" dependencies = [ + "graphviz-rust", "indexmap 2.0.0", "once_cell", "ordered-float", @@ -340,12 +395,28 @@ dependencies = [ "libc", ] +[[package]] +name = "fastrand" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" + [[package]] name = "fixedbitset" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.10" @@ -353,8 +424,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if 1.0.0", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -363,6 +436,22 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "graphviz-rust" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27dafd1ac303e0dfb347a3861d9ac440859bab26ec2f534bbceb262ea492a1e0" +dependencies = [ + "dot-generator", + "dot-structures", + "into-attr", + "into-attr-derive", + "pest", + "pest_derive", + "rand", + "tempfile", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -433,6 +522,28 @@ dependencies = [ "web-sys", ] +[[package]] +name = "into-attr" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18b48c537e49a709e678caec3753a7dba6854661a1eaa27675024283b3f8b376" +dependencies = [ + "dot-structures", +] + +[[package]] +name = "into-attr-derive" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecac7c1ae6cd2c6a3a64d1061a8bdc7f52ff62c26a831a2301e54c1b5d70d5b1" +dependencies = [ + "dot-generator", + "dot-structures", + "into-attr", + "quote", + "syn 1.0.109", +] + [[package]] name = "is-terminal" version = "0.4.9" @@ -655,6 +766,50 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "pest" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1acb4a4365a13f749a93f1a094a7805e5cfa0955373a9de860d962eaa3a5fe5a" +dependencies = [ + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "666d00490d4ac815001da55838c500eafb0320019bbaa44444137c48b443a853" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ca01446f50dbda87c1786af8770d535423fa8a53aec03b8f4e3d7eb10e0929" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.25", +] + +[[package]] +name = "pest_meta" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56af0a30af74d0445c0bf6d9d051c979b516a1a5af790d251daee76005420a48" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + [[package]] name = "petgraph" version = "0.6.3" @@ -680,6 +835,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "precomputed-hash" version = "0.1.1" @@ -710,16 +871,29 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ + "libc", + "rand_chacha", "rand_core", "serde", ] +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ + "getrandom", "serde", ] @@ -835,7 +1009,7 @@ checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.25", ] [[package]] @@ -850,6 +1024,17 @@ dependencies = [ "serde", ] +[[package]] +name = "sha2" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest", +] + [[package]] name = "siphasher" version = "0.3.10" @@ -895,6 +1080,17 @@ name = "symbolic_expressions" version = "5.0.3" source = "git+https://github.com/oflatt/symbolic-expressions?rev=4c0ea5ca008f972450b2af72387e64d2c1c6a791#4c0ea5ca008f972450b2af72387e64d2c1c6a791" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.25" @@ -906,6 +1102,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc02fddf48964c42031a0b3fe0428320ecf3a73c401040fc0096f97794310651" +dependencies = [ + "cfg-if 1.0.0", + "fastrand", + "redox_syscall 0.3.5", + "rustix", + "windows-sys", +] + [[package]] name = "term" version = "0.7.0" @@ -943,7 +1152,7 @@ checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.25", ] [[package]] @@ -964,6 +1173,18 @@ dependencies = [ "crunchy", ] +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + [[package]] name = "unicode-ident" version = "1.0.10" @@ -1015,7 +1236,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.25", "wasm-bindgen-shared", ] @@ -1037,7 +1258,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.25", "wasm-bindgen-backend", "wasm-bindgen-shared", ] diff --git a/Cargo.toml b/Cargo.toml index 460f5075..7099d033 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ name = "files" default = ["bin"] bin = ["dep:clap", "dep:env_logger", "egraph-serialize/serde", "dep:serde_json"] -wasm-bindgen = ["instant/wasm-bindgen"] +wasm-bindgen = ["instant/wasm-bindgen", "dep:getrandom"] [dependencies] hashbrown = {version = "0.14", features = ["raw"]} @@ -33,7 +33,7 @@ num-traits = "0.2.15" smallvec = "1.11" symbolic_expressions = {git = "https://github.com/oflatt/symbolic-expressions", rev = "4c0ea5ca008f972450b2af72387e64d2c1c6a791"} -egraph-serialize = {git = "https://github.com/egraphs-good/egraph-serialize", rev = "54b1a4f1e2f2135846b084edcb495cd159839540", default-features = false} +egraph-serialize = {git = "https://github.com/egraphs-good/egraph-serialize", rev = "e406ffcec8c6e841089fd3e4f9b76c35ce448950", features = ["serde", "graphviz"]} serde_json ={optional=true, version = "1.0.100", features = ["preserve_order"]} lalrpop-util = {version = "0.20", features = ["lexer"]} @@ -45,6 +45,9 @@ env_logger = {version = "0.10", optional = true} ordered-float = {version = "3.7"} +# Need to add "js" feature for "graphviz-rust" to work in wasm +getrandom = {version="*", features = ["js"], optional = true} + [build-dependencies] lalrpop = "0.20" diff --git a/Makefile b/Makefile index be765806..f67c3a88 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: all web test nits docs serve +.PHONY: all web test nits docs serve graphs rm-graphs RUST_SRC=$(shell find . -type f -wholename '*/src/*.rs' -or -name 'Cargo.toml') TESTS=$(shell find tests/ -type f -name '*.egg' -not -name '*repro-*') @@ -34,7 +34,7 @@ web: docs ${DIST_WASM} ${WEB_SRC} ${WWW}/examples.json cp ${WEB_SRC} ${WWW} find target -name .gitignore -delete # ignored files are wonky to deploy -serve: +serve: cargo watch --shell "make web && python3 -m http.server 8080 -d ${WWW}" ${WWW}/examples.json: web-demo/examples.py ${TESTS} @@ -44,4 +44,10 @@ ${DIST_WASM}: ${RUST_SRC} wasm-pack build web-demo --target no-modules --no-typescript --out-dir ${WWW} rm -f ${WWW}/{.gitignore,package.json} +graphs: $(patsubst %.egg,%.svg,$(filter-out $(wildcard tests/repro-*.egg),$(wildcard tests/*.egg))) +%.svg: %.egg + cargo run --release -- --to-dot --to-svg $^ + +rm-graphs: + rm -f tests/*.dot tests/*.svg diff --git a/README.md b/README.md index b43e1f05..672efcfd 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ make all ## Usage ``` -cargo run [-f fact-path] [-naive] [--to-json] +cargo run [-f fact-path] [-naive] [--to-json] [--to-dot] [--to-svg] ``` or just @@ -42,6 +42,10 @@ cargo run for the REPL. +* The `--to-dot` command will save a graphviz dot file at the end of the program, replacing the `.egg` extension with `.dot`. +* The `--to-svg`, which requires [Graphviz to be installed](https://graphviz.org/download/), will save a graphviz svg file at the end of the program, replacing the `.egg` extension with `.svg`. + + ## VS Code plugin There is a VS Code extension in the vscode folder. Install using 'Install from VSIX...' in the three-dot menu of the extensions tab and pick `vscode/vscode/egglog.vsix`. diff --git a/src/lib.rs b/src/lib.rs index d4f06cf5..4623cb93 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1236,15 +1236,22 @@ impl EGraph { self.proof_state.type_info.add_arcsort(arcsort) } - // Gets the last extract report and returns it, if the last command saved it. + /// Gets the last extract report and returns it, if the last command saved it. pub fn get_extract_report(&self) -> &Option { &self.extract_report } - // Gets the last run report and returns it, if the last command saved it. + /// Gets the last run report and returns it, if the last command saved it. pub fn get_run_report(&self) -> &Option { &self.run_report } + + /// Serializes the egraph for export to graphviz. + pub fn serialize_for_graphviz(&self) -> egraph_serialize::EGraph { + let mut serialized = self.serialize(SerializeConfig::default()); + serialized.inline_leaves(); + serialized + } } #[derive(Debug, Error)] diff --git a/src/main.rs b/src/main.rs index 01c28154..1b7c1325 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,6 +23,10 @@ struct Args { inputs: Vec, #[clap(long)] to_json: bool, + #[clap(long)] + to_dot: bool, + #[clap(long)] + to_svg: bool, } fn main() { @@ -120,6 +124,17 @@ fn main() { serialized.to_json_file(json_path).unwrap(); } + if args.to_dot || args.to_svg { + let serialized = egraph.serialize_for_graphviz(); + if args.to_dot { + let dot_path = input.with_extension("dot"); + serialized.to_dot_file(dot_path).unwrap() + } + if args.to_svg { + let svg_path = input.with_extension("svg"); + serialized.to_svg_file(svg_path).unwrap() + } + } // no need to drop the egraph if we are going to exit if idx == args.inputs.len() - 1 { std::mem::forget(egraph) diff --git a/src/serialize.rs b/src/serialize.rs index 4c485a4b..8d9b162e 100644 --- a/src/serialize.rs +++ b/src/serialize.rs @@ -51,7 +51,7 @@ impl EGraph { /// - Functions which return primitive values will be added to the e-class of that value. /// - Nodes will have consistant IDs throughout execution of e-graph (used for animating changes in the visualization) /// - Edges in the visualization will be well distributed (used for animating changes in the visualization) - /// (Note that this will be changed in https://github.com/egraphs-good/egglog/pull/158 so that edges point to exact nodes instead of looking up the e-class) + /// (Note that this will be changed in `` so that edges point to exact nodes instead of looking up the e-class) pub fn serialize(&self, config: SerializeConfig) -> egraph_serialize::EGraph { // First collect a list of all the calls we want to serialize, into the function decl, the inputs, and the output, and if its an eq sort let all_calls: Vec<(&FunctionDecl, &ValueVec, &Value, egraph_serialize::NodeId)> = self diff --git a/tests/eqsat-basic.egg b/tests/eqsat-basic.egg index f6bead84..4831dbf7 100644 --- a/tests/eqsat-basic.egg +++ b/tests/eqsat-basic.egg @@ -5,7 +5,7 @@ (Mul Math Math)) ;; expr1 = 2 * (x + 3) -(let expr1 (Mul (Num 2) (Add (Var "x") (Num 3)))) +(let expr1 (Mul (Num 2) (Add (Var "x") (Num 3)))) ;; expr2 = 6 + 2 * x (let expr2 (Add (Num 6) (Mul (Num 2) (Var "x")))) @@ -16,10 +16,10 @@ (Add b a)) (rewrite (Mul a (Add b c)) (Add (Mul a b) (Mul a c))) -(rewrite (Add (Num a) (Num b)) +(rewrite (Add (Num a) (Num b)) (Num (+ a b))) (rewrite (Mul (Num a) (Num b)) (Num (* a b))) (run 10) -(check (= expr1 expr2)) \ No newline at end of file +(check (= expr1 expr2)) diff --git a/tests/files.rs b/tests/files.rs index b5d64996..cbb46615 100644 --- a/tests/files.rs +++ b/tests/files.rs @@ -63,6 +63,8 @@ impl Run { for msg in msgs { log::info!(" {}", msg); } + // Test graphviz dot generation + egraph.serialize_for_graphviz().to_dot(); } } Err(err) => { diff --git a/web-demo/src/lib.rs b/web-demo/src/lib.rs index 0e756465..a5f10af3 100644 --- a/web-demo/src/lib.rs +++ b/web-demo/src/lib.rs @@ -4,17 +4,30 @@ use wasm_bindgen::prelude::*; #[global_allocator] static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; +#[wasm_bindgen(getter_with_clone)] +pub struct Result { + pub text: String, + pub dot: String, +} + #[wasm_bindgen] -pub fn run_program(input: &str) -> String { +pub fn run_program(input: &str) -> Result { let mut egraph = egglog::EGraph::default(); match egraph.parse_and_run_program(input) { Ok(outputs) => { log::info!("egg ok, {} outputs", outputs.len()); - outputs.join("
") + let serialized = egraph.serialize_for_graphviz(); + Result { + text: outputs.join("
"), + dot: serialized.to_dot(), + } } Err(e) => { log::info!("egg failed"); - e.to_string() + Result { + text: e.to_string(), + dot: "".to_string(), + } } } } diff --git a/web-demo/static/index.html b/web-demo/static/index.html index 6562ec52..63133cd6 100644 --- a/web-demo/static/index.html +++ b/web-demo/static/index.html @@ -25,7 +25,6 @@ } #panel { - padding: 10px; flex: 1 1 0; border-left: 2px solid gray; @@ -35,6 +34,7 @@ #toolbar { margin-top: -5px; + padding: 10px; } #toolbar button, @@ -47,31 +47,68 @@ } #output { + padding-left: 10px; font-family: monospace; - margin-top: 10px; - flex-grow: 1; white-space: pre-wrap; overflow-y: scroll; + resize: vertical; + height: 60%; } .right { float: right; } + + #graph { + border-top: 2px solid gray; + flex-grow: 1; + position: relative; + overflow: auto; + } + /* Don't let graph expand https://stackoverflow.com/a/38852981/907060 */ + #graph-inner { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + } - +