├── .config └── nextest.toml ├── .github ├── CODEOWNERS └── workflows │ ├── build.yml │ └── deploy.yml ├── .gitignore ├── CHANGELOG.md ├── CITATION.bib ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── Makefile ├── README.md ├── benches └── example_benchmarks.rs ├── build.rs ├── clippy.toml ├── release-instructions.md ├── rust-toolchain.toml ├── scripts └── minimize.rkt ├── src ├── actions.rs ├── ast │ ├── check_shadowing.rs │ ├── desugar.rs │ ├── expr.rs │ ├── mod.rs │ ├── parse.rs │ └── remove_globals.rs ├── cli.rs ├── constraint.rs ├── core.rs ├── extract.rs ├── function │ ├── binary_search.rs │ ├── index.rs │ ├── mod.rs │ └── table.rs ├── gj.rs ├── lib.rs ├── main.rs ├── serialize.rs ├── sort │ ├── bigint.rs │ ├── bigrat.rs │ ├── bool.rs │ ├── f64.rs │ ├── fn.rs │ ├── i64.rs │ ├── macros.rs │ ├── map.rs │ ├── mod.rs │ ├── multiset.rs │ ├── set.rs │ ├── string.rs │ ├── unit.rs │ └── vec.rs ├── termdag.rs ├── typechecking.rs ├── unionfind.rs ├── util.rs └── value.rs ├── tests ├── antiunify.egg ├── array.egg ├── bdd.egg ├── before-proofs.egg ├── bignum.egg ├── birewrite.egg ├── bitwise.egg ├── bool.egg ├── calc.egg ├── combinators.egg ├── combined-nested.egg ├── container-rebuild.egg ├── cyk.egg ├── cykjson.egg ├── cykjson_End.csv ├── cykjson_Prod.csv ├── cykjson_medium_token.csv ├── cykjson_small_token.csv ├── datatypes.egg ├── delete.egg ├── eggcc-extraction.egg ├── eqsat-basic-multiset.egg ├── eqsat-basic.egg ├── eqsolve.egg ├── f64.egg ├── fail-typecheck │ ├── arity-mismatch.egg │ ├── constructor_non_sort.egg │ ├── looking_up_nonconstructor_in_action_case_let.egg │ ├── looking_up_nonconstructor_in_action_case_set.egg │ ├── looking_up_nonconstructor_in_action_case_union.egg │ ├── looking_up_nonconstructor_in_birewrite.egg │ ├── looking_up_nonconstructor_in_rewrite.egg │ ├── repro-containers-disallowed.egg │ ├── repro-duplicated-var.egg │ ├── semi_naive_set_function.egg │ ├── set-a-primitive.egg │ ├── unbound.egg │ ├── union_non_sort.egg │ ├── unstable-fn-wrong-args-type.egg │ ├── unstable-fn-wrong-args.egg │ ├── unstable-fn-wrong-return-type.egg │ └── unstable-fn-wrong-return.egg ├── fail_wrong_assertion.egg ├── fibonacci-demand.egg ├── fibonacci.egg ├── files.rs ├── fusion.egg ├── herbie-tutorial.egg ├── herbie.egg ├── i64.egg ├── include.egg ├── integer_math.egg ├── integration_test.rs ├── intersection.egg ├── interval.egg ├── knapsack.egg ├── lambda.egg ├── levenshtein-distance.egg ├── list.egg ├── looking_up_global.egg ├── looking_up_nonconstructor_in_rewrite_good.egg ├── map.egg ├── math-microbenchmark.egg ├── math.egg ├── matrix.egg ├── merge-during-rebuild.egg ├── merge-saturates.egg ├── merge_read.egg ├── multiset.egg ├── name-resolution.egg ├── no-messages │ ├── README.md │ ├── extract-vec-bench.egg │ ├── python_array_optimize.egg │ └── stresstest_large_expr.egg ├── path-union.egg ├── path.egg ├── pathproof.egg ├── points-to.egg ├── primitives.egg ├── prims.egg ├── push-pop.egg ├── rat-pow-eval.egg ├── repro-define.egg ├── repro-desugar-143.egg ├── repro-empty-query.egg ├── repro-equal-constant.egg ├── repro-equal-constant2.egg ├── repro-noteqbug.egg ├── repro-primitive-query.egg ├── repro-querybug.egg ├── repro-querybug2.egg ├── repro-querybug3.egg ├── repro-querybug4.egg ├── repro-should-saturate.egg ├── repro-silly-panic.egg ├── repro-typechecking-schedule.egg ├── repro-unsound-htutorial.egg ├── repro-unsound.egg ├── repro-vec-unequal.egg ├── resolution.egg ├── rw-analysis.egg ├── schedule-demo.egg ├── set.egg ├── set_sort_function.egg ├── stratified.egg ├── string.egg ├── string_quotes.csv ├── string_quotes.egg ├── subsume-relation.egg ├── subsume.egg ├── terms.rs ├── test-combined-steps.egg ├── test-combined.egg ├── towers-of-hanoi.egg ├── tricky-type-checking.egg ├── type-constraints-tests.egg ├── typecheck.egg ├── typeinfer.egg ├── unification-points-to.egg ├── unify.egg ├── unstable-fn.egg ├── until.egg └── vec.egg └── web-demo ├── Cargo.toml ├── examples.py ├── src └── lib.rs └── static ├── base64.mjs ├── index.html ├── lzma-url.mjs └── worker.js /.config/nextest.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | slow-timeout.period = "5s" 3 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @egraphs-good/egglog-reviewers -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - "main" 7 | pull_request: 8 | workflow_dispatch: 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - run: echo "CARGO_INCREMENTAL=0" >> "$GITHUB_ENV" 14 | - uses: actions/checkout@v3 15 | - uses: taiki-e/install-action@v2 16 | with: 17 | tool: nextest 18 | - uses: Swatinem/rust-cache@v2 19 | - run: make test 20 | nits: 21 | runs-on: ubuntu-latest 22 | steps: 23 | - run: echo "CARGO_INCREMENTAL=0" >> "$GITHUB_ENV" 24 | - uses: actions/checkout@v3 25 | - uses: Swatinem/rust-cache@v2 26 | - run: make nits 27 | benchmark: 28 | runs-on: ubuntu-latest 29 | steps: 30 | - run: echo "CARGO_INCREMENTAL=0" >> "$GITHUB_ENV" 31 | - uses: actions/checkout@v4 32 | - uses: taiki-e/install-action@v2 33 | with: 34 | tool: cargo-codspeed 35 | - uses: Swatinem/rust-cache@v2 36 | - run: cargo codspeed build 37 | - uses: CodSpeedHQ/action@v3 38 | with: 39 | run: cargo codspeed run 40 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Web Demo and Docs 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Disable Incremental Build 10 | run: echo "CARGO_INCREMENTAL=0" >> "$GITHUB_ENV" 11 | 12 | - uses: actions/checkout@v3 13 | 14 | - name: Install wasm-pack 15 | run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh 16 | 17 | - name: Cache 18 | uses: Swatinem/rust-cache@v2 19 | 20 | - name: Build 21 | run: make web 22 | 23 | # Upload the built website as an artifact, so that runs which are not deployed 24 | # (i.e. other branches and PRs) to Github Pages can be be downloaded 25 | # (https://docs.github.com/en/actions/managing-workflow-runs/downloading-workflow-artifacts) 26 | # and viewed locally. 27 | # 28 | # When Github adds support for PR Github Pages previews 29 | # (https://github.com/orgs/community/discussions/7730) 30 | # this can be removed. 31 | - name: Upload web artifact 32 | uses: actions/upload-artifact@v4 33 | with: 34 | name: www 35 | path: target/www 36 | 37 | - name: Deploy 38 | uses: peaceiris/actions-gh-pages@v3 39 | # only actually deploy if pushed to main branch 40 | if: ${{ github.ref == 'refs/heads/main' && github.event_name == 'push' }} 41 | with: 42 | github_token: ${{ secrets.GITHUB_TOKEN }} 43 | publish_dir: target/www 44 | force_orphan: true 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | notes.md 3 | .vscode 4 | node_modules 5 | dist/ 6 | pkg/ 7 | 8 | # perf stuff 9 | flamegraph.svg 10 | perf.data* 11 | profile.json 12 | 13 | _scratch.egg 14 | /*.egg 15 | *.log 16 | *.dot 17 | *.svg 18 | *.DS_Store 19 | 20 | # racket 21 | scripts/compiled 22 | tests/*.json 23 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changes 2 | 3 | ## [Unreleased] - ReleaseDate 4 | 5 | ## [0.4.0] - 2024-1-TODO 6 | 7 | Semantic change (BREAKING) 8 | 9 | - Split `function` into `constructor` and `functions` with merge functions. (#461) 10 | - Remove `:default` keyword. (#461) 11 | - Disallow lookup functions in the right hand side. (#461) 12 | - Remove `:on_merge`, `:cost`, and `:unextractable` from functions, require `:no-merge` (#485) 13 | 14 | Language features 15 | 16 | - Add multi-sets (#446, #454, #471) 17 | - Recursive datatypes with `datatype*` (#432) 18 | - Add `BigInt` and `BigRat` and move `Rational` to `egglog-experimental` (#457, #475, #499) 19 | 20 | Command-line interface and web demo 21 | 22 | - Display build info when in binary mode (#427) 23 | - Expose egglog CLI (#507, #510) 24 | - Add a new interactive visualizer (#426) 25 | - Disable build script for library builds (#467) 26 | 27 | Rust interface improvements 28 | 29 | - Make the type constraint system user-extensible (#509) 30 | - New extensible parser (#435, #450, #484, #489, #497, #498, #506) 31 | - Remove `Value::tag` when in release mode (#448) 32 | 33 | Extraction 34 | 35 | - Remove unused 'serde-1' attribute (#465) 36 | - Extract egraph-serialize features (#466) 37 | - Expose extraction module publicly (#503) 38 | - Use `set-of` instead of `set-insert` for extraction result of sets. (#514) 39 | 40 | Bug fixes 41 | 42 | - Fix the behavior of i64 primitives on overflow (#502) 43 | - Fix memory blowup issue in `TermDag::to_string` 44 | - Fix the issue that rule names are ignored (#500) 45 | 46 | Cleanups and improvements 47 | 48 | - Allow disabling messages for performance (#492) 49 | - Determinize egglog (#438, #439) 50 | - Refactor sort extraction API (#495) 51 | - Add automated benchmarking to continuous integration (#443) 52 | - Improvements to performance of testing (#458) 53 | - Other small cleanups and improvements (#428, #429, #433, #434, #436, #437, #440, #442, #444, #445, #449, #453, #456, #469, #474, #477, #490, #491, #494, #501, #504, #508, #511) 54 | 55 | ## [0.3.0] - 2024-9-12 56 | 57 | Cleanups 58 | 59 | - Remove `declare` and `calc` keywords (#418, #419) 60 | - Fix determinism bug from new combined ruleset code (#406) 61 | - Fix performance bug in typechecking containers (#395) 62 | - Minor improvements to the web demo (#413, #414, #415) 63 | - Add power operators to i64 and f64 (#412) 64 | 65 | Error reporting 66 | 67 | - Report the source locations for errors (#389, #398, #405) 68 | 69 | Serialization 70 | 71 | - Include subsumption information in serialization (#424) 72 | - Move splitting primitive nodes into the serialize library (#407) 73 | - Support omitted nodes (#394) 74 | - Support Class ID <-> Value conversion (#396) 75 | 76 | REPL 77 | 78 | - Evaluate multiple lines at once (#402) 79 | - Show build information in the REPL (#427) 80 | 81 | Higher-order functions (UNSTABLE) 82 | 83 | - Infer types of function values based on names (#400) 84 | 85 | Import relation from files 86 | 87 | - Accept f64 function arguments #384 88 | 89 | ## [0.2.0] - 2024-05-17 90 | 91 | Usability 92 | 93 | - Improve statistics for runs (#284) 94 | - Improve user-defined primitive support (#280, #288) 95 | - Improve serialization (#293) 96 | - Add more container primitives (#306) 97 | 98 | Web demo 99 | 100 | - Add slidemode in the web demo (#302) 101 | - Fix box shadowing problem (#372) 102 | 103 | Refactor 104 | 105 | - Big refactoring to the intermediate representation (#320) 106 | - Make global variables a syntactic sugar (#338) 107 | - Drop experimental implementation for proofs and terms (#320, #342) 108 | 109 | New features 110 | 111 | - Support Subsumptions (#301) 112 | - Add basic support for first-class, higher-order functions (UNSTABLE) (#348) 113 | - Support combined rulesets (UNSTABLE) (#362) 114 | 115 | Others 116 | 117 | - Numerous bug fixes 118 | 119 | ## [0.1.0] - 2023-10-24 120 | This is egglog's first release! Egglog is ready for use, but is still fairly experimental. Expect some significant changes in the future. 121 | 122 | - Egglog is better than [egg](https://github.com/egraphs-good/egg) in many ways, including performance and new features. 123 | - Egglog now includes cargo documentation for the language interface. 124 | 125 | As of yet, the rust interface is not documented or well supported. We reccomend using the language interface. Egglog also lacks proofs, a feature that egg has. 126 | 127 | 128 | [Unreleased]: https://github.com/egraphs-good/egglog/compare/v0.2.0...HEAD 129 | [0.1.0]: https://github.com/egraphs-good/egglog/tree/v0.1.0 130 | [0.2.0]: https://github.com/egraphs-good/egglog/tree/v0.2.0 131 | [0.3.0]: https://github.com/egraphs-good/egglog/tree/v0.3.0 132 | [0.4.0]: https://github.com/egraphs-good/egglog/tree/v0.4.0 133 | 134 | 135 | See release-instructions.md for more information on how to do a release. 136 | -------------------------------------------------------------------------------- /CITATION.bib: -------------------------------------------------------------------------------- 1 | @article{egglog, 2 | author = { 3 | Zhang, Yihong and 4 | Wang, Yisu Remy and 5 | Flatt, Oliver and 6 | Cao, David and 7 | Zucker, Philip and 8 | Rosenthal, Eli and 9 | Tatlock, Zachary and 10 | Willsey, Max 11 | }, 12 | title = {Better Together: Unifying Datalog and Equality Saturation}, 13 | year = {2023}, 14 | issue_date = {June 2023}, 15 | publisher = {Association for Computing Machinery}, 16 | address = {New York, NY, USA}, 17 | volume = {7}, 18 | number = {PLDI}, 19 | url = {https://doi.org/10.1145/3591239}, 20 | doi = {10.1145/3591239}, 21 | abstract = { 22 | We present egglog, a fixpoint reasoning system that unifies Datalog and equality saturation (EqSat). 23 | Like Datalog, egglog supports efficient incremental execution, cooperating analyses, and lattice-based reasoning. 24 | Like EqSat, egglog supports term rewriting, efficient congruence closure, and extraction of optimized terms. 25 | We identify two recent applications -- a unification-based pointer analysis in Datalog and an EqSat-based floating-point term rewriter -- 26 | that have been hampered by features missing from Datalog but found in EqSat or vice-versa. 27 | We evaluate our system by reimplementing those projects in egglog. 28 | The resulting systems in egglog are faster, simpler, and fix bugs found in the original systems. 29 | }, 30 | journal = {Proc. ACM Program. Lang.}, 31 | month = {jun}, 32 | articleno = {125}, 33 | numpages = {25}, 34 | keywords = {Equality saturation, Program optimization, Datalog, Rewrite systems} 35 | } 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition = "2021" 3 | name = "egglog" 4 | version = "0.4.0" 5 | description = "egglog is a language that combines the benefits of equality saturation and datalog. It can be used for analysis, optimization, and synthesis of programs. It is the successor to the popular rust library egg." 6 | repository = "https://github.com/egraphs-good/egglog" 7 | keywords = ["e-graphs", "egglog", "datalog", "compiler", "equality"] 8 | license = "MIT" 9 | readme = "README.md" 10 | 11 | [[bench]] 12 | name = "example_benchmarks" 13 | harness = false 14 | 15 | [workspace] 16 | members = [".", "web-demo"] 17 | 18 | [[test]] 19 | name = "files" 20 | harness = false 21 | required-features = ["bin"] 22 | 23 | [[bin]] 24 | name = "egglog" 25 | path = "src/main.rs" 26 | required-features = ["bin"] 27 | 28 | [features] 29 | default = ["bin"] 30 | 31 | bin = [ 32 | "serde", 33 | "graphviz", 34 | "dep:clap", 35 | "dep:env_logger", 36 | "dep:chrono", 37 | ] 38 | serde = ["egraph-serialize/serde"] 39 | graphviz = ["egraph-serialize/graphviz"] 40 | wasm-bindgen = ["instant/wasm-bindgen", "dep:getrandom"] 41 | nondeterministic = [] 42 | 43 | [dependencies] 44 | clap = { version = "4", features = ["derive"], optional = true } 45 | egraph-serialize = { version = "0.2.0", default-features = false } 46 | env_logger = { version = "0.10", optional = true } 47 | hashbrown = { version = "0.15" } 48 | im-rc = "15.1.0" 49 | im = "15.1.0" 50 | indexmap = "2.0" 51 | instant = "0.1" 52 | lazy_static = "1.4" 53 | log = "0.4" 54 | num = "0.4.3" 55 | ordered-float = { version = "3.7" } 56 | rustc-hash = "1.1" 57 | smallvec = "1.11" 58 | symbol_table = { version = "0.4.0", features = ["global"] } 59 | thiserror = "1" 60 | 61 | # Need to add "js" feature for "graphviz-rust" to work in wasm 62 | getrandom = { version = "0.2.10", optional = true, features = ["js"] } 63 | 64 | [build-dependencies] 65 | chrono = { version = "0.4", default-features = false, features = ["now"], optional = true } 66 | 67 | [dev-dependencies] 68 | codspeed-criterion-compat = "2.7.2" 69 | glob = "0.3.1" 70 | libtest-mimic = "0.6.1" 71 | 72 | [profile.release] 73 | incremental = true 74 | 75 | # https://github.com/mstange/samply/?tab=readme-ov-file#turn-on-debug-info-for-full-stacks 76 | [profile.profiling] 77 | inherits = "release" 78 | debug = true 79 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Max Willsey 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all web test nits docs serve graphs rm-graphs 2 | 3 | RUST_SRC=$(shell find . -type f -wholename '*/src/*.rs' -or -name 'Cargo.toml') 4 | TESTS=$(shell find tests/ -type f -name '*.egg' -not -name '*repro-*') 5 | 6 | WWW=${PWD}/target/www/ 7 | 8 | WEB_SRC=$(wildcard web-demo/static/*) 9 | 10 | WASM=web_demo.js web_demo_bg.wasm 11 | DIST_WASM=$(addprefix ${WWW}, ${WASM}) 12 | 13 | all: test nits web docs 14 | 15 | test: 16 | cargo nextest run --release 17 | # nextest doesn't run doctests, so do it here 18 | cargo test --doc --release 19 | 20 | nits: 21 | @rustup component add clippy 22 | cargo clippy --tests -- -D warnings 23 | @rustup component add rustfmt 24 | cargo fmt --check 25 | 26 | docs: 27 | mkdir -p ${WWW} 28 | cargo doc --no-deps --all-features 29 | touch target/doc/.nojekyll # prevent github from trying to run jekyll 30 | cp -r target/doc ${WWW}/docs 31 | 32 | web: docs ${DIST_WASM} ${WEB_SRC} ${WWW}/examples.json 33 | mkdir -p ${WWW} 34 | cp ${WEB_SRC} ${WWW} 35 | find target -name .gitignore -delete # ignored files are wonky to deploy 36 | 37 | serve: 38 | cargo watch --shell "make web && python3 -m http.server 8080 -d ${WWW}" 39 | 40 | ${WWW}/examples.json: web-demo/examples.py ${TESTS} 41 | $^ > $@ 42 | 43 | ${DIST_WASM}: ${RUST_SRC} 44 | wasm-pack build web-demo --target no-modules --no-typescript --out-dir ${WWW} 45 | rm -f ${WWW}/{.gitignore,package.json} 46 | 47 | graphs: $(patsubst %.egg,%.svg,$(filter-out $(wildcard tests/repro-*.egg),$(wildcard tests/*.egg))) 48 | 49 | json: $(patsubst %.egg,%.json,$(filter-out $(wildcard tests/repro-*.egg),$(wildcard tests/*.egg))) 50 | 51 | %.svg: %.egg 52 | cargo run --release -- --to-dot --to-svg $^ 53 | 54 | %.json: %.egg 55 | cargo run --release -- --to-json $^ 56 | 57 | rm-graphs: 58 | rm -f tests/*.dot tests/*.svg 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # egglog 2 | 3 | 4 | Web Demo 5 | 6 | Main Branch Documentation 7 | 8 | CodSpeed Badge 9 | 10 | This is the repo for the `egglog` tool accompanying the paper 11 | "Better Together: Unifying Datalog and Equality Saturation" 12 | ([ACM DL](https://dl.acm.org/doi/10.1145/3591239), [arXiv](https://arxiv.org/abs/2304.04332)). 13 | 14 | If you use this work, please use [this citation](./CITATION.bib). 15 | 16 | See also the Python binding, which provides a bit more documentation: 17 | https://egglog-python.readthedocs.io/ 18 | 19 | ## Chat 20 | 21 | There is a Zulip chat about egglog here: 22 | https://egraphs.zulipchat.com/#narrow/stream/375765-egglog 23 | 24 | ## Prerequisites & compilation 25 | 26 | ``` 27 | apt-get install make cargo 28 | cargo install cargo-nextest 29 | make all 30 | ``` 31 | 32 | 33 | ## Usage 34 | 35 | ``` 36 | cargo run [-f fact-path] [-naive] [--to-json] [--to-dot] [--to-svg] 37 | ``` 38 | 39 | or just 40 | 41 | ``` 42 | cargo run 43 | ``` 44 | 45 | for the REPL. 46 | 47 | * The `--to-dot` command will save a graphviz dot file at the end of the program, replacing the `.egg` extension with `.dot`. 48 | * 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`. 49 | 50 | 51 | ## Community extensions 52 | 53 | * [@hatoo](https://github.com/hatoo) maintains an [egglog-language extension](https://marketplace.visualstudio.com/items?itemName=hatookov.egglog-language) in VS Code (just search for "egglog" in VS Code). 54 | * [@segeljakt](https://github.com/segeljakt) maintains a [Neovim plugin](https://github.com/segeljakt/tree-sitter-egg) for egglog using tree-sitter. 55 | 56 | ## Development 57 | 58 | To run the tests use `make test`. 59 | 60 | ## Benchmarks 61 | 62 | We run all of our "examples" [as benchmarks in codspeed](https://codspeed.io/egraphs-good/egglog). These are in CI 63 | for every commit in main and for all PRs. It will run the examples with extra instrumentation added so that it can 64 | capture a single trace of the CPU interactions ([src](https://docs.codspeed.io/features/understanding-the-metrics/)): 65 | 66 | > CodSpeed instruments your benchmarks to measure the performance of your code. A benchmark will be run only once and the CPU behavior will be simulated. This ensures that the measurement is as accurate as possible, taking into account not only the instructions executed but also the cache and memory access patterns. The simulation gives us an equivalent of the CPU cycles that includes cache and memory access. 67 | 68 | Since many of the shorter running benchmarks have unstable timings due to non deterministic performance ([like in the memory allocator](https://github.com/oxc-project/backlog/issues/89)), 69 | we ["ignore"](https://docs.codspeed.io/features/ignoring-benchmarks/) them in codspeed. That way, we still 70 | capture their performance, but their timings don't show up in our reports by default. 71 | 72 | We use 50ms as our cutoff currently, any benchmarks shorter than that are ignored. This number was selected to try to ignore 73 | any benchmarks with have changes > 1% when they haven't been modified. Note that all the ignoring is done manually, 74 | so if you add another example that's short, an admin on the codspeed project will need to manually ignore it. 75 | 76 | ## Profiling 77 | 78 | One way to profile egglog is to use [samply](https://github.com/mstange/samply/). Here's how you can use it: 79 | 80 | ```bash 81 | # install samply 82 | cargo install --locked samply 83 | # build a profile build which includes debug symbols 84 | cargo build --profile profiling 85 | # run the egglog file and profile 86 | samply record ./target/profiling/egglog tests/extract-vec-bench.egg 87 | # [optional] run the egglog file without logging or printing messages, which can help reduce the stdout 88 | # when you are profiling extracting a large expression 89 | env RUST_LOG=error samply record ./target/profiling/egglog --dont-print-messages tests/extract-vec-bench.egg 90 | ``` 91 | 92 | # Documentation 93 | 94 | To view documentation, run `cargo doc --open`. 95 | 96 | -------------------------------------------------------------------------------- /benches/example_benchmarks.rs: -------------------------------------------------------------------------------- 1 | use codspeed_criterion_compat::{criterion_group, criterion_main, Criterion}; 2 | use egglog::EGraph; 3 | 4 | fn run_example(filename: &str, program: &str, no_messages: bool) { 5 | let mut egraph = EGraph::default(); 6 | if no_messages { 7 | egraph.disable_messages(); 8 | } 9 | egraph 10 | .parse_and_run_program(Some(filename.to_owned()), program) 11 | .unwrap(); 12 | // test performance of serialization as well 13 | let _ = egraph.serialize(egglog::SerializeConfig::default()); 14 | } 15 | 16 | pub fn criterion_benchmark(c: &mut Criterion) { 17 | for entry in glob::glob("tests/**/*.egg").unwrap() { 18 | let path = entry.unwrap().clone(); 19 | let path_string = path.to_string_lossy().to_string(); 20 | if path_string.contains("fail-typecheck") { 21 | continue; 22 | } 23 | let name = path.file_stem().unwrap().to_string_lossy().to_string(); 24 | let filename = path.to_string_lossy().to_string(); 25 | let program = std::fs::read_to_string(&filename).unwrap(); 26 | let no_messages = path_string.contains("no-messages"); 27 | c.bench_function(&name, |b| { 28 | b.iter(|| run_example(&filename, &program, no_messages)) 29 | }); 30 | } 31 | } 32 | 33 | criterion_group!(benches, criterion_benchmark); 34 | criterion_main!(benches); 35 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "bin")] 2 | #[allow(clippy::disallowed_macros)] // for println! 3 | fn main() { 4 | use std::{env, process::Command}; 5 | 6 | let git_hash = Command::new("git") 7 | .args(["rev-parse", "--short", "HEAD"]) 8 | .output() 9 | .map(|output| { 10 | String::from_utf8(output.stdout) 11 | .map(|s| "_".to_owned() + &s) 12 | .unwrap_or_default() 13 | }) 14 | .unwrap_or_default(); 15 | let build_date = chrono::Utc::now().format("%Y-%m-%d"); 16 | let version = env::var("CARGO_PKG_VERSION").unwrap(); 17 | let full_version = format!("{}_{}{}", version, build_date, git_hash); 18 | println!("cargo:rustc-env=FULL_VERSION={}", full_version); 19 | } 20 | 21 | #[cfg(not(feature = "bin"))] 22 | fn main() {} 23 | -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | disallowed-types = [ 2 | # prefer hashbrown 3 | "std::collections::HashMap", 4 | "std::collections::HashSet", 5 | # prefer instant crate, it works on wasm 6 | "std::time::Instant", 7 | ] 8 | 9 | disallowed-macros = [ 10 | # only allowed in main.rs 11 | "std::print", 12 | "std::println", 13 | # use log crate instead 14 | "std::eprint", 15 | "std::eprintln", 16 | ] 17 | 18 | type-complexity-threshold = 350 19 | -------------------------------------------------------------------------------- /release-instructions.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | How to do a release: 4 | 1. Update `CHANGELOG.md` with a new entry and new link at the bottom. 5 | 2. Find and replace in the codebase to update the version number. Make sure to get `Cargo.toml` and places in the changelog. Be careful not the screw up old links though! 6 | 4. Commit. 7 | 5. Tag the commit with the version number. 8 | 6. Make a PR and make sure the tag gets added. 9 | 7. Merge the PR 10 | 8. `cargo publish --dry-run` 11 | 1. Sometimes this can result in an error- you may need to run `cargo update` to update `cargo.lock` 12 | 9. `cargo publish` 13 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.79.0" 3 | -------------------------------------------------------------------------------- /scripts/minimize.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (require racket/runtime-path) 4 | 5 | (define (read-lines port) 6 | (define line (read port)) 7 | (if (eof-object? line) 8 | '() 9 | (cons line (read-lines port)))) 10 | 11 | ;; don't remove any check statements 12 | (define (remove-at n lst) 13 | (define-values (head tail) (split-at lst n)) 14 | (define line (car tail)) 15 | (if (and (list? line) 16 | (or (equal? (first line) 'check) 17 | (equal? (first line) 'keep))) 18 | lst 19 | (append head (cdr tail)))) 20 | 21 | (define-runtime-path egglog-binary 22 | "../target/release/egglog") 23 | 24 | ;; timeout in seconds 25 | (define TIMEOUT 5) 26 | (define ITERATIONS 1) 27 | (define RANDOM-SAMPLE-FACTOR 1) 28 | (define MUST-NOT-STRINGS `()) 29 | (define TARGET-STRINGS `("src/lib.rs:250")) 30 | 31 | (define (desugar line) 32 | (match line 33 | [`(keep ,body) 34 | body] 35 | [else line])) 36 | 37 | (define (desired-error? program) 38 | (displayln (format "Trying program of size ~a" (length program))) 39 | (flush-output) 40 | (define-values (egglog-process egglog-output egglog-in err) 41 | (subprocess (current-output-port) #f #f egglog-binary)) 42 | 43 | (for ([line program]) 44 | (writeln (desugar line) egglog-in)) 45 | (close-output-port egglog-in) 46 | 47 | (when (not (sync/timeout TIMEOUT egglog-process)) 48 | (displayln "Timed out")) 49 | (subprocess-kill egglog-process #t) 50 | (displayln "checking output") 51 | (flush-output) 52 | (define err-str (read-string 10000 err)) 53 | (close-input-port err) 54 | (define still-unsound (and (string? err-str) 55 | (for/and ([must-not-string MUST-NOT-STRINGS]) 56 | (not (string-contains? err-str must-not-string))) 57 | (for/or ([TARGET-STRING TARGET-STRINGS]) 58 | (string-contains? err-str TARGET-STRING)))) 59 | (println err-str) 60 | (if still-unsound 61 | (displayln "Reduced program") 62 | (displayln "Did not reduce")) 63 | still-unsound) 64 | 65 | (define (min-program program index) 66 | (fprintf (current-output-port) "Trying to remove index ~a out of ~a\n" index (length program)) 67 | (flush-output) 68 | 69 | (cond 70 | [(>= index (length program)) program] 71 | [else 72 | (define removed (remove-at index program)) 73 | (cond 74 | [(equal? (length removed) (length program)) 75 | (min-program removed (+ index 1))] 76 | [(desired-error? removed) 77 | (min-program removed index)] 78 | [else (min-program program (+ index 1))])])) 79 | 80 | (define (remove-random-lines program n) 81 | (cond 82 | [(<= n 0) program] 83 | [else 84 | (define index (random (length program))) 85 | (define new-program (remove-at index program)) 86 | (remove-random-lines new-program (- n 1))])) 87 | 88 | (define (min-program-random program iters) 89 | (cond 90 | [(= iters 0) program] 91 | [else 92 | (define index (random (length program))) 93 | (define new-program (remove-at index program)) 94 | (if (desired-error? new-program) 95 | (min-program-random new-program (- iters 1)) 96 | (min-program-random program (- iters 1)))])) 97 | 98 | (define (min-program-greedy program num) 99 | (cond 100 | [(< num 1) 101 | program] 102 | [else 103 | (define prog (remove-random-lines program num)) 104 | (if (desired-error? prog) 105 | (min-program-greedy prog num) 106 | (min-program-greedy program (* num 2/3)))])) 107 | 108 | (define (random-and-sequential program) 109 | (define binary (min-program-greedy program (/ (length program) 2))) 110 | (define random-prog (min-program-random binary (* (length binary) RANDOM-SAMPLE-FACTOR))) 111 | (min-program random-prog 0)) 112 | 113 | (define (min-iterations program) 114 | (define programs (for/list ([i (in-range ITERATIONS)]) 115 | (random-and-sequential program))) 116 | (first (sort programs (lambda (a b) (< (length a) (length b)))))) 117 | 118 | 119 | 120 | (define (minimize port-in port-out) 121 | #;((define-values (process out in err) (subprocess #f #f #f "cargo")) 122 | (define err-str (read-string 800 err)) 123 | (when (not (string=? err-str "")) 124 | (error err-str)) 125 | (close-input-port out) 126 | (close-output-port in) 127 | (close-input-port err) 128 | (subprocess-wait process)) 129 | 130 | (define egglog (read-lines port-in)) 131 | (pretty-print egglog) 132 | 133 | (when (not (desired-error? egglog)) 134 | (error "Original program did not have error")) 135 | 136 | (define minimized (min-iterations egglog)) 137 | (for ([line minimized]) 138 | (writeln (desugar line) port-out))) 139 | 140 | 141 | (module+ main 142 | (command-line 143 | #:args (file-in file-out) 144 | (minimize (open-input-file file-in) (open-output-file file-out #:exists 'replace)))) 145 | -------------------------------------------------------------------------------- /src/ast/check_shadowing.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | #[derive(Clone, Debug, Default)] 4 | pub(crate) struct Names(HashMap); 5 | 6 | impl Names { 7 | fn check(&mut self, name: Symbol, new: Span) -> Result<(), Error> { 8 | if let Some(old) = self.0.get(&name) { 9 | Err(Error::Shadowing(name, old.clone(), new)) 10 | } else { 11 | self.0.insert(name, new); 12 | Ok(()) 13 | } 14 | } 15 | 16 | /// WARNING: this function does not handle `push` and `pop`. 17 | /// Because `Names` is contained on the `EGraph`, this will 18 | /// work correctly when executed from `process_command`, but 19 | /// a unit test that called this function multiple times without 20 | /// changing the `EGraph` will be wrong. 21 | pub(crate) fn check_shadowing(&mut self, command: &ResolvedNCommand) -> Result<(), Error> { 22 | match command { 23 | ResolvedNCommand::Sort(span, name, _args) => self.check(*name, span.clone()), 24 | ResolvedNCommand::Function(decl) => self.check(decl.name, decl.span.clone()), 25 | ResolvedNCommand::AddRuleset(span, name) => self.check(*name, span.clone()), 26 | ResolvedNCommand::UnstableCombinedRuleset(span, name, _args) => { 27 | self.check(*name, span.clone()) 28 | } 29 | ResolvedNCommand::NormRule { rule, .. } => { 30 | let mut inner = self.clone(); 31 | inner.check_shadowing_query(&rule.body)?; 32 | for action in rule.head.iter() { 33 | inner.check_shadowing_action(action)?; 34 | } 35 | Ok(()) 36 | } 37 | ResolvedNCommand::CoreAction(action) => self.check_shadowing_action(action), 38 | ResolvedNCommand::Check(_span, query) => { 39 | let mut inner = self.clone(); 40 | inner.check_shadowing_query(query) 41 | } 42 | ResolvedNCommand::Fail(_span, command) => { 43 | let mut inner = self.clone(); 44 | inner.check_shadowing(command) 45 | } 46 | ResolvedNCommand::SetOption { .. } => Ok(()), 47 | ResolvedNCommand::RunSchedule(..) => Ok(()), 48 | ResolvedNCommand::PrintOverallStatistics => Ok(()), 49 | ResolvedNCommand::PrintTable(..) => Ok(()), 50 | ResolvedNCommand::PrintSize(..) => Ok(()), 51 | ResolvedNCommand::Input { .. } => Ok(()), 52 | ResolvedNCommand::Output { .. } => Ok(()), 53 | ResolvedNCommand::Push(..) => Ok(()), 54 | ResolvedNCommand::Pop(..) => Ok(()), 55 | } 56 | } 57 | 58 | fn check_shadowing_query(&mut self, query: &[ResolvedFact]) -> Result<(), Error> { 59 | // we want to allow names in queries to shadow each other, so we first collect 60 | // all of the variable names, and then we check each of those names once 61 | fn get_expr_names(expr: &ResolvedExpr, inner: &mut Names) { 62 | match expr { 63 | ResolvedExpr::Lit(..) => {} 64 | ResolvedExpr::Var(span, name) => { 65 | if !inner.0.contains_key(&name.name) { 66 | inner.0.insert(name.name, span.clone()); 67 | } 68 | } 69 | ResolvedExpr::Call(_span, _func, args) => { 70 | args.iter().for_each(|e| get_expr_names(e, inner)) 71 | } 72 | }; 73 | } 74 | 75 | let mut inner = Names::default(); 76 | 77 | for fact in query { 78 | match fact { 79 | ResolvedFact::Eq(_span, e1, e2) => { 80 | get_expr_names(e1, &mut inner); 81 | get_expr_names(e2, &mut inner); 82 | } 83 | ResolvedFact::Fact(e) => get_expr_names(e, &mut inner), 84 | } 85 | } 86 | 87 | for (name, span) in inner.0 { 88 | self.check(name, span.clone())?; 89 | } 90 | 91 | Ok(()) 92 | } 93 | 94 | fn check_shadowing_action(&mut self, action: &ResolvedAction) -> Result<(), Error> { 95 | if let ResolvedAction::Let(span, name, _args) = action { 96 | self.check(name.name, span.clone()) 97 | } else { 98 | Ok(()) 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/function/binary_search.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::Ordering; 2 | 3 | use super::table::Table; 4 | 5 | /// Binary search a [`Table`] for the smallest index with a timestamp greater 6 | /// than or equal to `target`. 7 | pub(crate) fn binary_search_table_by_key(data: &Table, target: u32) -> Option { 8 | if data.is_empty() { 9 | return None; 10 | } 11 | if data.max_ts() < target { 12 | return None; 13 | } 14 | if data.min_ts().unwrap() > target { 15 | return Some(0); 16 | } 17 | // adapted from std::slice::binary_search_by 18 | let mut size = data.num_offsets(); 19 | let mut left = 0; 20 | let mut right = size; 21 | while left < right { 22 | let mut mid = left + size / 2; 23 | let cmp = data.get_timestamp(mid).unwrap().cmp(&target); 24 | 25 | // The std implementation claims that if/else generates better code than match. 26 | if cmp == Ordering::Less { 27 | left = mid + 1; 28 | } else if cmp == Ordering::Greater { 29 | right = mid; 30 | } else { 31 | // We need to march back to the start of the matching elements. We 32 | // could have jumped into the middle of a run. 33 | // 34 | // TODO: this makes the algorithm O(n); we can use a variant of 35 | // gallop to get it back to log(n) if needed. See 36 | // https://github.com/frankmcsherry/blog/blob/master/posts/2018-05-19.md 37 | while mid > 0 { 38 | let next_mid = mid - 1; 39 | if data.get_timestamp(next_mid).unwrap() != target { 40 | break; 41 | } 42 | mid = next_mid; 43 | } 44 | return Some(mid); 45 | } 46 | size = right - left; 47 | } 48 | Some(left) 49 | } 50 | 51 | #[cfg(test)] 52 | mod tests { 53 | use super::*; 54 | use crate::Value; 55 | 56 | fn make_value(bits: u32) -> Value { 57 | Value { 58 | #[cfg(debug_assertions)] 59 | tag: "testing".into(), 60 | bits: bits as u64, 61 | } 62 | } 63 | 64 | fn insert_to_map(table: &mut Table, i: u32, ts: u32) { 65 | let v = make_value(i); 66 | table.insert(&[v], v, ts); 67 | } 68 | 69 | #[test] 70 | fn binary_search() { 71 | let mut map = Table::default(); 72 | assert_eq!(binary_search_table_by_key(&map, 0), None); 73 | insert_to_map(&mut map, 1, 1); 74 | assert_eq!(binary_search_table_by_key(&map, 0), Some(0)); 75 | map.clear(); 76 | for i in 0..128 { 77 | // have a run of 4 24s and then skip to 26 78 | let v = if i == 50 || i == 51 { 24 } else { i / 2 }; 79 | insert_to_map(&mut map, i, v); 80 | } 81 | 82 | assert_eq!(binary_search_table_by_key(&map, 3), Some(6)); 83 | assert_eq!(binary_search_table_by_key(&map, 0), Some(0)); 84 | assert_eq!(binary_search_table_by_key(&map, 63), Some(126)); 85 | assert_eq!(binary_search_table_by_key(&map, 200), None); 86 | assert_eq!(binary_search_table_by_key(&map, 24), Some(48)); 87 | assert_eq!(binary_search_table_by_key(&map, 25), Some(52)); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/function/index.rs: -------------------------------------------------------------------------------- 1 | //! Column-level indexes on values from a common sort. 2 | use smallvec::SmallVec; 3 | 4 | use crate::{unionfind::UnionFind, util::HashMap, Symbol, Value}; 5 | 6 | pub(crate) type Offset = u32; 7 | 8 | #[derive(Clone, Debug)] 9 | pub(crate) struct ColumnIndex { 10 | sort: Symbol, 11 | ids: HashMap>, 12 | } 13 | 14 | impl ColumnIndex { 15 | pub(crate) fn new(sort: Symbol) -> ColumnIndex { 16 | ColumnIndex { 17 | sort, 18 | ids: Default::default(), 19 | } 20 | } 21 | 22 | pub(crate) fn sort(&self) -> Symbol { 23 | self.sort 24 | } 25 | 26 | pub(crate) fn add(&mut self, v: Value, i: usize) { 27 | #[cfg(debug_assertions)] 28 | assert_eq!(v.tag, self.sort); 29 | 30 | self.ids.entry(v.bits).or_default().push(i as Offset); 31 | } 32 | 33 | pub(crate) fn clear(&mut self) { 34 | self.ids.clear() 35 | } 36 | 37 | pub(crate) fn len(&self) -> usize { 38 | self.ids.len() 39 | } 40 | 41 | pub(crate) fn get(&self, v: &Value) -> Option<&[Offset]> { 42 | self.get_indexes_for_bits(v.bits) 43 | } 44 | 45 | fn get_indexes_for_bits(&self, bits: u64) -> Option<&[Offset]> { 46 | self.ids.get(&bits).map(|x| x.as_slice()) 47 | } 48 | 49 | pub(crate) fn iter(&self) -> impl Iterator + '_ { 50 | self.ids.iter().map(|(bits, v)| { 51 | ( 52 | Value { 53 | #[cfg(debug_assertions)] 54 | tag: self.sort, 55 | bits: *bits, 56 | }, 57 | v.as_slice(), 58 | ) 59 | }) 60 | } 61 | 62 | pub(crate) fn to_canonicalize<'a>( 63 | &'a self, 64 | uf: &'a UnionFind, 65 | ) -> impl Iterator + 'a { 66 | uf.dirty_ids(self.sort).flat_map(|x| { 67 | self.get_indexes_for_bits(x) 68 | .unwrap_or(&[]) 69 | .iter() 70 | .copied() 71 | .map(|x| x as usize) 72 | }) 73 | } 74 | } 75 | #[derive(Clone, Debug)] 76 | pub(crate) struct CompositeColumnIndex(SmallVec<[ColumnIndex; 2]>); 77 | 78 | impl CompositeColumnIndex { 79 | pub(crate) fn new() -> CompositeColumnIndex { 80 | CompositeColumnIndex(SmallVec::new()) 81 | } 82 | 83 | pub(crate) fn add(&mut self, s: Symbol, v: Value, i: usize) { 84 | if let Some(index) = self.0.iter().position(|index| index.sort() == s) { 85 | (self.0)[index].add(v, i); 86 | } else { 87 | let mut index = ColumnIndex::new(s); 88 | index.add(v, i); 89 | self.0.push(index); 90 | } 91 | } 92 | 93 | pub(crate) fn clear(&mut self) { 94 | for index in self.0.iter_mut() { 95 | index.clear(); 96 | } 97 | } 98 | 99 | pub(crate) fn iter(&self) -> impl Iterator { 100 | self.0.iter() 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | egglog::cli(egglog::EGraph::default()) 3 | } 4 | -------------------------------------------------------------------------------- /src/sort/bigint.rs: -------------------------------------------------------------------------------- 1 | use num::BigInt; 2 | use std::ops::{Shl, Shr}; 3 | use std::sync::Mutex; 4 | 5 | type Z = BigInt; 6 | use crate::{ast::Literal, util::IndexSet}; 7 | 8 | use super::*; 9 | 10 | lazy_static! { 11 | static ref BIG_INT_SORT_NAME: Symbol = "BigInt".into(); 12 | static ref INTS: Mutex> = Default::default(); 13 | } 14 | 15 | #[derive(Debug)] 16 | pub struct BigIntSort; 17 | 18 | impl Sort for BigIntSort { 19 | fn name(&self) -> Symbol { 20 | *BIG_INT_SORT_NAME 21 | } 22 | 23 | fn as_arc_any(self: Arc) -> Arc { 24 | self 25 | } 26 | 27 | #[rustfmt::skip] 28 | fn register_primitives(self: Arc, eg: &mut TypeInfo) { 29 | type Opt = Option; 30 | 31 | add_primitives!(eg, "bigint" = |a: i64| -> Z { a.into() }); 32 | 33 | add_primitives!(eg, "+" = |a: Z, b: Z| -> Z { a + b }); 34 | add_primitives!(eg, "-" = |a: Z, b: Z| -> Z { a - b }); 35 | add_primitives!(eg, "*" = |a: Z, b: Z| -> Z { a * b }); 36 | add_primitives!(eg, "/" = |a: Z, b: Z| -> Opt { (b != BigInt::ZERO).then(|| a / b) }); 37 | add_primitives!(eg, "%" = |a: Z, b: Z| -> Opt { (b != BigInt::ZERO).then(|| a % b) }); 38 | 39 | add_primitives!(eg, "&" = |a: Z, b: Z| -> Z { a & b }); 40 | add_primitives!(eg, "|" = |a: Z, b: Z| -> Z { a | b }); 41 | add_primitives!(eg, "^" = |a: Z, b: Z| -> Z { a ^ b }); 42 | add_primitives!(eg, "<<" = |a: Z, b: i64| -> Z { a.shl(b) }); 43 | add_primitives!(eg, ">>" = |a: Z, b: i64| -> Z { a.shr(b) }); 44 | add_primitives!(eg, "not-Z" = |a: Z| -> Z { !a }); 45 | 46 | add_primitives!(eg, "bits" = |a: Z| -> Z { a.bits().into() }); 47 | 48 | add_primitives!(eg, "<" = |a: Z, b: Z| -> Opt { (a < b).then_some(()) }); 49 | add_primitives!(eg, ">" = |a: Z, b: Z| -> Opt { (a > b).then_some(()) }); 50 | add_primitives!(eg, "<=" = |a: Z, b: Z| -> Opt { (a <= b).then_some(()) }); 51 | add_primitives!(eg, ">=" = |a: Z, b: Z| -> Opt { (a >= b).then_some(()) }); 52 | 53 | add_primitives!(eg, "bool-=" = |a: Z, b: Z| -> bool { a == b }); 54 | add_primitives!(eg, "bool-<" = |a: Z, b: Z| -> bool { a < b }); 55 | add_primitives!(eg, "bool->" = |a: Z, b: Z| -> bool { a > b }); 56 | add_primitives!(eg, "bool-<=" = |a: Z, b: Z| -> bool { a <= b }); 57 | add_primitives!(eg, "bool->=" = |a: Z, b: Z| -> bool { a >= b }); 58 | 59 | add_primitives!(eg, "min" = |a: Z, b: Z| -> Z { a.min(b) }); 60 | add_primitives!(eg, "max" = |a: Z, b: Z| -> Z { a.max(b) }); 61 | 62 | add_primitives!(eg, "to-string" = |a: Z| -> Symbol { a.to_string().into() }); 63 | add_primitives!(eg, "from-string" = |a: Symbol| -> Opt { a.as_str().parse::().ok() }); 64 | } 65 | 66 | fn extract_term( 67 | &self, 68 | _egraph: &EGraph, 69 | value: Value, 70 | _extractor: &Extractor, 71 | termdag: &mut TermDag, 72 | ) -> Option<(Cost, Term)> { 73 | #[cfg(debug_assertions)] 74 | debug_assert_eq!(value.tag, self.name()); 75 | 76 | let bigint = Z::load(self, &value); 77 | 78 | let as_string = termdag.lit(Literal::String(bigint.to_string().into())); 79 | Some((1, termdag.app("from-string".into(), vec![as_string]))) 80 | } 81 | } 82 | 83 | impl FromSort for Z { 84 | type Sort = BigIntSort; 85 | fn load(_sort: &Self::Sort, value: &Value) -> Self { 86 | let i = value.bits as usize; 87 | INTS.lock().unwrap().get_index(i).unwrap().clone() 88 | } 89 | } 90 | 91 | impl IntoSort for Z { 92 | type Sort = BigIntSort; 93 | fn store(self, _sort: &Self::Sort) -> Option { 94 | let (i, _) = INTS.lock().unwrap().insert_full(self); 95 | Some(Value { 96 | #[cfg(debug_assertions)] 97 | tag: BigIntSort.name(), 98 | bits: i as u64, 99 | }) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/sort/bool.rs: -------------------------------------------------------------------------------- 1 | use crate::ast::Literal; 2 | 3 | use super::*; 4 | 5 | #[derive(Debug)] 6 | pub struct BoolSort; 7 | 8 | lazy_static! { 9 | static ref BOOL_SORT_NAME: Symbol = "bool".into(); 10 | } 11 | 12 | impl Sort for BoolSort { 13 | fn name(&self) -> Symbol { 14 | *BOOL_SORT_NAME 15 | } 16 | 17 | fn as_arc_any(self: Arc) -> Arc { 18 | self 19 | } 20 | 21 | #[rustfmt::skip] 22 | fn register_primitives(self: Arc, eg: &mut TypeInfo) { 23 | add_primitives!(eg, "not" = |a: bool| -> bool { !a }); 24 | add_primitives!(eg, "and" = |a: bool, b: bool| -> bool { a && b }); 25 | add_primitives!(eg, "or" = |a: bool, b: bool| -> bool { a || b }); 26 | add_primitives!(eg, "xor" = |a: bool, b: bool| -> bool { a ^ b }); 27 | add_primitives!(eg, "=>" = |a: bool, b: bool| -> bool { !a || b }); 28 | } 29 | 30 | fn extract_term( 31 | &self, 32 | _egraph: &EGraph, 33 | value: Value, 34 | _extractor: &Extractor, 35 | termdag: &mut TermDag, 36 | ) -> Option<(Cost, Term)> { 37 | #[cfg(debug_assertions)] 38 | debug_assert_eq!(value.tag, self.name()); 39 | 40 | Some((1, termdag.lit(Literal::Bool(value.bits > 0)))) 41 | } 42 | } 43 | 44 | impl IntoSort for bool { 45 | type Sort = BoolSort; 46 | fn store(self, _sort: &Self::Sort) -> Option { 47 | Some(Value { 48 | #[cfg(debug_assertions)] 49 | tag: BoolSort.name(), 50 | bits: self as u64, 51 | }) 52 | } 53 | } 54 | 55 | impl FromSort for bool { 56 | type Sort = BoolSort; 57 | fn load(_sort: &Self::Sort, value: &Value) -> Self { 58 | value.bits != 0 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/sort/f64.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::ast::Literal; 3 | use ordered_float::OrderedFloat; 4 | 5 | /// 64-bit floating point numbers supporting these primitives: 6 | /// - Arithmetic: `+`, `-`, `*`, `/`, `%`, `^`, `neg`, `abs` 7 | /// - Comparisons: `<`, `>`, `<=`, `>=` 8 | /// - Other: `min`, `max`, `to-i64`, `to-string` 9 | #[derive(Debug)] 10 | pub struct F64Sort; 11 | 12 | lazy_static! { 13 | static ref F64_SORT_NAME: Symbol = "f64".into(); 14 | } 15 | 16 | impl Sort for F64Sort { 17 | fn name(&self) -> Symbol { 18 | *F64_SORT_NAME 19 | } 20 | 21 | fn as_arc_any(self: Arc) -> Arc { 22 | self 23 | } 24 | 25 | #[rustfmt::skip] 26 | // We need the closure for division and mod operations, as they can panic. 27 | // cf https://github.com/rust-lang/rust-clippy/issues/9422 28 | #[allow(clippy::unnecessary_lazy_evaluations)] 29 | fn register_primitives(self: Arc, eg: &mut TypeInfo) { 30 | type Opt = Option; 31 | 32 | add_primitives!(eg, "+" = |a: f64, b: f64| -> f64 { a + b }); 33 | add_primitives!(eg, "-" = |a: f64, b: f64| -> f64 { a - b }); 34 | add_primitives!(eg, "*" = |a: f64, b: f64| -> f64 { a * b }); 35 | add_primitives!(eg, "/" = |a: f64, b: f64| -> Opt { (b != 0.0).then(|| a / b) }); 36 | add_primitives!(eg, "%" = |a: f64, b: f64| -> Opt { (b != 0.0).then(|| a % b) }); 37 | add_primitives!(eg, "^" = |a: f64, b: f64| -> f64 { a.powf(b) }); 38 | add_primitives!(eg, "neg" = |a: f64| -> f64 { -a }); 39 | 40 | add_primitives!(eg, "<" = |a: f64, b: f64| -> Opt { (a < b).then(|| ()) }); 41 | add_primitives!(eg, ">" = |a: f64, b: f64| -> Opt { (a > b).then(|| ()) }); 42 | add_primitives!(eg, "<=" = |a: f64, b: f64| -> Opt { (a <= b).then(|| ()) }); 43 | add_primitives!(eg, ">=" = |a: f64, b: f64| -> Opt { (a >= b).then(|| ()) }); 44 | 45 | add_primitives!(eg, "min" = |a: f64, b: f64| -> f64 { a.min(b) }); 46 | add_primitives!(eg, "max" = |a: f64, b: f64| -> f64 { a.max(b) }); 47 | add_primitives!(eg, "abs" = |a: f64| -> f64 { a.abs() }); 48 | 49 | // `to-f64` should be in `i64.rs`, but `F64Sort` wouldn't exist yet 50 | add_primitives!(eg, "to-f64" = |a: i64| -> f64 { a as f64 }); 51 | add_primitives!(eg, "to-i64" = |a: f64| -> i64 { a as i64 }); 52 | // Use debug instead of to_string so that decimal place is always printed 53 | add_primitives!(eg, "to-string" = |a: f64| -> Symbol { format!("{:?}", a).into() }); 54 | } 55 | 56 | fn extract_term( 57 | &self, 58 | _egraph: &EGraph, 59 | value: Value, 60 | _extractor: &Extractor, 61 | termdag: &mut TermDag, 62 | ) -> Option<(Cost, Term)> { 63 | #[cfg(debug_assertions)] 64 | debug_assert_eq!(value.tag, self.name()); 65 | 66 | Some(( 67 | 1, 68 | termdag.lit(Literal::Float(OrderedFloat(f64::from_bits(value.bits)))), 69 | )) 70 | } 71 | } 72 | 73 | impl IntoSort for f64 { 74 | type Sort = F64Sort; 75 | fn store(self, _sort: &Self::Sort) -> Option { 76 | Some(Value { 77 | #[cfg(debug_assertions)] 78 | tag: F64Sort.name(), 79 | bits: self.to_bits(), 80 | }) 81 | } 82 | } 83 | 84 | impl FromSort for f64 { 85 | type Sort = F64Sort; 86 | fn load(_sort: &Self::Sort, value: &Value) -> Self { 87 | f64::from_bits(value.bits) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/sort/i64.rs: -------------------------------------------------------------------------------- 1 | use crate::{ast::Literal, constraint::AllEqualTypeConstraint}; 2 | 3 | use super::*; 4 | 5 | /// Signed 64-bit integers supporting these primitives: 6 | /// - Arithmetic: `+`, `-`, `*`, `/`, `%` 7 | /// - Bitwise: `&`, `|`, `^`, `<<`, `>>`, `not-i64` 8 | /// - Fallible comparisons: `<`, `>`, `<=`, `>=` 9 | /// - Boolean comparisons: `bool-=`, `bool-<`, `bool->`, `bool-<=`, `bool->=` 10 | /// - Other: `min`, `max`, `to-f64`, `to-string`, `log2` 11 | /// 12 | /// Note: fallible comparisons are used at the top-level of a query. 13 | /// For example, this rule will only match if `a` is less than `b`. 14 | /// ```text 15 | /// (rule (... (< a b)) (...)) 16 | /// ``` 17 | /// On the other hand, boolean comparisons will always match, and so 18 | /// make sense to use inside expressions. 19 | #[derive(Debug)] 20 | pub struct I64Sort; 21 | 22 | lazy_static! { 23 | static ref I64_SORT_NAME: Symbol = "i64".into(); 24 | } 25 | 26 | impl Sort for I64Sort { 27 | fn name(&self) -> Symbol { 28 | *I64_SORT_NAME 29 | } 30 | 31 | fn as_arc_any(self: Arc) -> Arc { 32 | self 33 | } 34 | 35 | #[rustfmt::skip] 36 | fn register_primitives(self: Arc, typeinfo: &mut TypeInfo) { 37 | typeinfo.add_primitive(TermOrderingMin { 38 | }); 39 | typeinfo.add_primitive(TermOrderingMax { 40 | }); 41 | 42 | type Opt = Option; 43 | 44 | add_primitives!(typeinfo, "+" = |a: i64, b: i64| -> Opt { a.checked_add(b) }); 45 | add_primitives!(typeinfo, "-" = |a: i64, b: i64| -> Opt { a.checked_sub(b) }); 46 | add_primitives!(typeinfo, "*" = |a: i64, b: i64| -> Opt { a.checked_mul(b) }); 47 | add_primitives!(typeinfo, "/" = |a: i64, b: i64| -> Opt { a.checked_div(b) }); 48 | add_primitives!(typeinfo, "%" = |a: i64, b: i64| -> Opt { a.checked_rem(b) }); 49 | 50 | add_primitives!(typeinfo, "&" = |a: i64, b: i64| -> i64 { a & b }); 51 | add_primitives!(typeinfo, "|" = |a: i64, b: i64| -> i64 { a | b }); 52 | add_primitives!(typeinfo, "^" = |a: i64, b: i64| -> i64 { a ^ b }); 53 | add_primitives!(typeinfo, "<<" = |a: i64, b: i64| -> Opt { b.try_into().ok().and_then(|b| a.checked_shl(b)) }); 54 | add_primitives!(typeinfo, ">>" = |a: i64, b: i64| -> Opt { b.try_into().ok().and_then(|b| a.checked_shr(b)) }); 55 | add_primitives!(typeinfo, "not-i64" = |a: i64| -> i64 { !a }); 56 | 57 | add_primitives!(typeinfo, "log2" = |a: i64| -> i64 { (a as i64).ilog2() as i64 }); 58 | 59 | add_primitives!(typeinfo, "<" = |a: i64, b: i64| -> Opt { (a < b).then_some(()) }); 60 | add_primitives!(typeinfo, ">" = |a: i64, b: i64| -> Opt { (a > b).then_some(()) }); 61 | add_primitives!(typeinfo, "<=" = |a: i64, b: i64| -> Opt { (a <= b).then_some(()) }); 62 | add_primitives!(typeinfo, ">=" = |a: i64, b: i64| -> Opt { (a >= b).then_some(()) }); 63 | 64 | add_primitives!(typeinfo, "bool-=" = |a: i64, b: i64| -> bool { a == b }); 65 | add_primitives!(typeinfo, "bool-<" = |a: i64, b: i64| -> bool { a < b }); 66 | add_primitives!(typeinfo, "bool->" = |a: i64, b: i64| -> bool { a > b }); 67 | add_primitives!(typeinfo, "bool-<=" = |a: i64, b: i64| -> bool { a <= b }); 68 | add_primitives!(typeinfo, "bool->=" = |a: i64, b: i64| -> bool { a >= b }); 69 | 70 | add_primitives!(typeinfo, "min" = |a: i64, b: i64| -> i64 { a.min(b) }); 71 | add_primitives!(typeinfo, "max" = |a: i64, b: i64| -> i64 { a.max(b) }); 72 | 73 | add_primitives!(typeinfo, "to-string" = |a: i64| -> Symbol { a.to_string().into() }); 74 | 75 | // Must be in the i64 sort register function because the string sort is registered before the i64 sort. 76 | typeinfo.add_primitive(CountMatches { 77 | name: "count-matches".into(), 78 | string: typeinfo.get_sort_nofail(), 79 | int: self.clone(), 80 | }); 81 | 82 | } 83 | 84 | fn extract_term( 85 | &self, 86 | _egraph: &EGraph, 87 | value: Value, 88 | _extractor: &Extractor, 89 | termdag: &mut TermDag, 90 | ) -> Option<(Cost, Term)> { 91 | Some((1, termdag.lit(Literal::Int(value.bits as _)))) 92 | } 93 | } 94 | 95 | impl IntoSort for i64 { 96 | type Sort = I64Sort; 97 | fn store(self, _sort: &Self::Sort) -> Option { 98 | Some(Value { 99 | #[cfg(debug_assertions)] 100 | tag: I64Sort.name(), 101 | bits: self as u64, 102 | }) 103 | } 104 | } 105 | 106 | impl FromSort for i64 { 107 | type Sort = I64Sort; 108 | fn load(_sort: &Self::Sort, value: &Value) -> Self { 109 | value.bits as Self 110 | } 111 | } 112 | 113 | struct CountMatches { 114 | name: Symbol, 115 | string: Arc, 116 | int: Arc, 117 | } 118 | 119 | impl PrimitiveLike for CountMatches { 120 | fn name(&self) -> Symbol { 121 | self.name 122 | } 123 | 124 | fn get_type_constraints(&self, span: &Span) -> Box { 125 | AllEqualTypeConstraint::new(self.name(), span.clone()) 126 | .with_all_arguments_sort(self.string.clone()) 127 | .with_exact_length(3) 128 | .with_output_sort(self.int.clone()) 129 | .into_box() 130 | } 131 | 132 | fn apply( 133 | &self, 134 | values: &[Value], 135 | _sorts: (&[ArcSort], &ArcSort), 136 | _egraph: Option<&mut EGraph>, 137 | ) -> Option { 138 | let string1 = Symbol::load(&self.string, &values[0]).to_string(); 139 | let string2 = Symbol::load(&self.string, &values[1]).to_string(); 140 | Some(Value::from(string1.matches(&string2).count() as i64)) 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/sort/macros.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! add_primitives { 3 | ($type_info:expr, 4 | $name:literal = |$($param:ident : $param_t:ty),*| -> $ret:ty { $body:expr } 5 | ) => {{ 6 | let type_info: &mut _ = $type_info; 7 | #[allow(unused_imports, non_snake_case)] 8 | { 9 | use $crate::{*, ast::*, sort::*, constraint::*}; 10 | use ::std::sync::Arc; 11 | 12 | struct MyPrim {$( 13 | $param: Arc<<$param_t as FromSort>::Sort>, 14 | )* 15 | __out: Arc<<$ret as IntoSort>::Sort>, 16 | } 17 | 18 | impl PrimitiveLike for MyPrim { 19 | fn name(&self) -> Symbol { 20 | $name.into() 21 | } 22 | 23 | fn get_type_constraints( 24 | &self, 25 | span: &Span 26 | ) -> Box { 27 | let sorts = vec![$(self.$param.clone() as ArcSort,)* self.__out.clone() as ArcSort]; 28 | SimpleTypeConstraint::new(self.name(), sorts, span.clone()).into_box() 29 | } 30 | 31 | fn apply( 32 | &self, 33 | values: &[Value], 34 | _sorts: (&[ArcSort], &ArcSort), 35 | _egraph: Option<&mut EGraph>, 36 | ) -> Option { 37 | if let [$($param),*] = values { 38 | $(let $param: $param_t = <$param_t as FromSort>::load(&self.$param, $param);)* 39 | // print!("{}( ", $name); 40 | // $( print!("{}={:?}, ", stringify!($param), $param); )* 41 | let result: $ret = $body; 42 | // println!(") = {result:?}"); 43 | result.store(&self.__out) 44 | } else { 45 | panic!("wrong number of arguments") 46 | } 47 | } 48 | } 49 | type_info.add_primitive( Primitive::from(MyPrim { 50 | $( $param: type_info.get_sort_nofail::<<$param_t as IntoSort>::Sort>(), )* 51 | __out: type_info.get_sort_nofail::<<$ret as IntoSort>::Sort>(), 52 | })) 53 | } 54 | }}; 55 | } 56 | -------------------------------------------------------------------------------- /src/sort/mod.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | mod macros; 3 | use lazy_static::lazy_static; 4 | use std::fmt::Debug; 5 | use std::{any::Any, sync::Arc}; 6 | 7 | mod bigint; 8 | pub use bigint::*; 9 | mod bigrat; 10 | pub use bigrat::*; 11 | mod bool; 12 | pub use self::bool::*; 13 | mod string; 14 | pub use string::*; 15 | mod unit; 16 | pub use unit::*; 17 | mod i64; 18 | pub use self::i64::*; 19 | mod f64; 20 | pub use self::f64::*; 21 | mod map; 22 | pub use map::*; 23 | mod set; 24 | pub use set::*; 25 | mod vec; 26 | pub use vec::*; 27 | mod r#fn; 28 | pub use r#fn::*; 29 | mod multiset; 30 | pub use multiset::*; 31 | 32 | use crate::constraint::AllEqualTypeConstraint; 33 | use crate::extract::{Cost, Extractor}; 34 | use crate::*; 35 | 36 | pub trait Sort: Any + Send + Sync + Debug { 37 | fn name(&self) -> Symbol; 38 | 39 | fn as_arc_any(self: Arc) -> Arc; 40 | 41 | fn is_eq_sort(&self) -> bool { 42 | false 43 | } 44 | 45 | // return true if it is a container sort. 46 | fn is_container_sort(&self) -> bool { 47 | false 48 | } 49 | 50 | // return true if it is a container sort that contains ids. 51 | // only eq_sort and eq_container_sort need to be canonicalized. 52 | fn is_eq_container_sort(&self) -> bool { 53 | false 54 | } 55 | 56 | // Only eq_container_sort need to implement this method, 57 | // which returns a list of ids to be tracked. 58 | fn foreach_tracked_values<'a>( 59 | &'a self, 60 | value: &'a Value, 61 | mut f: Box, 62 | ) { 63 | for (sort, value) in self.inner_values(value) { 64 | if sort.is_eq_sort() { 65 | f(sort, value) 66 | } 67 | } 68 | } 69 | 70 | // Sort-wise canonicalization. Return true if value is modified. 71 | // Only EqSort or containers of EqSort should override. 72 | fn canonicalize(&self, value: &mut Value, unionfind: &UnionFind) -> bool { 73 | #[cfg(debug_assertions)] 74 | debug_assert_eq!(self.name(), value.tag); 75 | 76 | #[cfg(not(debug_assertions))] 77 | let _ = value; 78 | let _ = unionfind; 79 | false 80 | } 81 | 82 | /// Return the serialized name of the sort 83 | /// 84 | /// Only used for container sorts, which cannot be serialized with make_expr so need an explicit name 85 | fn serialized_name(&self, _value: &Value) -> Symbol { 86 | self.name() 87 | } 88 | 89 | /// Return the inner values and sorts. 90 | /// Only eq_container_sort need to implement this method, 91 | fn inner_values(&self, value: &Value) -> Vec<(ArcSort, Value)> { 92 | let _ = value; 93 | vec![] 94 | } 95 | 96 | fn register_primitives(self: Arc, info: &mut TypeInfo) { 97 | let _ = info; 98 | } 99 | 100 | /// Extracting a term (with smallest cost) out of a primitive value 101 | fn extract_term( 102 | &self, 103 | egraph: &EGraph, 104 | value: Value, 105 | _extractor: &Extractor, 106 | _termdag: &mut TermDag, 107 | ) -> Option<(Cost, Term)>; 108 | } 109 | 110 | // Note: this trait is currently intended to be implemented on the 111 | // same struct as `Sort`. If in the future we have dynamic presorts 112 | // (for example, we want to add partial application) we should revisit 113 | // this and make the methods take a `self` parameter. 114 | pub trait Presort { 115 | fn presort_name() -> Symbol; 116 | fn reserved_primitives() -> Vec; 117 | fn make_sort( 118 | typeinfo: &mut TypeInfo, 119 | name: Symbol, 120 | args: &[Expr], 121 | ) -> Result; 122 | } 123 | 124 | #[derive(Debug)] 125 | pub struct EqSort { 126 | pub name: Symbol, 127 | } 128 | 129 | impl Sort for EqSort { 130 | fn name(&self) -> Symbol { 131 | self.name 132 | } 133 | 134 | fn as_arc_any(self: Arc) -> Arc { 135 | self 136 | } 137 | 138 | fn is_eq_sort(&self) -> bool { 139 | true 140 | } 141 | 142 | fn canonicalize(&self, value: &mut Value, unionfind: &UnionFind) -> bool { 143 | #[cfg(debug_assertions)] 144 | debug_assert_eq!(self.name(), value.tag); 145 | 146 | let bits = unionfind.find(value.bits); 147 | if bits != value.bits { 148 | value.bits = bits; 149 | true 150 | } else { 151 | false 152 | } 153 | } 154 | 155 | fn extract_term( 156 | &self, 157 | _egraph: &EGraph, 158 | _value: Value, 159 | _extractor: &Extractor, 160 | _termdag: &mut TermDag, 161 | ) -> Option<(Cost, Term)> { 162 | unimplemented!("No extract_term for EqSort {}", self.name) 163 | } 164 | } 165 | 166 | pub trait FromSort: Sized { 167 | type Sort: Sort; 168 | fn load(sort: &Self::Sort, value: &Value) -> Self; 169 | } 170 | 171 | pub trait IntoSort: Sized { 172 | type Sort: Sort; 173 | fn store(self, sort: &Self::Sort) -> Option; 174 | } 175 | 176 | impl IntoSort for Option { 177 | type Sort = T::Sort; 178 | 179 | fn store(self, sort: &Self::Sort) -> Option { 180 | self?.store(sort) 181 | } 182 | } 183 | 184 | pub type PreSort = 185 | fn(typeinfo: &mut TypeInfo, name: Symbol, params: &[Expr]) -> Result; 186 | 187 | pub(crate) struct ValueEq; 188 | 189 | impl PrimitiveLike for ValueEq { 190 | fn name(&self) -> Symbol { 191 | "value-eq".into() 192 | } 193 | 194 | fn get_type_constraints(&self, span: &Span) -> Box { 195 | AllEqualTypeConstraint::new(self.name(), span.clone()) 196 | .with_exact_length(3) 197 | .with_output_sort(Arc::new(UnitSort)) 198 | .into_box() 199 | } 200 | 201 | fn apply( 202 | &self, 203 | values: &[Value], 204 | _sorts: (&[ArcSort], &ArcSort), 205 | _egraph: Option<&mut EGraph>, 206 | ) -> Option { 207 | assert_eq!(values.len(), 2); 208 | if values[0] == values[1] { 209 | Some(Value::unit()) 210 | } else { 211 | None 212 | } 213 | } 214 | } 215 | 216 | pub fn literal_sort(lit: &Literal) -> ArcSort { 217 | match lit { 218 | Literal::Int(_) => Arc::new(I64Sort) as ArcSort, 219 | Literal::Float(_) => Arc::new(F64Sort) as ArcSort, 220 | Literal::String(_) => Arc::new(StringSort) as ArcSort, 221 | Literal::Bool(_) => Arc::new(BoolSort) as ArcSort, 222 | Literal::Unit => Arc::new(UnitSort) as ArcSort, 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /src/sort/string.rs: -------------------------------------------------------------------------------- 1 | use std::num::NonZeroU32; 2 | 3 | use crate::{ast::Literal, constraint::AllEqualTypeConstraint}; 4 | 5 | use super::*; 6 | 7 | #[derive(Debug)] 8 | pub struct StringSort; 9 | 10 | lazy_static! { 11 | static ref STRING_SORT_NAME: Symbol = "String".into(); 12 | } 13 | 14 | impl Sort for StringSort { 15 | fn name(&self) -> Symbol { 16 | *STRING_SORT_NAME 17 | } 18 | 19 | fn as_arc_any(self: Arc) -> Arc { 20 | self 21 | } 22 | 23 | fn extract_term( 24 | &self, 25 | _egraph: &EGraph, 26 | value: Value, 27 | _extractor: &Extractor, 28 | termdag: &mut TermDag, 29 | ) -> Option<(Cost, Term)> { 30 | #[cfg(debug_assertions)] 31 | debug_assert_eq!(value.tag, self.name()); 32 | 33 | let sym = Symbol::from(NonZeroU32::new(value.bits as _).unwrap()); 34 | Some((1, termdag.lit(Literal::String(sym)))) 35 | } 36 | 37 | fn register_primitives(self: Arc, typeinfo: &mut TypeInfo) { 38 | typeinfo.add_primitive(Add { 39 | name: "+".into(), 40 | string: self.clone(), 41 | }); 42 | typeinfo.add_primitive(Replace { 43 | name: "replace".into(), 44 | string: self, 45 | }); 46 | } 47 | } 48 | 49 | // TODO could use a local symbol table 50 | 51 | impl IntoSort for Symbol { 52 | type Sort = StringSort; 53 | fn store(self, _sort: &Self::Sort) -> Option { 54 | Some(Value { 55 | #[cfg(debug_assertions)] 56 | tag: StringSort.name(), 57 | bits: NonZeroU32::from(self).get() as _, 58 | }) 59 | } 60 | } 61 | 62 | impl FromSort for Symbol { 63 | type Sort = StringSort; 64 | fn load(_sort: &Self::Sort, value: &Value) -> Self { 65 | NonZeroU32::new(value.bits as u32).unwrap().into() 66 | } 67 | } 68 | 69 | struct Add { 70 | name: Symbol, 71 | string: Arc, 72 | } 73 | 74 | impl PrimitiveLike for Add { 75 | fn name(&self) -> Symbol { 76 | self.name 77 | } 78 | 79 | fn get_type_constraints(&self, span: &Span) -> Box { 80 | AllEqualTypeConstraint::new(self.name(), span.clone()) 81 | .with_all_arguments_sort(self.string.clone()) 82 | .into_box() 83 | } 84 | 85 | fn apply( 86 | &self, 87 | values: &[Value], 88 | _sorts: (&[ArcSort], &ArcSort), 89 | _egraph: Option<&mut EGraph>, 90 | ) -> Option { 91 | let mut res_string: String = "".to_owned(); 92 | for value in values { 93 | let sym = Symbol::load(&self.string, value); 94 | res_string.push_str(sym.as_str()); 95 | } 96 | let res_symbol: Symbol = res_string.into(); 97 | Some(Value::from(res_symbol)) 98 | } 99 | } 100 | 101 | struct Replace { 102 | name: Symbol, 103 | string: Arc, 104 | } 105 | 106 | impl PrimitiveLike for Replace { 107 | fn name(&self) -> Symbol { 108 | self.name 109 | } 110 | 111 | fn get_type_constraints(&self, span: &Span) -> Box { 112 | AllEqualTypeConstraint::new(self.name(), span.clone()) 113 | .with_all_arguments_sort(self.string.clone()) 114 | .with_exact_length(4) 115 | .into_box() 116 | } 117 | 118 | fn apply( 119 | &self, 120 | values: &[Value], 121 | _sorts: (&[ArcSort], &ArcSort), 122 | _egraph: Option<&mut EGraph>, 123 | ) -> Option { 124 | let string1 = Symbol::load(&self.string, &values[0]).to_string(); 125 | let string2 = Symbol::load(&self.string, &values[1]).to_string(); 126 | let string3 = Symbol::load(&self.string, &values[2]).to_string(); 127 | let res: Symbol = string1.replace(&string2, &string3).into(); 128 | Some(Value::from(res)) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/sort/unit.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::{ast::Literal, constraint::AllEqualTypeConstraint, ArcSort, PrimitiveLike}; 3 | 4 | #[derive(Debug)] 5 | pub struct UnitSort; 6 | 7 | lazy_static! { 8 | static ref UNIT_SORT_NAME: Symbol = "Unit".into(); 9 | } 10 | 11 | impl Sort for UnitSort { 12 | fn name(&self) -> Symbol { 13 | *UNIT_SORT_NAME 14 | } 15 | 16 | fn as_arc_any(self: Arc) -> Arc { 17 | self 18 | } 19 | 20 | fn register_primitives(self: Arc, type_info: &mut TypeInfo) { 21 | type_info.add_primitive(NotEqualPrimitive { unit: self }) 22 | } 23 | 24 | fn extract_term( 25 | &self, 26 | _egraph: &EGraph, 27 | _value: Value, 28 | _extractor: &Extractor, 29 | termdag: &mut TermDag, 30 | ) -> Option<(Cost, Term)> { 31 | Some((1, termdag.lit(Literal::Unit))) 32 | } 33 | } 34 | 35 | impl IntoSort for () { 36 | type Sort = UnitSort; 37 | 38 | fn store(self, _sort: &Self::Sort) -> Option { 39 | Some(Value::unit()) 40 | } 41 | } 42 | 43 | pub struct NotEqualPrimitive { 44 | unit: ArcSort, 45 | } 46 | 47 | impl PrimitiveLike for NotEqualPrimitive { 48 | fn name(&self) -> Symbol { 49 | "!=".into() 50 | } 51 | 52 | fn get_type_constraints(&self, span: &Span) -> Box { 53 | AllEqualTypeConstraint::new(self.name(), span.clone()) 54 | .with_exact_length(3) 55 | .with_output_sort(self.unit.clone()) 56 | .into_box() 57 | } 58 | 59 | fn apply( 60 | &self, 61 | values: &[Value], 62 | _sorts: (&[ArcSort], &ArcSort), 63 | _egraph: Option<&mut EGraph>, 64 | ) -> Option { 65 | (values[0] != values[1]).then(Value::unit) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/unionfind.rs: -------------------------------------------------------------------------------- 1 | //! Baseline union-find implementation without sizes or ranks, using path 2 | //! halving for compression. 3 | //! 4 | //! This implementation uses interior mutability for `find`. 5 | use crate::util::HashMap; 6 | use crate::{Symbol, Value}; 7 | 8 | use std::cell::Cell; 9 | use std::fmt::Debug; 10 | use std::mem; 11 | 12 | pub type Id = u64; 13 | 14 | #[derive(Debug, Clone, Default)] 15 | pub struct UnionFind { 16 | parents: Vec>, 17 | n_unions: usize, 18 | recent_ids: HashMap>, 19 | staged_ids: HashMap>, 20 | } 21 | 22 | impl UnionFind { 23 | /// The number of unions that have been performed over the lifetime of this 24 | /// data-structure. 25 | pub(crate) fn n_unions(&self) -> usize { 26 | self.n_unions 27 | } 28 | 29 | /// Create a fresh [`Id`]. 30 | pub(crate) fn make_set(&mut self) -> Id { 31 | let res = self.parents.len() as u64; 32 | self.parents.push(Cell::new(res)); 33 | res 34 | } 35 | 36 | /// The number of ids that recently stopped being canonical. 37 | pub(crate) fn new_ids(&self, sort_filter: impl Fn(Symbol) -> bool) -> usize { 38 | self.recent_ids 39 | .iter() 40 | .filter_map(|(sort, ids)| { 41 | if sort_filter(*sort) { 42 | Some(ids.len()) 43 | } else { 44 | None 45 | } 46 | }) 47 | .sum() 48 | } 49 | 50 | /// Clear any ids currently marked as dirty and then move any ids marked 51 | /// non-canonical since the last call to this method (or the 52 | /// data-structure's creation) into the dirty set. 53 | pub(crate) fn clear_recent_ids(&mut self) { 54 | mem::swap(&mut self.recent_ids, &mut self.staged_ids); 55 | self.staged_ids.values_mut().for_each(Vec::clear); 56 | } 57 | 58 | /// Iterate over the ids of the given sort marked as "dirty", i.e. any 59 | /// [`Id`]s that ceased to be canonical between the last call to 60 | /// [`clear_recent_ids`] and the call prior to that. 61 | /// 62 | /// [`clear_recent_ids`]: UnionFind::clear_recent_ids 63 | pub(crate) fn dirty_ids(&self, sort: Symbol) -> impl Iterator + '_ { 64 | let ids = self 65 | .recent_ids 66 | .get(&sort) 67 | .map(|ids| ids.as_slice()) 68 | .unwrap_or(&[]); 69 | ids.iter().copied() 70 | } 71 | 72 | /// Look up the canonical representative for the given [`Id`]. 73 | pub fn find(&self, id: Id) -> Id { 74 | let mut cur = self.parent(id); 75 | loop { 76 | let next = self.parent(cur.get()); 77 | if cur.get() == next.get() { 78 | return cur.get(); 79 | } 80 | // Path halving 81 | let grand = self.parent(next.get()); 82 | cur.set(grand.get()); 83 | cur = grand; 84 | } 85 | } 86 | 87 | /// Merge the equivalence classes associated with the two values. 88 | /// 89 | /// This method assumes that the given values belong to the same, "eq-able", 90 | /// sort. Its behavior is unspecified on other values. 91 | pub(crate) fn union_values(&mut self, val1: Value, val2: Value, sort: Symbol) -> Value { 92 | #[cfg(debug_assertions)] 93 | debug_assert_eq!(val1.tag, val2.tag); 94 | 95 | Value { 96 | #[cfg(debug_assertions)] 97 | tag: val1.tag, 98 | bits: self.union(val1.bits, val2.bits, sort), 99 | } 100 | } 101 | 102 | /// Like [`union_values`], but operating on raw [`Id`]s. 103 | /// 104 | /// [`union_values`]: UnionFind::union_values 105 | pub(crate) fn union(&mut self, id1: Id, id2: Id, sort: Symbol) -> Id { 106 | let (res, reparented) = self.do_union(id1, id2); 107 | if let Some(id) = reparented { 108 | self.staged_ids.entry(sort).or_default().push(id) 109 | } 110 | res 111 | } 112 | 113 | fn do_union(&mut self, id1: Id, id2: Id) -> (Id, Option) { 114 | let id1 = self.find(id1); 115 | let id2 = self.find(id2); 116 | if id1 != id2 { 117 | self.parent(id2).set(id1); 118 | self.n_unions += 1; 119 | (id1, Some(id2)) 120 | } else { 121 | (id1, None) 122 | } 123 | } 124 | 125 | fn parent(&self, id: Id) -> &Cell { 126 | &self.parents[id as usize] 127 | } 128 | } 129 | 130 | #[cfg(test)] 131 | mod tests { 132 | use super::*; 133 | 134 | fn ids(us: impl IntoIterator) -> Vec> { 135 | us.into_iter().map(Cell::new).collect() 136 | } 137 | 138 | #[test] 139 | fn union_find() { 140 | let n = 10; 141 | 142 | let mut uf = UnionFind::default(); 143 | for _ in 0..n { 144 | uf.make_set(); 145 | } 146 | 147 | // test the initial condition of everyone in their own set 148 | assert_eq!(uf.parents, ids(0..n)); 149 | 150 | // build up one set 151 | uf.union(0, 1, "T".into()); 152 | uf.union(0, 2, "T".into()); 153 | uf.union(0, 3, "T".into()); 154 | 155 | // build up another set 156 | uf.union(6, 7, "T".into()); 157 | uf.union(6, 8, "T".into()); 158 | uf.union(6, 9, "T".into()); 159 | 160 | // this should compress all paths 161 | for i in 0..n { 162 | uf.find(i); 163 | } 164 | 165 | // indexes: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 166 | let expected = vec![0, 0, 0, 0, 4, 5, 6, 6, 6, 6]; 167 | assert_eq!(uf.parents, ids(expected)); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | 3 | use std::fmt::Display; 4 | 5 | use crate::core::SpecializedPrimitive; 6 | #[allow(unused_imports)] 7 | use crate::*; 8 | 9 | pub(crate) type BuildHasher = std::hash::BuildHasherDefault; 10 | 11 | /// Use an index map by default everywhere. 12 | /// We could fix the seed, but symbol generation is not determinisic so 13 | /// this doesn't fix the problem. 14 | #[cfg(not(feature = "nondeterministic"))] 15 | pub(crate) type HashMap = indexmap::IndexMap; 16 | #[cfg(feature = "nondeterministic")] 17 | pub(crate) type HashMap = hashbrown::HashMap; 18 | 19 | #[cfg(not(feature = "nondeterministic"))] 20 | pub(crate) type HashSet = indexmap::IndexSet; 21 | #[cfg(feature = "nondeterministic")] 22 | pub(crate) type HashSet = hashbrown::HashSet; 23 | 24 | #[cfg(feature = "nondeterministic")] 25 | pub(crate) type HEntry<'a, A, B, D> = hashbrown::hash_map::Entry<'a, A, B, D>; 26 | #[cfg(not(feature = "nondeterministic"))] 27 | pub(crate) type HEntry<'a, A, B> = Entry<'a, A, B>; 28 | 29 | pub type IndexMap = indexmap::IndexMap; 30 | pub type IndexSet = indexmap::IndexSet; 31 | 32 | pub(crate) fn concat_vecs(to: &mut Vec, mut from: Vec) { 33 | if to.len() < from.len() { 34 | std::mem::swap(to, &mut from) 35 | } 36 | to.extend(from); 37 | } 38 | 39 | pub(crate) struct ListDisplay<'a, TS>(pub TS, pub &'a str); 40 | 41 | impl<'a, TS> Display for ListDisplay<'a, TS> 42 | where 43 | TS: Clone + IntoIterator, 44 | TS::Item: Display, 45 | { 46 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 47 | let mut did_something = false; 48 | for item in self.0.clone().into_iter() { 49 | if did_something { 50 | f.write_str(self.1)?; 51 | } 52 | Display::fmt(&item, f)?; 53 | did_something = true; 54 | } 55 | Ok(()) 56 | } 57 | } 58 | 59 | pub(crate) struct ListDebug<'a, TS>(pub TS, pub &'a str); 60 | 61 | impl<'a, TS> Debug for ListDebug<'a, TS> 62 | where 63 | TS: Clone + IntoIterator, 64 | TS::Item: Debug, 65 | { 66 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 67 | let mut did_something = false; 68 | for item in self.0.clone().into_iter() { 69 | if did_something { 70 | f.write_str(self.1)?; 71 | } 72 | Debug::fmt(&item, f)?; 73 | did_something = true; 74 | } 75 | Ok(()) 76 | } 77 | } 78 | 79 | /// Generates fresh symbols for internal use during typechecking and flattening. 80 | /// These are guaranteed not to collide with the 81 | /// user's symbols because they use $. 82 | #[derive(Debug, Clone, PartialEq, Eq)] 83 | pub struct SymbolGen { 84 | gen: usize, 85 | reserved_string: String, 86 | } 87 | 88 | impl SymbolGen { 89 | pub fn new(reserved_string: String) -> Self { 90 | Self { 91 | gen: 0, 92 | reserved_string, 93 | } 94 | } 95 | 96 | pub fn has_been_used(&self) -> bool { 97 | self.gen > 0 98 | } 99 | } 100 | 101 | /// This trait lets us statically dispatch between `fresh` methods for generic structs. 102 | pub trait FreshGen { 103 | fn fresh(&mut self, name_hint: &Head) -> Leaf; 104 | } 105 | 106 | impl FreshGen for SymbolGen { 107 | fn fresh(&mut self, name_hint: &Symbol) -> Symbol { 108 | let s = format!("{}{}{}", self.reserved_string, name_hint, self.gen); 109 | self.gen += 1; 110 | Symbol::from(s) 111 | } 112 | } 113 | 114 | impl FreshGen for SymbolGen { 115 | fn fresh(&mut self, name_hint: &ResolvedCall) -> ResolvedVar { 116 | let s = format!("{}{}{}", self.reserved_string, name_hint, self.gen); 117 | self.gen += 1; 118 | let sort = match name_hint { 119 | ResolvedCall::Func(f) => f.output.clone(), 120 | ResolvedCall::Primitive(SpecializedPrimitive { output, .. }) => output.clone(), 121 | }; 122 | ResolvedVar { 123 | name: s.into(), 124 | sort, 125 | // fresh variables are never global references, since globals 126 | // are desugared away by `remove_globals` 127 | is_global_ref: false, 128 | } 129 | } 130 | } 131 | 132 | // This is a convenient for `for<'a> impl Into for &'a T` 133 | pub(crate) trait SymbolLike { 134 | fn to_symbol(&self) -> Symbol; 135 | } 136 | 137 | impl SymbolLike for Symbol { 138 | fn to_symbol(&self) -> Symbol { 139 | *self 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/value.rs: -------------------------------------------------------------------------------- 1 | use ordered_float::OrderedFloat; 2 | use std::num::NonZeroU32; 3 | 4 | use lazy_static::lazy_static; 5 | 6 | use crate::ast::Symbol; 7 | 8 | #[cfg(debug_assertions)] 9 | use crate::{BoolSort, F64Sort, I64Sort, Sort, StringSort}; 10 | 11 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] 12 | // FIXME this shouldn't be pub 13 | pub struct Value { 14 | // since egglog is type-safe, we don't need to store the tag 15 | // however, it is useful in debugging, so we keep it in debug builds 16 | #[cfg(debug_assertions)] 17 | pub tag: Symbol, 18 | pub bits: u64, 19 | } 20 | 21 | lazy_static! { 22 | static ref BOGUS: Symbol = "__bogus__".into(); 23 | static ref UNIT: Symbol = "Unit".into(); 24 | } 25 | 26 | impl Value { 27 | pub fn unit() -> Self { 28 | Value { 29 | #[cfg(debug_assertions)] 30 | tag: *UNIT, 31 | bits: 0, 32 | } 33 | } 34 | 35 | pub fn fake() -> Self { 36 | Value { 37 | #[cfg(debug_assertions)] 38 | tag: *BOGUS, 39 | bits: 1234567890, 40 | } 41 | } 42 | } 43 | 44 | impl From for Value { 45 | fn from(i: i64) -> Self { 46 | Self { 47 | #[cfg(debug_assertions)] 48 | tag: I64Sort.name(), 49 | bits: i as u64, 50 | } 51 | } 52 | } 53 | 54 | impl From> for Value { 55 | fn from(f: OrderedFloat) -> Self { 56 | Self { 57 | #[cfg(debug_assertions)] 58 | tag: F64Sort.name(), 59 | bits: f.into_inner().to_bits(), 60 | } 61 | } 62 | } 63 | 64 | impl From for Value { 65 | fn from(s: Symbol) -> Self { 66 | Self { 67 | #[cfg(debug_assertions)] 68 | tag: StringSort.name(), 69 | bits: NonZeroU32::from(s).get().into(), 70 | } 71 | } 72 | } 73 | 74 | impl From for Value { 75 | fn from(b: bool) -> Self { 76 | Self { 77 | #[cfg(debug_assertions)] 78 | tag: BoolSort.name(), 79 | bits: b as u64, 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /tests/antiunify.egg: -------------------------------------------------------------------------------- 1 | (datatype Expr 2 | (Num i64) 3 | (Var String) 4 | (Add Expr Expr)) 5 | 6 | (rewrite (Add x y) (Add y x)) 7 | (rewrite (Add (Num x) (Num y)) (Num (+ x y))) 8 | 9 | ;; antiunificaiton returns an expression that could unify with either 10 | ;; of the input expressions 11 | ;; (AU x y) can be considered a placeholder variable 12 | (constructor AU (Expr Expr) Expr) 13 | 14 | (rewrite (AU x x) x) 15 | (rewrite 16 | (AU (Add a b) (Add c d)) 17 | (Add (AU a c) (AU b d))) 18 | 19 | (let e1 (Add (Var "x") (Add (Num 1) (Num 2)))) 20 | (let e2 (Add (Num 3) (Var "y"))) 21 | 22 | (let au12 (AU e1 e2)) 23 | 24 | (run 4) 25 | (check (= au12 (Add (Num 3) (AU (Var "x") (Var "y"))))) 26 | (query-extract au12) 27 | -------------------------------------------------------------------------------- /tests/array.egg: -------------------------------------------------------------------------------- 1 | ; Smtlib theory of arrays 2 | ; https://smtlib.cs.uiowa.edu/theories-ArraysEx.shtml 3 | ; http://smtlib.cs.uiowa.edu/version1/theories/Arrays.smt 4 | 5 | (datatype Math 6 | (Num i64) 7 | (Var String) 8 | ) 9 | 10 | 11 | (datatype Array 12 | (Const i64) 13 | (AVar String) 14 | ) 15 | 16 | (constructor add (Math Math) Math) 17 | (constructor select (Array Math) Math) 18 | (constructor store (Array Math Math) Array) 19 | 20 | (relation neq (Math Math)) 21 | 22 | (rule ((neq x y)) 23 | ((neq y x))) 24 | 25 | (rule ((neq x x)) 26 | ((panic "query (neq x x) found something equal to itself"))) 27 | 28 | 29 | ; injectivity rules take not equal to not equal. 30 | (rule ((neq x y) (= (add x z) e)) 31 | ((neq (add x z) (add y z)))) 32 | (rule ((= (add x (Num i)) e) (!= i 0)) 33 | ((neq e x))) 34 | 35 | 36 | (rule ((= (Num a) n1) (= (Num b) n2) (!= a b)) 37 | ((neq n1 n2))) 38 | 39 | ; select gets from store 40 | (rewrite (select (store mem i e) i) e) 41 | ; select passes through wrong index 42 | (rule ((= (select (store mem i1 e) i2) e1) (neq i1 i2)) 43 | ((union (select mem i2) e1))) 44 | ; aliasing writes destroy old value 45 | (rewrite (store (store mem i e1) i e2) (store mem i e2)) 46 | ; non-aliasing writes commutes 47 | (rule ((= (store (store mem i2 e2) i1 e1) mem1) (neq i1 i2)) 48 | ((union (store (store mem i1 e1) i2 e2) mem1))) 49 | 50 | ; typical math rules 51 | (rewrite (add x y) (add y x)) 52 | (rewrite (add (add x y) z) (add x (add y z))) 53 | (rewrite (add (Num x) (Num y)) (Num (+ x y))) 54 | (rewrite (add x (Num 0)) x) 55 | 56 | (push) 57 | (let r1 (Var "r1")) 58 | (let r2 (Var "r2")) 59 | (let r3 (Var "r3")) 60 | (let mem1 (AVar "mem1")) 61 | 62 | (neq r1 r2) 63 | (neq r2 r3) 64 | (neq r1 r3) 65 | (let test1 (select (store mem1 r1 (Num 42)) r1)) 66 | (let test2 (select (store mem1 r1 (Num 42)) (add r1 (Num 17)))) 67 | (let test3 (select (store (store mem1 (add r1 r2) (Num 1)) (add r2 r1) (Num 2)) (add r1 r3))) 68 | (let test4 (add (Num 1) (add (add (Num 1) (add (Num 1) r1)) (Num -3)))) 69 | 70 | (run 5) 71 | (check (= test1 (Num 42))) 72 | (check (neq r1 r2)) 73 | (check (neq r1 (add r1 (Num 17)))) 74 | (check (= test2 (select mem1 (add r1 (Num 17))))) 75 | (check (= test3 (select mem1 (add r1 r3)))) 76 | (check (= test4 r1)) 77 | (pop) 78 | -------------------------------------------------------------------------------- /tests/bdd.egg: -------------------------------------------------------------------------------- 1 | ; Binary Decision Diagrams are if-then-else trees/ compressed tries that hash cons their leaves 2 | ; This is easily expressible in the facilities provided. Everything in egglog is automatcally shared 3 | ; and Compression is easily expressible as a rule. 4 | 5 | ; They are a notion of first class set useful for certain classes of uniformly describable sets. 6 | ; https://en.wikipedia.org/wiki/Binary_decision_diagram 7 | ; https://www.lri.fr/~filliatr/ftp/publis/hash-consing2.pdf Type-Safe Modular Hash-Consing - Section 3.3 8 | 9 | (datatype BDD 10 | (ITE i64 BDD BDD) ; variables labelled by number 11 | ) 12 | (constructor TrueConst () BDD) 13 | (let True (TrueConst)) 14 | (constructor FalseConst () BDD) 15 | (let False (FalseConst)) 16 | 17 | ; compress unneeded nodes 18 | (rewrite (ITE n a a) a) 19 | 20 | (constructor bddand (BDD BDD) BDD) 21 | (rewrite (bddand x y) (bddand y x)) 22 | (rewrite (bddand False n) False) 23 | (rewrite (bddand True x) x) 24 | 25 | ; We use an order where low variables are higher in tree 26 | ; Could go the other way. 27 | (rewrite (bddand (ITE n a1 a2) (ITE m b1 b2)) 28 | (ITE n (bddand a1 (ITE m b1 b2)) (bddand a2 (ITE m b1 b2))) 29 | :when ((< n m)) 30 | ) 31 | (rewrite (bddand (ITE n a1 a2) (ITE n b1 b2)) 32 | (ITE n (bddand a1 b1) (bddand a2 b2)) 33 | ) 34 | 35 | (constructor bddor (BDD BDD) BDD) 36 | (rewrite (bddor x y) (bddor y x)) 37 | (rewrite (bddor True n) True) 38 | (rewrite (bddor False x) x) 39 | (rewrite (bddor (ITE n a1 a2) (ITE m b1 b2)) 40 | (ITE n (bddor a1 (ITE m b1 b2)) (bddor a2 (ITE m b1 b2))) 41 | :when ((< n m)) 42 | ) 43 | (rewrite (bddor (ITE n a1 a2) (ITE n b1 b2)) 44 | (ITE n (bddor a1 b1) (bddor a2 b2)) 45 | ) 46 | 47 | (constructor bddnot (BDD) BDD) 48 | (rewrite (bddnot True) False) 49 | (rewrite (bddnot False) True) 50 | (rewrite (bddnot (ITE n a1 a2)) (ITE n (bddnot a1) (bddnot a2))) 51 | 52 | 53 | (constructor bddxor (BDD BDD) BDD) 54 | (rewrite (bddxor x y) (bddxor y x)) 55 | (rewrite (bddxor True n) (bddnot n)) 56 | (rewrite (bddxor False x) x) 57 | 58 | (rewrite (bddxor (ITE n a1 a2) (ITE m b1 b2)) 59 | (ITE n (bddxor a1 (ITE m b1 b2)) (bddxor a2 (ITE m b1 b2))) 60 | :when ((< n m)) 61 | ) 62 | (rewrite (bddxor (ITE n a1 a2) (ITE n b1 b2)) 63 | (ITE n (bddxor a1 b1) (bddxor a2 b2)) 64 | ) 65 | 66 | (push) 67 | ;;; Tests 68 | 69 | (let v0 (ITE 0 True False)) 70 | (let v1 (ITE 1 True False)) 71 | (let v2 (ITE 2 True False)) 72 | 73 | (let t0 (bddnot (bddnot v0))) 74 | (let t1 (bddor v0 (bddnot v0))) 75 | (let t2 (bddand v0 (bddnot v0))) 76 | (let t3 (bddand v0 v0)) 77 | (let t4 (bddor v0 v0)) 78 | (let t5 (bddxor (bddnot v0) v0)) 79 | (let t6 (bddand (bddor v1 v2) v2)) 80 | 81 | (let t7a (bddxor (bddnot v0) v1)) 82 | (let t7b (bddxor v0 (bddnot v1))) 83 | (let t7c (bddnot (bddxor v0 v1))) 84 | 85 | (let t8 (bddand v1 v2)) 86 | 87 | (let t9 (bddand (bddnot v1) (bddand (bddnot v0) (bddxor v0 v1)))) 88 | (let t10 (bddor (bddnot v1) (bddor (bddnot v0) (bddxor v0 (bddnot v1))))) 89 | 90 | (run 30) 91 | 92 | (check (= t0 v0)) ; bddnot cancels 93 | (check (= t1 True)) 94 | (check (= t2 False)) 95 | (check (= t3 v0)) 96 | (check (= t4 v0)) 97 | (check (= t5 True)) 98 | (check (= t6 v2)) 99 | 100 | (check (= t7a t7b)) 101 | (check (= t7a t7c)) 102 | 103 | (check (= t8 (ITE 1 (ITE 2 True False) False))) 104 | 105 | (check (= t9 False)) 106 | (check (= t10 True)) 107 | (pop) 108 | -------------------------------------------------------------------------------- /tests/before-proofs.egg: -------------------------------------------------------------------------------- 1 | (datatype Math 2 | (Add Math Math) 3 | (Sub Math Math) 4 | (Const i64) 5 | (Var String)) 6 | 7 | (rewrite (Add a b) (Add (Add a b) (Const 0))) 8 | 9 | (rewrite (Add a b) (Add b a)) 10 | 11 | 12 | (rewrite (Add a (Add b c)) 13 | (Add (Add a b) c)) 14 | 15 | (let two 2) 16 | (let start1 (Add (Var "x") (Const two))) 17 | ;; add original proofs 18 | 19 | (run 3) 20 | 21 | 22 | (check (!= (Var "x") (Const two))) 23 | (check (= (Add (Var "x") (Const two)) 24 | (Add (Const two) (Var "x")))) 25 | 26 | (let zero (Const 0)) 27 | (let addx2 (Add (Var "x") (Const two))) 28 | (let addx20 (Add addx2 zero)) 29 | (let addzerofront (Add (Add zero (Var "x")) (Const two))) 30 | 31 | (check (= addx2 32 | addx20)) 33 | -------------------------------------------------------------------------------- /tests/bignum.egg: -------------------------------------------------------------------------------- 1 | 2 | (let x (bigint -1234)) 3 | (let y (from-string "2")) 4 | (let z (bigrat x y)) 5 | (check (= (to-string (numer z)) "-617")) 6 | 7 | (function bignums (BigInt BigInt) BigRat :no-merge) 8 | (set (bignums x y) z) 9 | (check 10 | (= (bignums a b) c) 11 | (= (numer c) (>> a 1)) 12 | (= (denom c) (>> b 1)) 13 | ) 14 | -------------------------------------------------------------------------------- /tests/birewrite.egg: -------------------------------------------------------------------------------- 1 | (datatype Math (Add Math Math) (Lit i64)) 2 | 3 | (birewrite (Add (Add x y) z) (Add x (Add y z))) 4 | 5 | (let a (Lit 1)) 6 | (let b (Lit 2)) 7 | (let c (Lit 3)) 8 | 9 | (let d (Lit 4)) 10 | (let e (Lit 5)) 11 | (let f (Lit 6)) 12 | 13 | (let ex1 (Add (Add a b) c)) 14 | (let ex2 (Add d (Add e f))) 15 | 16 | (run 10) 17 | (check (= ex1 (Add a (Add b c)))) 18 | (check (= ex2 (Add (Add d e) f))) 19 | -------------------------------------------------------------------------------- /tests/bitwise.egg: -------------------------------------------------------------------------------- 1 | (check (= 0 (& 10 0))) 2 | (check (= 8 (& 8 10))) 3 | (check (= 10 (| 8 10))) 4 | (check (= 2 (^ 8 10))) 5 | (check (= 8 (<< 1 3))) 6 | (check (= 1 (>> 8 3))) 7 | (check (= 2 (% 8 3))) 8 | (check (= 2 (/ 8 3))) 9 | (check (= -1 (not-i64 0))) 10 | 11 | ; bitsets 12 | ;(function bs-union (i64 i64) i64) 13 | ;(rewrite (bs-union a b) (| a b)) 14 | 15 | ;(function bs-inter (i64 i64) i64) 16 | ;(rewrite (bs-inter a b) (& a b)) 17 | 18 | ;(function bs-comp (i64) i64) 19 | ;(rewrite (bs-comp a) (bvnot a)) 20 | 21 | ; singleton set 22 | ;(function bs-sing (i64) i64) 23 | ;(rewrite (bs-sing a) (1 << a)) 24 | 25 | ;(function bs-insert (i64 i64) i64) 26 | ;(rewrite (bs-insert s x) (| s (1 << a)) 27 | 28 | ;(function bs-diff (i64 i64) i64) 29 | ;(rewrite (bs-diff a b) (^ a (bs-inter a b)) 30 | 31 | ;(let bs-empty 0) 32 | 33 | ;(let bs-subset (i64 i64) bool) 34 | ;(rewrite (bs-subset x y) (is-zero (bs-diff x y))) 35 | 36 | ;(let bs-is-elem (i64 i64) bool) 37 | ;(rewrite (bs-is-elem s x) (not (is-zero (bs-inter s (sing x))))) 38 | -------------------------------------------------------------------------------- /tests/bool.egg: -------------------------------------------------------------------------------- 1 | 2 | (check (= (and true true) true)) 3 | (check (= (and true false) false)) 4 | (check (= (or true false) true)) 5 | (check (!= (or true false) false)) 6 | 7 | (check (= (bool-= 1 1) true)) 8 | (check (= (bool-= -5 -5) true)) 9 | (check (= (bool-= 1 3) false)) 10 | (check (= (bool-= 3 1) false)) 11 | 12 | (check (= (bool-< 1 2) true)) 13 | (check (= (bool-< 2 1) false)) 14 | (check (= (bool-< 1 1) false)) 15 | 16 | (check (= (bool-<= 1 2) true)) 17 | (check (= (bool-<= 2 1) false)) 18 | (check (= (bool-<= 1 1) true)) 19 | 20 | (check (= (bool-> 1 2) false)) 21 | (check (= (bool-> 2 1) true)) 22 | (check (= (bool-> 1 1) false)) 23 | 24 | (check (= (bool->= 1 2) false)) 25 | (check (= (bool->= 2 1) true)) 26 | (check (= (bool->= 1 1) true)) 27 | 28 | ; Test bool's tag 29 | (relation R (i64)) 30 | (function F (i64) bool :no-merge) 31 | 32 | (rule 33 | ((R i)) 34 | ((set (F i) true)) 35 | ) 36 | 37 | (R 0) 38 | 39 | (run 3) 40 | -------------------------------------------------------------------------------- /tests/calc.egg: -------------------------------------------------------------------------------- 1 | (datatype G) 2 | (constructor IConst () G) 3 | (let I (IConst)) 4 | (constructor AConst () G) 5 | (let A (AConst)) 6 | (constructor BConst () G) 7 | (let B (BConst)) 8 | (constructor g* (G G) G) 9 | (constructor inv (G) G) 10 | (birewrite (g* (g* a b) c) (g* a (g* b c))) ; assoc 11 | (rewrite (g* I a) a) ; idl 12 | (rewrite (g* a I) a) ; idr 13 | (rewrite (g* (inv a) a) I) ; invl 14 | (rewrite (g* a (inv a)) I) ; invr 15 | 16 | ; A is cyclic of period 4 17 | (rewrite (g* A (g* A (g* A A))) I) 18 | 19 | (let A2 (g* A A)) 20 | (let A4 (g* A2 A2)) 21 | (let A8 (g* A4 A4)) 22 | 23 | 24 | (push) 25 | (g* A4 A4) 26 | 27 | (run 10000 :until (= (g* A4 A4) (g* (g* A2 A2) (g* A2 A2)))) 28 | 29 | (check (= (g* A4 A4) (g* (g* A2 A2) (g* A2 A2)))) 30 | (pop) 31 | 32 | (push) 33 | (g* (g* A2 A2) (g* A2 A2)) 34 | 35 | (run 10000 :until (= (g* (g* A2 A2) (g* A2 A2)) 36 | (g* A2 (g* A2 (g* A2 A2))))) 37 | (check (= (g* (g* A2 A2) (g* A2 A2)) 38 | (g* A2 (g* A2 (g* A2 A2))))) 39 | (pop) 40 | 41 | 42 | (constructor aConst () G) 43 | (constructor bConst () G) 44 | (let a (aConst)) 45 | (let b (bConst)) 46 | (push) 47 | 48 | (g* (g* b (g* (inv a) a)) (inv b)) 49 | 50 | (run 100000 :until (= (g* (g* b (g* (inv a) a)) (inv b)) (g* b (inv b)))) 51 | 52 | (check (= (g* (g* b (g* (inv a) a)) (inv b)) (g* b (inv b)))) 53 | 54 | (pop) 55 | 56 | (push) 57 | (g* b (inv b)) 58 | (run 100000 :until (= (g* b (inv b)) I)) 59 | (check (= (g* b (inv b)) I)) 60 | 61 | (pop) 62 | -------------------------------------------------------------------------------- /tests/combinators.egg: -------------------------------------------------------------------------------- 1 | ; Substitution in lambda-calculus via S/K/I combinators. Extremely slow, as 2 | ; abstraction elimination does not pay attention to whether variables are free 3 | ; in an expression before introducing 'S'. 4 | ; 5 | ; Provides an example of how to implement substitution by embedding in a 6 | ; 'richer' data-type and then mapping back to syntax. 7 | 8 | (datatype Expr 9 | (Var String :cost 100) 10 | (Abs String Expr) 11 | (If Expr Expr Expr) 12 | (N i64) 13 | (Add Expr Expr) 14 | (App Expr Expr)) 15 | (constructor TConst () Expr) 16 | (let T (TConst)) 17 | (constructor FConst () Expr) 18 | (let F (FConst)) 19 | 20 | 21 | ; (\x. (if x then 0 else 1) + 2) false 22 | (let test 23 | (App 24 | (Abs "x" (Add (If (Var "x") (N 0) (N 1)) (N 2))) F)) 25 | 26 | (datatype CExpr 27 | (CVar String :cost 10000) ; (variables that haven't been eliminated yet) 28 | (CAbs String CExpr :cost 10000) ; (abstractions that haven't been eliminated yet) 29 | (CN i64) 30 | (CApp CExpr CExpr)) 31 | (constructor CTConst () CExpr) 32 | (let CT (CTConst)) 33 | (constructor CFConst () CExpr) 34 | (let CF (CFConst)) 35 | (constructor CIfConst () CExpr) 36 | (let CIf (CIfConst)) 37 | (constructor CAddConst () CExpr) 38 | (let CAdd (CAddConst)) 39 | (constructor SConst () CExpr) 40 | (let S (SConst)) 41 | (constructor KConst () CExpr) 42 | (let K (KConst)) 43 | (constructor IConst () CExpr) 44 | (let I (IConst)) 45 | 46 | ;;;; Conversion functions 47 | (constructor Comb (Expr) CExpr :cost 1000000) 48 | (constructor Uncomb (CExpr) Expr) 49 | (rewrite (Comb (Uncomb cx)) cx) 50 | (rewrite (Uncomb (Comb x)) x) 51 | 52 | ; Mechanical mappings back and forth. 53 | ; Note: we avoid resugaring S/K/I 54 | (rule ((= x (N n))) ((union (Comb x) (CN n)))) 55 | (rule ((= cx (CN n))) ((union (Uncomb cx) (N n)))) 56 | (rule ((= x T)) ((union (Comb x) CT))) 57 | (rule ((= cx CT)) ((union (Uncomb cx) T))) 58 | (rule ((= x F)) ((union (Comb x) CF))) 59 | (rule ((= cx CF)) ((union (Uncomb cx) F))) 60 | 61 | (rule ((= x (If c t f))) 62 | ((union (Comb x) (CApp (CApp (CApp CIf (Comb c)) (Comb t)) (Comb f))))) 63 | (rule ((= cx (CApp (CApp (CApp CIf cc) ct) cf))) 64 | ((union (Uncomb cx) (If (Uncomb cc) (Uncomb ct) (Uncomb cf))))) 65 | 66 | (rule ((= x (Add l r))) 67 | ((union (Comb x) (CApp (CApp CAdd (Comb l)) (Comb r))))) 68 | (rule ((= cx (CApp (CApp CAdd cl) cr))) 69 | ((union (Uncomb cx) (Add (Uncomb cl) (Uncomb cr))))) 70 | (rule ((= x (App f a))) ((union (Comb x) (CApp (Comb f) (Comb a))))) 71 | 72 | (rule ((= x (Var v))) ((union (Comb x) (CVar v)))) 73 | (rule ((= x (Abs v body))) ((union (Comb x) (CAbs v (Comb body))))) 74 | 75 | ;;;; Abstraction Elimination 76 | (rewrite (CAbs v (CVar v)) I) 77 | ; Hacks, could be replaced by !free computation. 78 | (rewrite (CAbs v1 (CVar v2)) (CApp K (CVar v2)) 79 | :when ((!= v1 v2))) 80 | (rewrite (CAbs v (CN n)) (CApp K (CN n))) 81 | (rewrite (CAbs v CT) (CApp K CT)) 82 | (rewrite (CAbs v CF) (CApp K CF)) 83 | (rewrite (CAbs v CIf) (CApp K CIf)) 84 | (rewrite (CAbs v CAdd) (CApp K CAdd)) 85 | (rewrite (CAbs v (CApp x y)) (CApp (CApp S (CAbs v x)) (CAbs v y))) 86 | ; May be needed for multiple nested variables 87 | (rewrite (CAbs v (CApp K (CVar v))) K) 88 | 89 | ;;;; Primitive Evaluation rules (letd on "surface syntax") 90 | (rewrite (If T t f) t) 91 | (rewrite (If F t f) f) 92 | (rewrite (Add (N n) (N m)) (N (+ n m))) 93 | 94 | ;;;; Substitution Rules (letd on the combinator representation) 95 | (rewrite (CApp I cx) cx) 96 | (rewrite (CApp (CApp K cx) cy) cx) 97 | ; Without demand, this can cause an explosion in DB size. 98 | (rewrite (CApp (CApp (CApp S cx) cy) cz) (CApp (CApp cx cz) (CApp cy cz))) 99 | 100 | (run 11) 101 | (query-extract (Comb test)) 102 | (check (= test (N 3))) -------------------------------------------------------------------------------- /tests/combined-nested.egg: -------------------------------------------------------------------------------- 1 | (relation number (i64)) 2 | 3 | 4 | (ruleset myrules1) 5 | (rule () 6 | ((number 1)) 7 | :ruleset myrules1) 8 | (ruleset myrules2) 9 | (rule () 10 | ((number 2)) 11 | :ruleset myrules2) 12 | 13 | (unstable-combined-ruleset rules1and2 14 | myrules1 myrules2) 15 | 16 | ;; allowed to add to myrules2 and the change is reflected 17 | (rule () 18 | ((number 3)) 19 | :ruleset myrules2) 20 | 21 | ;; not allowed to add to combined ruleset 22 | (fail 23 | (rule () 24 | ((number 4)) 25 | :ruleset myrules1and2)) 26 | 27 | 28 | (fail 29 | (rule () 30 | ((number 4)) 31 | :ruleset unboundruleset)) 32 | 33 | (ruleset myrules5) 34 | (rule () 35 | ((number 5)) 36 | :ruleset myrules5) 37 | 38 | (unstable-combined-ruleset rules1and2and5 39 | rules1and2 myrules5) 40 | 41 | (run-schedule 42 | rules1and2and5) 43 | 44 | (check (number 1)) 45 | (check (number 2)) 46 | (check (number 3)) 47 | (check (number 5)) 48 | (fail (check (number 4))) 49 | -------------------------------------------------------------------------------- /tests/container-rebuild.egg: -------------------------------------------------------------------------------- 1 | (push) 2 | (datatype Math 3 | (Num i64)) 4 | 5 | (sort MathVec (Vec Math)) 6 | 7 | (let v1 (vec-of (Num 1) (Num 2))) 8 | (let v2 (vec-of (Num 2) (Num 2))) 9 | 10 | (union (Num 1) (Num 2)) 11 | 12 | (check (= v1 v2)) 13 | 14 | (constructor MyVec (MathVec) Math) 15 | 16 | (MyVec v1) 17 | 18 | (check (MyVec v2)) 19 | 20 | (check (= (MyVec v1) (MyVec v2))) 21 | 22 | (let v3 (vec-of (Num 4) (Num 5))) 23 | 24 | (union (Num 4) (Num 6)) 25 | (union (Num 5) (Num 7)) 26 | 27 | ;; We don't have any (MyVec v3) yet 28 | (fail (check (= (MyVec v3) (MyVec (vec-of (Num 6) (Num 7)))))) 29 | 30 | (MyVec v3) 31 | (check (= (MyVec v3) (MyVec (vec-of (Num 6) (Num 7))))) 32 | 33 | (pop) 34 | 35 | (push) 36 | 37 | (datatype Math 38 | (Num i64)) 39 | 40 | (sort MathVec (Vec Math)) 41 | 42 | 43 | (let v1 (vec-of (Num 1) (Num 2))) 44 | (let v2 (vec-of (Num 2) (Num 2))) 45 | 46 | (union (Num 1) (Num 2)) 47 | 48 | (constructor MyVec (MathVec) Math) 49 | 50 | ;; make a reference to v1 51 | (MyVec v1) 52 | 53 | (extract (MyVec v1)) 54 | 55 | ;; rebuilding creates (MyVec v2) 56 | (check (= (MyVec v1) (MyVec v2))) 57 | (pop) 58 | 59 | (push) 60 | (datatype Math 61 | (Add i64 i64) 62 | (Expensive :cost 100)) 63 | 64 | (sort MathVec (Vec Math)) 65 | 66 | (let myvec (vec-of (Expensive))) 67 | (let cheapvec (vec-of (Add 1 2))) 68 | 69 | (constructor VecContainer (MathVec) Math) 70 | 71 | (let myvecontainer (VecContainer cheapvec)) 72 | 73 | 74 | (union myvecontainer (Expensive)) 75 | 76 | ;; (vec-push (vec-empty) (VecContainer (vec-push (vec-empty) (Add 1 2)))) 77 | ;; should have cost 4 78 | (extract myvec 0) 79 | 80 | (pop) -------------------------------------------------------------------------------- /tests/cyk.egg: -------------------------------------------------------------------------------- 1 | (datatype term (Term String)) 2 | (datatype nonterm (NonTerm String)) 3 | (datatype tree (NT String tree tree) 4 | (T String String)) 5 | 6 | (function getString (i64) String :no-merge) 7 | 8 | (relation Prod (nonterm nonterm nonterm)) 9 | (relation End (nonterm String)) 10 | 11 | 12 | 13 | (relation P (i64 i64 nonterm)) 14 | (constructor B (i64 i64 nonterm) tree :cost 1000) 15 | 16 | (rule ((End (NonTerm a) s) 17 | (= s (getString pos))) 18 | ((P 1 pos (NonTerm a)) 19 | (union (B 1 pos (NonTerm a)) (T a s)))) 20 | 21 | (rule ((Prod (NonTerm a) (NonTerm b) (NonTerm c)) ;; a -> bc 22 | (P p1 s (NonTerm b)) 23 | (P p2 (+ s p1) (NonTerm c))) 24 | ((P (+ p1 p2) s (NonTerm a)))) 25 | 26 | 27 | (rule ((Prod (NonTerm a) (NonTerm b) (NonTerm c)) 28 | (= f1 (B p1 s (NonTerm b))) 29 | (= f2 (B p2 (+ s p1) (NonTerm c)))) 30 | ((union (B (+ p1 p2) s (NonTerm a)) 31 | (NT a f1 f2)))) 32 | 33 | (push) 34 | 35 | 36 | (set (getString 1) "she") 37 | (set (getString 2) "eats") 38 | (set (getString 3) "a") 39 | (set (getString 4) "fish") 40 | (set (getString 5) "with") 41 | (set (getString 6) "a") 42 | (set (getString 7) "fork") 43 | 44 | 45 | (Prod (NonTerm "S") (NonTerm "NP") (NonTerm "VP")) 46 | (Prod (NonTerm "VP") (NonTerm "VP") (NonTerm "PP")) 47 | (Prod (NonTerm "VP") (NonTerm "V") (NonTerm "NP")) 48 | (End (NonTerm "VP") "eats") 49 | (Prod (NonTerm "PP") (NonTerm "P") (NonTerm "NP")) 50 | (Prod (NonTerm "NP") (NonTerm "DET") (NonTerm "N")) 51 | (End (NonTerm "NP") "she") 52 | (End (NonTerm "V") "eats") 53 | (End (NonTerm "P") "with") 54 | (End (NonTerm "N") "fish") 55 | (End (NonTerm "N") "fork") 56 | (End (NonTerm "DET") "a") 57 | 58 | 59 | (run 100) 60 | 61 | (let test1 (B 7 1 (NonTerm "S"))) 62 | 63 | (check (P 7 1 (NonTerm "S"))) 64 | (fail (check (P 7 1 (NonTerm "VP")))) 65 | (fail (check (P 7 1 (NonTerm "")))) 66 | 67 | (query-extract test1) 68 | 69 | (pop) 70 | 71 | (push) 72 | 73 | (Prod (NonTerm "S") (NonTerm "A") (NonTerm "B")) 74 | (Prod (NonTerm "S") (NonTerm "B") (NonTerm "C")) 75 | (Prod (NonTerm "A") (NonTerm "B") (NonTerm "A")) 76 | (End (NonTerm "A") "a") 77 | (Prod (NonTerm "B") (NonTerm "C") (NonTerm "C")) 78 | (End (NonTerm "B") "b") 79 | (Prod (NonTerm "C") (NonTerm "A") (NonTerm "B")) 80 | (End (NonTerm "C") "a") 81 | 82 | (push) 83 | 84 | (set (getString 1) "a") 85 | (set (getString 2) "b") 86 | (set (getString 3) "a") 87 | (set (getString 4) "a") 88 | (set (getString 5) "b") 89 | 90 | (run 100) 91 | (check (P 5 1 (NonTerm "S"))) 92 | (fail (check (P 5 1 (NonTerm "B")))) 93 | (let test2 (B 5 1 (NonTerm "S"))) 94 | (query-extract :variants 10 test2) 95 | 96 | (pop) 97 | 98 | (push) 99 | 100 | (set (getString 1) "a") 101 | (set (getString 2) "a") 102 | (set (getString 3) "a") 103 | (set (getString 4) "a") 104 | (set (getString 5) "a") 105 | 106 | (run 100) 107 | (check (P 5 1 (NonTerm "S"))) 108 | (check (P 5 1 (NonTerm "A"))) 109 | (fail (check (P 5 1 (NonTerm "B")))) 110 | (fail (check (P 5 1 (NonTerm "")))) 111 | (fail (check (P 5 1 (NonTerm "unrelated")))) 112 | (let test3 (B 5 1 (NonTerm "S"))) 113 | (query-extract :variants 10 test3) 114 | 115 | (pop) -------------------------------------------------------------------------------- /tests/cykjson.egg: -------------------------------------------------------------------------------- 1 | (datatype tree (NT String tree tree) 2 | (T String String)) 3 | 4 | (function getString (i64) String :no-merge) 5 | 6 | (relation Prod (String String String)) 7 | (relation End (String String)) 8 | 9 | 10 | (relation P (i64 i64 String)) 11 | (function B (i64 i64 String) tree :no-merge) 12 | 13 | (rule ((End a s) 14 | (= s (getString pos))) 15 | ((P 1 pos a) 16 | (set (B 1 pos a) (T a s)))) 17 | 18 | (rule ((Prod a b c) ;; a -> bc 19 | (P p1 s b) 20 | (P p2 (+ s p1) c)) 21 | ((P (+ p1 p2) s a))) 22 | 23 | 24 | (rule ((Prod a b c) 25 | (= f1 (B p1 s b)) 26 | (= f2 (B p2 (+ s p1) c))) 27 | ((set (B (+ p1 p2) s a) 28 | (NT a f1 f2)))) 29 | 30 | 31 | (input Prod "./tests/cykjson_Prod.csv") 32 | (input End "./tests/cykjson_End.csv") 33 | 34 | ; small size 801 35 | (input getString "./tests/cykjson_small_token.csv") 36 | 37 | ; medium size 7821 but runs for 2 min. 38 | ;(input getString "./tests/cykjson_medium_token.csv") 39 | 40 | (run 10000) 41 | 42 | (let test1 (B 801 1 "VAL")) 43 | 44 | (check (P 801 1 "VAL")) -------------------------------------------------------------------------------- /tests/cykjson_End.csv: -------------------------------------------------------------------------------- 1 | VAL str 2 | VAL num 3 | VAL true 4 | VAL false 5 | VAL null 6 | ELM str 7 | ELM num 8 | ELM true 9 | ELM false 10 | ELM null 11 | H0 { 12 | H1 } 13 | H3 [ 14 | H4 ] 15 | H9 , 16 | H10 str 17 | H11 : -------------------------------------------------------------------------------- /tests/cykjson_Prod.csv: -------------------------------------------------------------------------------- 1 | VAL H0 H1 2 | VAL H2 H1 3 | VAL H3 H4 4 | VAL H5 H4 5 | MEM H6 MEM 6 | MEM H7 VAL 7 | PR H7 VAL 8 | ELM H8 ELM 9 | ELM H0 H1 10 | ELM H2 H1 11 | ELM H3 H4 12 | ELM H5 H4 13 | H2 H0 MEM 14 | H5 H3 ELM 15 | H6 PR H9 16 | H7 H10 H11 17 | H8 VAL H9 -------------------------------------------------------------------------------- /tests/datatypes.egg: -------------------------------------------------------------------------------- 1 | (datatype* 2 | (Math 3 | (Add Math Math) 4 | (Sum MathVec) 5 | (B Bool)) 6 | (sort MathVec (Vec Math)) 7 | (Bool 8 | (True) 9 | (False))) 10 | 11 | (let expr (Add (Sum (vec-of (B (True)) (B (False)))) (B (True)))) 12 | -------------------------------------------------------------------------------- /tests/delete.egg: -------------------------------------------------------------------------------- 1 | (function foo (i64) i64 :no-merge) 2 | (set (foo 1) 7) 3 | (check (= (foo 1) 7)) 4 | (delete (foo 1)) 5 | (rule ((= x (foo 1))) ((panic "foo 1 was there!"))) 6 | (run 1) -------------------------------------------------------------------------------- /tests/eqsat-basic-multiset.egg: -------------------------------------------------------------------------------- 1 | ;; Example showing how to use multisets to hold associative & commutative operations 2 | 3 | (datatype* 4 | (Math 5 | (Num i64) 6 | (Var String) 7 | (Add Math Math) 8 | (Mul Math Math) 9 | (Product MathMultiSet) 10 | (Sum MathMultiSet)) 11 | (sort MathToMath (UnstableFn (Math) Math)) 12 | (sort MathMultiSet (MultiSet Math))) 13 | 14 | ;; expr1 = 2 * (x + 3) 15 | (let expr1 (Mul (Num 2) (Add (Var "x") (Num 3)))) 16 | ;; expr2 = 6 + 2 * x 17 | (let expr2 (Add (Num 6) (Mul (Num 2) (Var "x")))) 18 | 19 | (rewrite (Add a b) (Sum (multiset-of a b))) 20 | (rewrite (Mul a b) (Product (multiset-of a b))) 21 | 22 | ;; 0 or 1 elements sums/products also can be extracted back to numbers 23 | (rule 24 | ( 25 | (= sum (Sum sum-inner)) 26 | (= 0 (multiset-length sum-inner)) 27 | ) 28 | ((union sum (Num 0))) 29 | ) 30 | (rule 31 | ( 32 | (= sum (Sum sum-inner)) 33 | (= 1 (multiset-length sum-inner)) 34 | ) 35 | ((union sum (multiset-pick sum-inner))) 36 | ) 37 | 38 | (rule 39 | ( 40 | (= product (Product product-inner)) 41 | (= 0 (multiset-length product-inner)) 42 | ) 43 | ((union product (Num 1))) 44 | ) 45 | (rule 46 | ( 47 | (= product (Product product-inner)) 48 | (= 1 (multiset-length product-inner)) 49 | ) 50 | ((union product (multiset-pick product-inner))) 51 | ) 52 | 53 | ; (rewrite (Mul a (Add b c)) 54 | ; (Add (Mul a b) (Mul a c))) 55 | 56 | ; -> we would like to write it like this, but cannot (yet) bc we can't match on the inner structure of the multisets 57 | ; and we don't support anonymous functions 58 | 59 | ; (rewrite (Product (multiset-insert a (Sum bc))) 60 | ; (Sum (multiset-map (lambda (x) (Product (multiset-insert a x))) bc))) 61 | 62 | 63 | ;; so instead we can define a function and partially apply it to get the same function as the lambda 64 | (constructor tmp-fn (MathMultiSet Math) Math) 65 | (rewrite (tmp-fn xs x) (Product (multiset-insert xs x))) 66 | 67 | (rule 68 | ( 69 | ;; and we can do a cross product search of all possible pairs of products/sums to find one we want 70 | (= sum (Sum bc)) 71 | (= product (Product product-inner)) 72 | (multiset-contains product-inner sum) 73 | (> (multiset-length product-inner) 1) 74 | (= a (multiset-remove product-inner sum)) 75 | ) 76 | ( 77 | (union product (Sum 78 | (unstable-multiset-map 79 | (unstable-fn "tmp-fn" a) 80 | bc) 81 | )) 82 | ) 83 | ) 84 | 85 | ; (rewrite (Add (Num a) (Num b)) 86 | ; (Num (+ a b))) 87 | 88 | (rule 89 | ( 90 | (= sum (Sum sum-inner)) 91 | (= num-a (Num a)) 92 | (multiset-contains sum-inner num-a) 93 | (= without-a (multiset-remove sum-inner num-a)) 94 | (= num-b (Num b)) 95 | (multiset-contains without-a num-b) 96 | ) 97 | ( 98 | (union sum 99 | (Sum (multiset-insert (multiset-remove without-a num-b) (Num (+ a b)))) 100 | ) 101 | ) 102 | ) 103 | 104 | ; (rewrite (Mul (Num a) (Num b)) 105 | ; (Num (* a b))) 106 | 107 | (rule 108 | ( 109 | (= product (Product product-inner)) 110 | (= num-a (Num a)) 111 | (multiset-contains product-inner num-a) 112 | (= without-a (multiset-remove product-inner num-a)) 113 | (= num-b (Num b)) 114 | (multiset-contains without-a num-b) 115 | ) 116 | ( 117 | (union product 118 | (Product (multiset-insert (multiset-remove without-a num-b) (Num (* a b)))) 119 | ) 120 | ) 121 | ) 122 | 123 | (run 100) 124 | (check (= expr1 expr2)) 125 | -------------------------------------------------------------------------------- /tests/eqsat-basic.egg: -------------------------------------------------------------------------------- 1 | (datatype Math 2 | (Num i64) 3 | (Var String) 4 | (Add Math Math) 5 | (Mul Math Math)) 6 | 7 | ;; expr1 = 2 * (x + 3) 8 | (let expr1 (Mul (Num 2) (Add (Var "x") (Num 3)))) 9 | ;; expr2 = 6 + 2 * x 10 | (let expr2 (Add (Num 6) (Mul (Num 2) (Var "x")))) 11 | 12 | 13 | ;; (rule ((= __root (Add a b))) 14 | ;; ((union __root (Add b a))) 15 | (rewrite (Add a b) 16 | (Add b a)) 17 | (rewrite (Mul a (Add b c)) 18 | (Add (Mul a b) (Mul a c))) 19 | (rewrite (Add (Num a) (Num b)) 20 | (Num (+ a b))) 21 | (rewrite (Mul (Num a) (Num b)) 22 | (Num (* a b))) 23 | 24 | (run 10) 25 | (check (= expr1 expr2)) 26 | -------------------------------------------------------------------------------- /tests/eqsolve.egg: -------------------------------------------------------------------------------- 1 | (datatype Expr 2 | (Add Expr Expr) 3 | (Neg Expr) 4 | (Num i64) 5 | (Mul Expr Expr) 6 | (Var String) 7 | ) 8 | 9 | (rewrite (Add x y) (Add y x)) 10 | (rewrite (Add (Add x y) z) (Add x (Add y z))) 11 | (rewrite (Add (Num x) (Num y)) (Num (+ x y))) 12 | (rule ((= (Add x y) z)) 13 | ((union (Add z (Neg y)) x))) 14 | (rewrite (Neg (Neg x)) x) 15 | (rewrite (Neg (Num n)) (Num (- 0 n))) 16 | 17 | (rule ((= x (Var v))) ((union (Mul (Num 1) x) x))) 18 | (rule ((= x (Add x1 x2))) ((union (Mul (Num 1) x) x))) 19 | (rewrite (Add (Mul y x) (Mul z x)) (Mul (Add y z) x)) 20 | (rewrite (Mul x y) (Mul y x)) 21 | (rule ((= (Mul (Num x) y) (Num z)) 22 | (= (% z x) 0)) 23 | ((union y (Num (/ z x))))) 24 | 25 | ; system 1: x + 2 = 7 26 | (union (Add (Var "x") (Num 2)) (Num 7)) 27 | ; system 2: z + y = 6, 2z = y 28 | (union (Add (Var "z") (Var "y")) (Num 6)) 29 | (union (Add (Var "z") (Var "z")) (Var "y")) 30 | 31 | (run 5) 32 | (query-extract (Var "x")) 33 | (query-extract (Var "y")) 34 | (query-extract (Var "z")) 35 | (check (= (Var "z") (Add (Num 6) (Neg (Var "y"))))) 36 | (check (= (Var "y") (Add (Add (Num 6) (Neg (Var "y"))) (Add (Num 6) (Neg (Var "y")))))) 37 | (check (= (Var "y") (Add (Add (Num 12) (Neg (Var "y"))) (Neg (Var "y"))))) 38 | (check (= (Add (Var "y") (Var "y")) 39 | (Add (Num 12) (Neg (Var "y"))))) 40 | (check (= (Add (Add (Var "y") (Var "y")) (Var "y")) 41 | (Num 12))) 42 | (check (= (Add (Mul (Num 2) (Var "y")) (Var "y")) 43 | (Num 12))) 44 | (check (= (Mul (Num 3) (Var "y")) 45 | (Num 12))) 46 | -------------------------------------------------------------------------------- /tests/f64.egg: -------------------------------------------------------------------------------- 1 | (check (= (neg 1.5) -1.5)) 2 | (check (= (+ 1.5 9.2) 10.7)) 3 | (check (= (/ 12.5 2.0) 6.25)) 4 | (check (< 1.5 9.2)) 5 | (check (>= 9.2 1.5)) 6 | (check (= (^ 9.0 2.5) 243.0)) 7 | (fail (check (= (^ 4.0 2.5) 31.99))) 8 | (fail (check (< 9.2 1.5))) 9 | (fail (check (= (+ 1.5 9.2) 10.6))) 10 | (check (= (to-f64 1) 1.0)) 11 | (check (= (to-i64 1.0) 1)) 12 | (check (= (to-string 1.2) "1.2")) 13 | (check (= (to-string 1.0) "1.0")) 14 | -------------------------------------------------------------------------------- /tests/fail-typecheck/arity-mismatch.egg: -------------------------------------------------------------------------------- 1 | (+ 1 2 3) 2 | -------------------------------------------------------------------------------- /tests/fail-typecheck/constructor_non_sort.egg: -------------------------------------------------------------------------------- 1 | (constructor f () i64) -------------------------------------------------------------------------------- /tests/fail-typecheck/looking_up_nonconstructor_in_action_case_let.egg: -------------------------------------------------------------------------------- 1 | (function f () i64) 2 | (function g () i64 :merge (min old new)) 3 | (datatype E) 4 | (function h () E :merge old) 5 | (rule ( 6 | ) ( 7 | (let x (f)) 8 | )) 9 | (rule ( 10 | ) ( 11 | (let x (g)) 12 | )) 13 | (rule ( 14 | ) ( 15 | (let x (h)) 16 | )) 17 | (run 1) -------------------------------------------------------------------------------- /tests/fail-typecheck/looking_up_nonconstructor_in_action_case_set.egg: -------------------------------------------------------------------------------- 1 | (datatype E) 2 | (function f () E :merge old) 3 | (rule ( 4 | ) ( 5 | (set (f) (f)) 6 | )) 7 | 8 | (run 1) -------------------------------------------------------------------------------- /tests/fail-typecheck/looking_up_nonconstructor_in_action_case_union.egg: -------------------------------------------------------------------------------- 1 | (function f (i64) i64) 2 | (datatype E 3 | (Sum i64 i64)) 4 | (rule ( 5 | (= 1 (f 2)) 6 | ) ( 7 | (union 8 | (Sum 3 4) 9 | (Sum 5 (+ 6 (f 7))) 10 | ) 11 | )) 12 | (run 1) -------------------------------------------------------------------------------- /tests/fail-typecheck/looking_up_nonconstructor_in_birewrite.egg: -------------------------------------------------------------------------------- 1 | (function f (i64) i64) 2 | (datatype E 3 | (Sum i64 i64)) 4 | (birewrite 5 | (Sum 5 (+ 6 (f 7))) 6 | (Sum 3 4) 7 | ) 8 | (run 1) -------------------------------------------------------------------------------- /tests/fail-typecheck/looking_up_nonconstructor_in_rewrite.egg: -------------------------------------------------------------------------------- 1 | (function f (i64) i64) 2 | (datatype E 3 | (Sum i64 i64)) 4 | (rewrite 5 | (Sum 3 4) 6 | (Sum 5 (+ 6 (f 7))) 7 | ) 8 | (run 1) -------------------------------------------------------------------------------- /tests/fail-typecheck/repro-containers-disallowed.egg: -------------------------------------------------------------------------------- 1 | (sort IVec (Vec i64)) 2 | 3 | ; Test vec-of 4 | (fail (check (= (vec-of 1 2) (vec-push (vec-push (vec-empty) 1) 2)))) 5 | -------------------------------------------------------------------------------- /tests/fail-typecheck/repro-duplicated-var.egg: -------------------------------------------------------------------------------- 1 | (function f (i64) i64) 2 | ;; the let's y should fail checking 3 | (rule ((= x 1) (= y x) (= f1 (f 1))) ((let y f1) (set (f 0) 0))) -------------------------------------------------------------------------------- /tests/fail-typecheck/semi_naive_set_function.egg: -------------------------------------------------------------------------------- 1 | ;; From issue#93. The change happened in right-hand-side of a rule may also impact output in semi-naive cases 2 | (push) 3 | (function f (i64) i64 :merge (max old new)) 4 | 5 | (set (f 0) 0) 6 | (set (f 3) 0) 7 | 8 | (rule ((= f0 (f 0))) ((set (f 1) f0))) 9 | (rule ((= f1 (f 1))) ((set (f 2) f1))) 10 | 11 | ;; update f3 some iters later to make sure f(0) is inactive 12 | (rule ((= f2 (f 2))) ((set (f 3) 3))) 13 | 14 | (push) 15 | 16 | ;; This rule should fire and set f(0) to be 3, but because f0 is inactive, 17 | ;; it does not fire (despite that f3 is active now) 18 | (rule ((= f0 (f 0))) ((set (f 0) (f 3)))) 19 | 20 | (run 100) 21 | (print-function f 100) ;; f0 is expected to have value 3, but has 0 in reality. 22 | 23 | (check (= (f 0) 3)) 24 | (check (= (f 1) 3)) 25 | (check (= (f 2) 3)) 26 | (check (= (f 3) 3)) 27 | 28 | (pop) 29 | (push) 30 | 31 | ;; variants of the last rule. 32 | (rule ((= f0 (f 0)) (= x 3) (= y x)) ((set (f 0) (f y)))) 33 | 34 | (run 100) 35 | (check (= (f 0) 3)) 36 | (check (= (f 1) 3)) 37 | (check (= (f 2) 3)) 38 | (check (= (f 3) 3)) 39 | 40 | (pop) 41 | (push) 42 | 43 | ;; adding let binding 44 | (rule ((= f0 (f 0))) ((let x 3) (let y x) (set (f 0) (f y)))) 45 | 46 | (run 100) 47 | (check (= (f 0) 3)) 48 | (check (= (f 1) 3)) 49 | (check (= (f 2) 3)) 50 | (check (= (f 3) 3)) 51 | 52 | (pop) 53 | (push) 54 | 55 | (function g (i64) i64 :merge (max old new)) 56 | (set (g 0) 3) 57 | 58 | ;; bind to another function 59 | (rule ((= f0 (f 0))) ((let x (g 0)) (let y x) (set (f 0) (f y)))) 60 | 61 | (run 100) 62 | (check (= (f 0) 3)) 63 | (check (= (f 1) 3)) 64 | (check (= (f 2) 3)) 65 | (check (= (f 3) 3)) 66 | 67 | (pop) 68 | (pop) 69 | 70 | ;; more complicated case, when the evaluation never finish 71 | ;; the semi_naive and naive behavior diverage a bit 72 | (function f (i64) i64 :merge (max old new)) 73 | 74 | (set (f 0) 0) 75 | (set (f 3) 0) 76 | 77 | (rule ((= f0 (f 0))) ((set (f 1) (+ 1 f0)))) 78 | (rule ((= f1 (f 1))) ((set (f 2) (+ 1 f1)))) 79 | 80 | (push) 81 | 82 | (rule ((= f2 (f 2))) ((set (f 3) 1))) 83 | (rule ((= f0 (f 0))) ((set (f 0) (f (f 3))))) 84 | 85 | 86 | (run 100) 87 | (print-function f 100) 88 | (check (!= 0 (f 0))) 89 | (check (!= 0 (f 1))) 90 | (check (!= 0 (f 2))) 91 | 92 | (pop) 93 | 94 | 95 | ;; id function that will set all int values, but need strong induction. 96 | (function g (i64) i64 :merge (max old new)) 97 | (set (g 0) 0) 98 | (set (g 1) 1) 99 | (rule ((= x (g x)) (= y (g (- x 1)))) ((set (g (+ x 1)) (+ y 2)))) 100 | 101 | (run 100) 102 | (print-function g 100) 103 | 104 | (check (= 20 (g 20))) -------------------------------------------------------------------------------- /tests/fail-typecheck/set-a-primitive.egg: -------------------------------------------------------------------------------- 1 | (set (+ 1 2) 3) 2 | -------------------------------------------------------------------------------- /tests/fail-typecheck/unbound.egg: -------------------------------------------------------------------------------- 1 | (datatype Math 2 | (Add Math Math) 3 | (Sub Math Math) 4 | ) 5 | 6 | (rule ((= e (Add x y))) ((Add x i))) 7 | -------------------------------------------------------------------------------- /tests/fail-typecheck/union_non_sort.egg: -------------------------------------------------------------------------------- 1 | (function f () i64 :no-merge) 2 | (set (f) 1) 3 | (function g () i64 :no-merge) 4 | (set (g) 2) 5 | (union (f) (g)) -------------------------------------------------------------------------------- /tests/fail-typecheck/unstable-fn-wrong-args-type.egg: -------------------------------------------------------------------------------- 1 | ;; test that you can't resolve a function with the wrong type of args 2 | 3 | (datatype Math 4 | (Zero) 5 | (Inc Math)) 6 | 7 | (sort Fn (UnstableFn (i64) Math)) 8 | (unstable-fn "Inc") 9 | -------------------------------------------------------------------------------- /tests/fail-typecheck/unstable-fn-wrong-args.egg: -------------------------------------------------------------------------------- 1 | ;; test that applying a function with the wrong number of args will violate the type checker 2 | 3 | 4 | (datatype Math 5 | (Inc Math)) 6 | 7 | (sort Fn (UnstableFn (Math) Math)) 8 | (unstable-app (unstable-fn "Inc") 10) 9 | -------------------------------------------------------------------------------- /tests/fail-typecheck/unstable-fn-wrong-return-type.egg: -------------------------------------------------------------------------------- 1 | ;; test that you can't resolve a function with the wrong return type 2 | 3 | (datatype Math 4 | (Zero) 5 | (Inc Math)) 6 | 7 | (sort Fn (UnstableFn (Math) i64)) 8 | (unstable-fn "Inc") 9 | -------------------------------------------------------------------------------- /tests/fail-typecheck/unstable-fn-wrong-return.egg: -------------------------------------------------------------------------------- 1 | ;; test that the value of a applied function is well typed 2 | 3 | 4 | (datatype Math 5 | (Zero) 6 | (Inc Math)) 7 | 8 | (sort Fn (UnstableFn (Math) Math)) 9 | (let x (unstable-app (unstable-fn "Inc") (Zero))) 10 | 11 | (+ x 10) 12 | -------------------------------------------------------------------------------- /tests/fail_wrong_assertion.egg: -------------------------------------------------------------------------------- 1 | ;; This test ensure check test fails for wrong assertion 2 | (function f (i64) i64 :merge (min old new)) 3 | 4 | (set (f 1) 4) 5 | (set (f 1) 5) 6 | 7 | (check (= (f 1) 4)) 8 | (fail (check (= (f 1) 2))) 9 | 10 | (delete (f 1)) 11 | (fail (check (= (f 1) 4))) 12 | 13 | (function g (i64 i64) i64 :merge (min old new)) 14 | 15 | (set (g 1 2) 3) 16 | (set (g 2 3) 3) 17 | 18 | (check (= (g 1 2) (g 2 3))) 19 | (fail (check (!= (g 1 2) (g 2 3)))) 20 | (fail (check (= (g 0 2) (g 2 3)))) 21 | (check (= x (g 1 2))) 22 | (fail (check (= x (g 1 3)))) 23 | (check (= x (g 1 2)) (= y (g 2 3)) (= x y)) 24 | (fail (check (= x (g 0 0)) (= y (g 1 1)) (= x y))) -------------------------------------------------------------------------------- /tests/fibonacci-demand.egg: -------------------------------------------------------------------------------- 1 | (datatype Expr 2 | (Num i64 :cost 1) 3 | (Add Expr Expr :cost 5)) 4 | 5 | (constructor Fib (i64) Expr :cost 10) 6 | 7 | (rewrite (Add (Num a) (Num b)) (Num (+ a b))) 8 | (rewrite (Fib x) (Add (Fib (- x 1)) (Fib (- x 2))) 9 | :when ((> x 1))) 10 | (rewrite (Fib x) (Num x) 11 | :when ((<= x 1))) 12 | 13 | (let f7 (Fib 7)) 14 | (run 1000) 15 | (print-function Fib 10) 16 | (extract f7) 17 | (check (= f7 (Num 13))) 18 | 19 | -------------------------------------------------------------------------------- /tests/fibonacci.egg: -------------------------------------------------------------------------------- 1 | (function fib (i64) i64 :no-merge) 2 | (set (fib 0) 0) 3 | (set (fib 1) 1) 4 | 5 | (rule ((= f0 (fib x)) 6 | (= f1 (fib (+ x 1)))) 7 | ((set (fib (+ x 2)) (+ f0 f1)))) 8 | 9 | (run 7) 10 | 11 | (check (= (fib 7) 13)) -------------------------------------------------------------------------------- /tests/files.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use egglog::*; 4 | use libtest_mimic::Trial; 5 | 6 | #[derive(Clone)] 7 | struct Run { 8 | path: PathBuf, 9 | resugar: bool, 10 | } 11 | 12 | impl Run { 13 | fn run(&self) { 14 | let _ = env_logger::builder().is_test(true).try_init(); 15 | let program = std::fs::read_to_string(&self.path) 16 | .unwrap_or_else(|err| panic!("Couldn't read {:?}: {:?}", self.path, err)); 17 | 18 | if !self.resugar { 19 | self.test_program( 20 | self.path.to_str().map(String::from), 21 | &program, 22 | "Top level error", 23 | ); 24 | } else { 25 | let mut egraph = EGraph::default(); 26 | egraph.run_mode = RunMode::ShowDesugaredEgglog; 27 | let desugared_str = egraph 28 | .parse_and_run_program(self.path.to_str().map(String::from), &program) 29 | .unwrap() 30 | .join("\n"); 31 | 32 | self.test_program( 33 | None, 34 | &desugared_str, 35 | "ERROR after parse, to_string, and parse again.", 36 | ); 37 | } 38 | } 39 | 40 | fn test_program(&self, filename: Option, program: &str, message: &str) { 41 | let mut egraph = EGraph::default(); 42 | match egraph.parse_and_run_program(filename, program) { 43 | Ok(msgs) => { 44 | if self.should_fail() { 45 | panic!( 46 | "Program should have failed! Instead, logged:\n {}", 47 | msgs.join("\n") 48 | ); 49 | } else { 50 | for msg in msgs { 51 | log::info!(" {}", msg); 52 | } 53 | // Test graphviz dot generation 54 | let mut serialized = egraph.serialize(SerializeConfig { 55 | max_functions: Some(40), 56 | max_calls_per_function: Some(40), 57 | ..Default::default() 58 | }); 59 | serialized.to_dot(); 60 | // Also try splitting and inlining 61 | serialized.split_classes(|id, _| egraph.from_node_id(id).is_primitive()); 62 | serialized.inline_leaves(); 63 | serialized.to_dot(); 64 | } 65 | } 66 | Err(err) => { 67 | if !self.should_fail() { 68 | panic!("{}: {err}", message) 69 | } 70 | } 71 | }; 72 | } 73 | 74 | fn into_trial(self) -> Trial { 75 | let name = self.name().to_string(); 76 | Trial::test(name, move || { 77 | self.run(); 78 | Ok(()) 79 | }) 80 | } 81 | 82 | fn name(&self) -> impl std::fmt::Display + '_ { 83 | struct Wrapper<'a>(&'a Run); 84 | impl std::fmt::Display for Wrapper<'_> { 85 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 86 | let stem = self.0.path.file_stem().unwrap(); 87 | let stem_str = stem.to_string_lossy().replace(['.', '-', ' '], "_"); 88 | write!(f, "{stem_str}")?; 89 | if self.0.resugar { 90 | write!(f, "_resugar")?; 91 | } 92 | Ok(()) 93 | } 94 | } 95 | Wrapper(self) 96 | } 97 | 98 | fn should_fail(&self) -> bool { 99 | self.path.to_string_lossy().contains("fail-typecheck") 100 | } 101 | } 102 | 103 | fn generate_tests(glob: &str) -> Vec { 104 | let mut trials = vec![]; 105 | let mut push_trial = |run: Run| trials.push(run.into_trial()); 106 | 107 | for entry in glob::glob(glob).unwrap() { 108 | let run = Run { 109 | path: entry.unwrap().clone(), 110 | resugar: false, 111 | }; 112 | let should_fail = run.should_fail(); 113 | 114 | push_trial(run.clone()); 115 | if !should_fail { 116 | push_trial(Run { 117 | resugar: true, 118 | ..run.clone() 119 | }); 120 | } 121 | } 122 | 123 | trials 124 | } 125 | 126 | fn main() { 127 | let args = libtest_mimic::Arguments::from_args(); 128 | let tests = generate_tests("tests/**/*.egg"); 129 | libtest_mimic::run(&args, tests).exit(); 130 | } 131 | -------------------------------------------------------------------------------- /tests/fusion.egg: -------------------------------------------------------------------------------- 1 | (datatype Var) 2 | (datatype Term 3 | (App Term Term) 4 | (Lam Var Term) 5 | (TVar Var) 6 | (Let Var Term Term) 7 | (Add Term Term) 8 | (Num i64) 9 | (CaseSplit Term Term Term) 10 | (Cons Term Term)) 11 | (constructor NilConst () Term) 12 | (let Nil (NilConst)) 13 | 14 | (constructor V (String) Var) 15 | (constructor From (Term) Var) 16 | 17 | ;; ==== FV ==== 18 | (sort StringSet (Set Var)) 19 | (function freer (Term) StringSet :merge (set-intersect old new)) 20 | (rule ((= e (App e1 e2)) 21 | (= (freer e1) fv1) 22 | (= (freer e2) fv2)) 23 | ((set (freer e) (set-union fv1 fv2)))) 24 | (rule ((= e (Lam var body)) 25 | (= (freer body) fv)) 26 | ((set (freer e) (set-remove fv var)))) 27 | (rule ((= e (TVar v))) 28 | ((set (freer e) (set-insert (set-empty) v)))) 29 | (rule ((= e (Let var e1 e2)) 30 | (= (freer e1) fv1) 31 | (= (freer e2) fv2)) 32 | ((set (freer e) (set-union fv1 (set-remove fv2 var))))) 33 | (rule ((= e (Add e1 e2)) 34 | (= (freer e1) fv1) 35 | (= (freer e2) fv2)) 36 | ((set (freer e) (set-union fv1 fv2)))) 37 | (rule ((= e (Num v))) 38 | ((set (freer e) (set-empty)))) 39 | (rule ((= e (CaseSplit e1 e2 e3)) 40 | (= (freer e1) fv1) 41 | (= (freer e2) fv2) 42 | (= (freer e3) fv3)) 43 | ((set (freer e) (set-union (set-union fv1 fv2) fv3)))) 44 | (rule ((= e (Cons e1 e2)) 45 | (= (freer e1) fv1) 46 | (= (freer e2) fv2)) 47 | ((set (freer e) (set-union fv1 fv2)))) 48 | (rule ((= e Nil)) 49 | ((set (freer e) (set-empty)))) 50 | 51 | ;; ==== eval ==== 52 | ; beta 53 | (rewrite (App (Lam v body) e) (Let v e body)) 54 | ; case-split-nil 55 | (rewrite (CaseSplit Nil e1 e2) e1) 56 | ; case-split-cons 57 | (rewrite (CaseSplit (Cons x xs) e1 e2) (App (App e2 x) xs)) 58 | 59 | ; let-num 60 | (rewrite (Let v e (Num n)) (Num n)) 61 | ; let-nil 62 | (rewrite (Let v e Nil) Nil) 63 | ; let-var-same 64 | (rewrite (Let v1 e (TVar v1)) e) 65 | ; let-var-diff 66 | (rewrite (Let v1 e (TVar v2)) (TVar v2) :when ((!= v1 v2))) 67 | 68 | ; let-lam-close 69 | (rewrite (Let v1 e expr) expr :when ((set-not-contains (freer expr) v1))) 70 | ; let-app 71 | (rewrite (Let v e expr) (App (Let v e a) (Let v e b)) :when ((= expr (App a b)) (set-contains (freer expr) v))) 72 | ; let-add 73 | (rewrite (Let v e expr) (Add (Let v e a) (Let v e b)) :when ((= expr (Add a b)) (set-contains (freer expr) v))) 74 | ; let-cons 75 | (rewrite (Let v e expr) (Cons (Let v e x) (Let v e xs)) :when ((= expr (Cons x xs)) (set-contains (freer expr) v))) 76 | ; let-case-split 77 | (rewrite (Let v e expr) 78 | (CaseSplit (Let v e e1) (Let v e e2) (Let v e e3)) 79 | :when ((= expr (CaseSplit e1 e2 e3)) 80 | (set-contains (freer expr) v))) 81 | ; let-lam-same 82 | (rewrite (Let v1 e (Lam v1 body)) (Lam v1 body)) 83 | ; let-lam-diff 84 | (rewrite (Let v1 e (Lam v2 body)) (Lam v2 (Let v1 e body)) 85 | :when ((set-contains (freer body) v1) 86 | (!= v1 v2) 87 | (= fvs (freer e)) 88 | (set-not-contains fvs v2))) 89 | (rule ((= expr (Let v1 e (Lam v2 body))) 90 | (set-contains (freer body) v1) 91 | (!= v1 v2) 92 | (= fvs (freer e)) 93 | (set-contains fvs v2)) 94 | ((union expr (Lam (From expr) (Let v1 e (Let v2 (TVar (From expr)) body)))))) 95 | 96 | (constructor pushdown (Term Term) Term :cost 10000) 97 | (rewrite (App f (App (Lam x e) e2)) 98 | (App (Lam x (pushdown f e)) e2)) 99 | 100 | (rewrite (pushdown f (CaseSplit e e1 (Lam x (Lam xs e2)))) 101 | (CaseSplit e (App f e1) (Lam x (Lam xs (App f e2))))) 102 | 103 | (relation is-tail (Term)) 104 | (rule ((= demand (pushdown f e)) (= e (App e1 e2))) ((is-tail e))) 105 | (rule ((= demand (pushdown f e)) (= e (Lam x e))) ((is-tail e))) 106 | (rule ((= demand (pushdown f e)) (= e (TVar x))) ((is-tail e))) 107 | (rule ((= demand (pushdown f e)) (= e (Cons e1 e2))) ((is-tail e))) 108 | (rule ((= demand (pushdown f e)) (= e Nil)) ((is-tail e))) 109 | (rule ((= demand (pushdown f e)) (= e (Add e1 e2))) ((is-tail e))) 110 | (rule ((= demand (pushdown f e)) (= e (Num n1))) ((is-tail e))) 111 | (rewrite (pushdown f e) (App f e) :when ((is-tail e))) 112 | 113 | ;; ==== definition ==== 114 | 115 | (constructor sum () Term :cost 1000) 116 | (constructor mapf () Term :cost 1000) 117 | (constructor sum-o-mapf () Term) 118 | (rewrite (App (sum) (App (mapf) x)) (App (sum-o-mapf) x)) 119 | (union (sum) (Lam (V "xs") 120 | (CaseSplit (TVar (V "xs")) 121 | (Num 0) 122 | (Lam (V "x") (Lam (V "xs'") 123 | (Add (TVar (V "x")) (App (sum) (TVar (V "xs'"))))))))) 124 | 125 | (union (mapf) (Lam (V "xs") 126 | (CaseSplit (TVar (V "xs")) 127 | Nil 128 | (Lam (V "x") (Lam (V "xs'") 129 | (Cons (Add (TVar (V "x")) (Num 1)) 130 | (App (mapf) (TVar (V "xs'"))))))))) 131 | 132 | (set (freer (sum)) (set-empty)) 133 | (set (freer (mapf)) (set-empty)) 134 | 135 | (let expr (App (sum) (App (mapf) (TVar (V "expr"))))) 136 | 137 | (run 100) 138 | 139 | (query-extract (freer expr)) 140 | 141 | 142 | (let my-output 143 | (CaseSplit (TVar (V "expr")) (Num 0) 144 | (Lam (V "x") (Lam (V "xs'") 145 | (Add (Add (TVar (V "x")) (Num 1)) 146 | (App (sum-o-mapf) (TVar (V "xs'")))))))) 147 | 148 | (check (= (App (sum-o-mapf) (TVar (V "expr"))) 149 | (CaseSplit (TVar (V "expr")) (Num 0) 150 | (Lam (V "x") (Lam (V "xs'") 151 | (Add (Add (TVar (V "x")) (Num 1)) 152 | (App (sum-o-mapf) (TVar (V "xs'"))))))))) 153 | -------------------------------------------------------------------------------- /tests/herbie-tutorial.egg: -------------------------------------------------------------------------------- 1 | (datatype Math 2 | (Num BigRat) 3 | (Var String) 4 | (Add Math Math) 5 | (Div Math Math) 6 | (Mul Math Math)) 7 | 8 | (let zero (Num (bigrat (bigint 0) (bigint 1)))) 9 | (let one (Num (bigrat (bigint 1) (bigint 1)))) 10 | (let two (Num (bigrat (bigint 2) (bigint 1)))) 11 | 12 | (rewrite (Add a b) (Add b a)) 13 | (rewrite (Add a zero) a) 14 | (rewrite (Add (Num r1) (Num r2)) 15 | (Num (+ r1 r2))) 16 | 17 | (let one-two (Add one two)) 18 | 19 | (push) 20 | (run 1) 21 | ;; yay, constant folding works 22 | (check (= one-two (Num (bigrat (bigint 3) (bigint 1))))) 23 | ;; also, commutativity works 24 | (check (= (Add two one) one-two)) 25 | (pop) 26 | 27 | (push) 28 | ;; rule is like rewrite, but more general 29 | ;; the following rule doesn't union (Num r) with the result: 30 | (rule ((Num r)) 31 | ((union one (Div (Num r) (Num r))))) 32 | ;; uh oh, division by zero! 33 | (run 1) 34 | 35 | (pop) 36 | 37 | ;; we need to detect when things are non-zero 38 | (function lower-bound (Math) BigRat :merge (max old new)) 39 | (function upper-bound (Math) BigRat :merge (min old new)) 40 | 41 | (rule ((Num r)) 42 | ((set (lower-bound (Num r)) r) 43 | (set (upper-bound (Num r)) r))) 44 | (rule ((= e (Add a b)) (= x (lower-bound a)) (= y (lower-bound b))) 45 | ((set (lower-bound e) (+ x y)))) 46 | (rule ((= e (Add a b)) (= x (upper-bound a)) (= y (upper-bound b))) 47 | ((set (upper-bound e) (+ x y)))) 48 | (rule ((= e (Mul a b)) 49 | (= lba (lower-bound a)) 50 | (= lbb (lower-bound b)) 51 | (= uba (upper-bound a)) 52 | (= ubb (upper-bound b)) 53 | ) 54 | ((set (lower-bound e) 55 | (min (* lba lbb) 56 | (min (* lba ubb) 57 | (min (* uba lbb) 58 | (* uba ubb))))) 59 | (set (upper-bound e) 60 | (min (* lba lbb) 61 | (min (* lba ubb) 62 | (min (* uba lbb) 63 | (* uba ubb))))))) 64 | 65 | (rule ((= e (Add a b)) 66 | (> (lower-bound e) (bigrat (bigint 0) (bigint 1)))) 67 | ((union one (Div (Add a b) (Add a b))))) 68 | 69 | (let x (Var "x")) 70 | (let x1 (Add x one)) 71 | 72 | (push) 73 | (set (lower-bound x) (bigrat (bigint 0) (bigint 1))) 74 | (set (upper-bound x) (bigrat (bigint 1) (bigint 1))) 75 | 76 | (run 3) 77 | 78 | (query-extract (lower-bound x1)) 79 | (query-extract (upper-bound x1)) 80 | (check (= one (Div x1 x1))) 81 | 82 | (pop) 83 | 84 | 85 | ;; Set the variable x to a particular input value 200/201 86 | (set (lower-bound x) (bigrat (bigint 200) (bigint 201))) 87 | (set (upper-bound x) (bigrat (bigint 200) (bigint 201))) 88 | 89 | (run 3) 90 | 91 | (query-extract (lower-bound x1)) 92 | (query-extract (upper-bound x1)) 93 | 94 | (function true-value (Math) f64 :no-merge) 95 | 96 | (rule ((= (to-f64 (lower-bound e)) 97 | (to-f64 (upper-bound e))) 98 | (= lbe (lower-bound e)) 99 | ) 100 | ((set (true-value e) 101 | (to-f64 lbe)))) 102 | 103 | (run 1) 104 | (query-extract (true-value x1)) 105 | 106 | (function best-error (Math) f64 :merge new) 107 | 108 | (rule ((Num n)) 109 | ((set (best-error (Num n)) (to-f64 n)))) 110 | (rule ((Add a b)) ((set (best-error (Add a b)) (to-f64 (bigrat (bigint 10000) (bigint 1)))))) 111 | 112 | ;; finally, the mega rule for finding more accurate programs 113 | (rule ((= expr (Add a b)) 114 | (= (best-error a) va) 115 | (= (best-error b) vb) 116 | (= true-v (true-value (Add a b))) 117 | (= computed (+ va vb)) 118 | (< (abs (- computed true-v)) 119 | (best-error (Add a b)))) 120 | ((set (best-error (Add a b)) computed))) 121 | 122 | 123 | 124 | (push) 125 | 126 | (let target 127 | (Add 128 | (Add (Num (bigrat (bigint 1) (bigint 100))) (Num (bigrat (bigint 1) (bigint 100)))) 129 | (Num (bigrat (bigint -2) (bigint 100))))) 130 | 131 | (run 1) 132 | 133 | ;; set a default 134 | (set (best-error target) (to-f64 (bigrat (bigint 10000) (bigint 1)))) 135 | ;; error is bad, constant folding hasn't fired enough 136 | (query-extract (best-error target)) 137 | 138 | (run 1) 139 | 140 | ;; error is good, constant folding has fired enough 141 | (query-extract (best-error target)) 142 | 143 | 144 | (pop) -------------------------------------------------------------------------------- /tests/i64.egg: -------------------------------------------------------------------------------- 1 | (check (= (to-string 20) "20")) 2 | -------------------------------------------------------------------------------- /tests/include.egg: -------------------------------------------------------------------------------- 1 | (include "tests/path.egg") 2 | (check (path 1 3)) 3 | -------------------------------------------------------------------------------- /tests/integer_math.egg: -------------------------------------------------------------------------------- 1 | (datatype Math 2 | (Diff Math Math) 3 | (Integral Math Math) 4 | 5 | (Add Math Math) 6 | (Sub Math Math) 7 | (Mul Math Math) 8 | (Div Math Math) 9 | (Pow Math Math) 10 | (RShift Math Math) 11 | (LShift Math Math) 12 | (Mod Math Math) 13 | (Not Math) 14 | 15 | (Const i64) 16 | (Var String)) 17 | 18 | (relation MathU (Math)) 19 | (rule ((= e (Diff x y))) ((MathU e))) 20 | (rule ((= e (Integral x y))) ((MathU e))) 21 | (rule ((= e (Add x y))) ((MathU e))) 22 | (rule ((= e (Sub x y))) ((MathU e))) 23 | (rule ((= e (Mul x y))) ((MathU e))) 24 | (rule ((= e (Div x y))) ((MathU e))) 25 | (rule ((= e (Pow x y))) ((MathU e))) 26 | (rule ((= e (Const x))) ((MathU e))) 27 | (rule ((= e (Var x))) ((MathU e))) 28 | (rule ((= e (RShift x y))) ((MathU e))) 29 | (rule ((= e (LShift x y))) ((MathU e))) 30 | (rule ((= e (Mod x y))) ((MathU e))) 31 | (rule ((= e (Not x))) ((MathU e))) 32 | 33 | (relation evals-to (Math i64)) 34 | (rule ((evals-to x vx)) ((union x (Const vx)))) 35 | (rule ((= e (Const c))) ((evals-to e c))) 36 | 37 | (relation is-not-zero (Math)) 38 | (rule ((MathU a) (!= a (Const 0))) ((is-not-zero a))) 39 | 40 | ;; Evaluation 41 | (rewrite (Add (Const a) (Const b)) 42 | (Const (+ a b))) 43 | (rewrite (Sub (Const a) (Const b)) 44 | (Const (- a b))) 45 | (rewrite (Mul (Const a) (Const b)) (Const (* a b))) 46 | (rewrite (Div (Const a) (Const b)) (Const (/ a b)) :when ((!= 0 b))) 47 | (rewrite (RShift (Const a) (Const b)) (Const (>> a b))) 48 | (rewrite (LShift (Const a) (Const b)) (Const (<< a b))) 49 | (rewrite (Not (Const a)) (Const (not-i64 a))) 50 | 51 | ;; Properties 52 | (rewrite (Add a b) (Add b a)) 53 | (rewrite (Mul a b) (Mul b a)) 54 | (rewrite (Add a (Add b c)) (Add (Add a b) c)) 55 | (rewrite (Mul a (Mul b c)) (Mul (Mul a b) c)) 56 | 57 | (rewrite (Sub a b) (Add a (Mul (Const -1) b))) 58 | 59 | (rewrite (Add a (Const 0)) a) 60 | (rewrite (Mul a (Const 0)) (Const 0)) 61 | (rewrite (Mul a (Const 1)) a) 62 | 63 | (rule ((MathU a) (!= a (Const 0))) ((union a (Add a (Const 0))))) 64 | (rule ((MathU a) (!= a (Const 1))) ((union a (Mul a (Const 1))))) 65 | 66 | (rewrite (Sub a a) (Const 0)) 67 | (rewrite (Div a a) (Const 1) :when ((is-not-zero a))) 68 | 69 | (rewrite (Mul a (Add b c)) (Add (Mul a b) (Mul a c))) 70 | (rewrite (Add (Mul a b) (Mul a c)) (Mul a (Add b c))) 71 | 72 | ; This rule doesn't work when pow is negative - consider 2^-1 * 2^1, which is 0, but 2^0 = 1 73 | (rewrite (Mul (Pow a b) (Pow a c)) (Pow a (Add b c)) :when ((is-not-zero b) (is-not-zero c))) 74 | 75 | (rewrite (Pow x (Const 0)) (Const 1) :when ((is-not-zero x))) 76 | (rewrite (Pow x (Const 1 )) x) 77 | (rewrite (Pow x (Const 2)) (Mul x x)) 78 | 79 | (rewrite (Pow x (Const -1)) (Div (Const 1) x) :when ((is-not-zero x))) 80 | 81 | (rewrite (Mul x (Pow (Const 2) y)) (LShift x y)) 82 | (rewrite (Div x (Pow (Const 2) y)) (RShift x y)) 83 | 84 | (rewrite (Not (Not x)) x) 85 | 86 | 87 | ;; Tests 88 | (let start-expr (Div ( 89 | Mul (Var "a") (Pow (Const 2) (Const 3)) 90 | ) ( 91 | Add (Var "c") ( 92 | Sub (Mul (Var "b") (Const 2)) (Mul (Var "b") (Const 2)) 93 | ) 94 | ))) 95 | 96 | (let equiv-expr (Div ( 97 | LShift (Var "a") (Const 3) 98 | ) ( 99 | Mul (Var "c") (Not (Not (Const 1))) 100 | ) 101 | )) 102 | 103 | (run 4) 104 | 105 | (check (= start-expr equiv-expr)) 106 | 107 | -------------------------------------------------------------------------------- /tests/intersection.egg: -------------------------------------------------------------------------------- 1 | ;; computes "e-graph intersection" 2 | 3 | (datatype Expr 4 | (Var String) 5 | (f Expr)) 6 | 7 | (constructor intersect (Expr Expr) Expr) 8 | 9 | (rule ( 10 | (= x3 (intersect x1 x2)) 11 | (= f1 (f x1)) 12 | (= f2 (f x2)) 13 | )( 14 | (union (intersect f1 f2) (f x3)) 15 | )) 16 | 17 | (let a1 (Var "a1")) (let a2 (Var "a2")) (let a3 (Var "a3")) 18 | (let b1 (Var "b1")) (let b2 (Var "b2")) (let b3 (Var "b3")) 19 | 20 | ;; e-graph 1: f(a) = f(b), f(f(a)) 21 | (let t1 (f (f a1))) 22 | (let fb1 (f b1)) 23 | (union (f a1) fb1) 24 | 25 | ;; e-graph 2: f(f(a)) = f(f(b)) 26 | (let t2 (f (f a2))) 27 | (let t2p (f (f b2))) 28 | (union t2 t2p) 29 | 30 | (union (intersect a1 a2) a3) 31 | (union (intersect b1 b2) b3) 32 | 33 | (run 100) 34 | 35 | (let t3 (f (f a3))) 36 | (query-extract :variants 5 t3) 37 | 38 | ;; f(f(a)) = f(f(b)) is preserved 39 | (check (= (f (f a3)) (f (f b3)))) 40 | ;; but not f(a) = f(b), it was only in e-graph 1 41 | (check (!= (f a3) (f b3))) -------------------------------------------------------------------------------- /tests/interval.egg: -------------------------------------------------------------------------------- 1 | (datatype Math 2 | (Num i64) 3 | (Var String) 4 | (Mul Math Math)) 5 | 6 | (function hi (Math) i64 :merge (min old new)) 7 | (function lo (Math) i64 :merge (max old new)) 8 | 9 | (rule ((= mul (Mul a b)) 10 | (= loa (lo a)) 11 | (= lob (lo b)) 12 | (= hia (hi a)) 13 | (= hib (hi b)) 14 | ) 15 | ((set (lo mul) 16 | (min (min (* loa lob) (* loa hib)) 17 | (min (* hia lob) (* hia hib)))))) 18 | 19 | (let x (Var "x")) 20 | (let e (Mul x x)) 21 | 22 | (set (lo x) -10) 23 | (set (hi x) 10) 24 | 25 | (run 1) 26 | 27 | (check (= (lo e) -100)) 28 | 29 | (rule ((= mul (Mul a a)) 30 | (= loa (lo a)) 31 | ) 32 | ((set (lo mul) (* loa loa)))) 33 | 34 | (run 1) 35 | (check (= (lo e) 100)) 36 | 37 | ;; testing extraction of rationals 38 | (query-extract (lo e)) 39 | -------------------------------------------------------------------------------- /tests/knapsack.egg: -------------------------------------------------------------------------------- 1 | (datatype expr 2 | (Num i64) 3 | (Add expr expr) 4 | (Max expr expr)) 5 | (rewrite (Add (Num a) (Num b)) (Num (+ a b))) 6 | (rewrite (Max (Num a) (Num b)) (Num (max a b))) 7 | 8 | ; List of (weight, value) pairs 9 | (datatype objects 10 | (Cons i64 i64 objects)) 11 | (constructor NilConst () objects) 12 | (let Nil (NilConst)) 13 | 14 | ; Given a capacity and a list of objects, finds the maximum value of a 15 | ; collection of objects whose total weight does not exceed the capacity. 16 | (constructor Knap (i64 objects) expr) 17 | 18 | (rule ((= f (Knap capacity (Cons weight val rest))) (<= weight capacity)) 19 | ((union (Knap capacity (Cons weight val rest)) 20 | (Max 21 | (Add (Num val) (Knap (- capacity weight) rest)) 22 | (Knap capacity rest))))) 23 | 24 | (rule ((= f (Knap capacity (Cons weight val rest))) (> weight capacity)) 25 | ((union (Knap capacity (Cons weight val rest)) 26 | (Knap capacity rest)))) 27 | 28 | (rule ((= f (Knap capacity Nil))) 29 | ((union (Knap capacity Nil) (Num 0)))) 30 | 31 | (let test1 (Knap 13 (Cons 5 5 (Cons 3 3 (Cons 12 12 (Cons 5 5 Nil)))))) 32 | 33 | (let test2 (Knap 5 (Cons 6 6 Nil))) 34 | 35 | (let test3 (Knap 5 (Cons 1 1 (Cons 1 1 (Cons 1 1 Nil))))) 36 | 37 | (let test4 (Knap 15 (Cons 12 40 (Cons 2 20 (Cons 1 20 (Cons 1 10 (Cons 4 100 Nil))))))) 38 | 39 | ; turn a (Num n) into n 40 | (function Unwrap (expr) i64 :no-merge) 41 | (rule ((= x (Num n))) ((set (Unwrap (Num n)) n))) 42 | 43 | (run 100) 44 | 45 | (check (= test1 (Num 13))) 46 | 47 | -------------------------------------------------------------------------------- /tests/levenshtein-distance.egg: -------------------------------------------------------------------------------- 1 | ; Datatypes 2 | 3 | (datatype expr 4 | (Num i64) 5 | (Add expr expr) 6 | (Min expr expr expr)) 7 | (rewrite (Add (Num a) (Num b)) (Num (+ a b))) 8 | (rewrite (Min (Num a) (Num b) (Num c)) (Num (min (min a b) c))) 9 | 10 | ; `String` supports limited operations, let's just use it as a char type 11 | (datatype str 12 | (Cons String str)) 13 | (constructor EmptyConst () str) 14 | (let Empty (EmptyConst)) 15 | 16 | ; Length function 17 | 18 | (constructor Length (str) expr) 19 | 20 | (rule ((= f (Length Empty))) 21 | ((union (Length Empty) (Num 0)))) 22 | 23 | (rule ((= f (Length (Cons c cs)))) 24 | ((union (Length (Cons c cs)) (Add (Num 1) (Length cs))))) 25 | 26 | ; EditDist function 27 | 28 | (constructor EditDist (str str) expr) 29 | 30 | (rule ((= f (EditDist Empty s))) 31 | ((union (EditDist Empty s) (Length s)))) 32 | 33 | (rule ((= f (EditDist s Empty))) 34 | ((union (EditDist s Empty) (Length s)))) 35 | 36 | (rule ((= f (EditDist (Cons head rest1) (Cons head rest2)))) 37 | ((union (EditDist (Cons head rest1) (Cons head rest2)) 38 | (EditDist rest1 rest2)))) 39 | 40 | (rule ((= f (EditDist (Cons head1 rest1) (Cons head2 rest2))) (!= head1 head2)) 41 | ((union (EditDist (Cons head1 rest1) (Cons head2 rest2)) 42 | (Add (Num 1) 43 | (Min (EditDist rest1 rest2) 44 | (EditDist (Cons head1 rest1) rest2) 45 | (EditDist rest1 (Cons head2 rest2))))))) 46 | 47 | ; Unwrap function - turn a (Num n) into n 48 | 49 | (function Unwrap (expr) i64 :no-merge) 50 | (rule ((= x (Num n))) ((set (Unwrap (Num n)) n))) 51 | 52 | ; Tests 53 | (let HorseStr (Cons "h" (Cons "o" (Cons "r" (Cons "s" (Cons "e" Empty)))))) 54 | (let RosStr (Cons "r" (Cons "o" (Cons "s" Empty)))) 55 | (let IntentionStr (Cons "i" (Cons "n" (Cons "t" (Cons "e" (Cons "n" (Cons "t" (Cons "i" (Cons "o" (Cons "n" Empty)))))))))) 56 | (let ExecutionStr (Cons "e" (Cons "x" (Cons "e" (Cons "c" (Cons "u" (Cons "t" (Cons "i" (Cons "o" (Cons "n" Empty)))))))))) 57 | 58 | (let Test1 (EditDist HorseStr RosStr)) 59 | (let Test2 (EditDist IntentionStr ExecutionStr)) 60 | (let Test3 (EditDist HorseStr Empty)) 61 | 62 | (run 100) 63 | 64 | (extract (Unwrap Test1)) 65 | (check (= Test1 (Num 3))) 66 | 67 | (extract (Unwrap Test2)) 68 | (check (= Test2 (Num 5))) 69 | 70 | (extract (Unwrap Test3)) 71 | (check (= Test3 (Num 5))) -------------------------------------------------------------------------------- /tests/list.egg: -------------------------------------------------------------------------------- 1 | (datatype List 2 | (Nil) 3 | (Cons i64 List)) 4 | 5 | (ruleset list-ruleset) 6 | 7 | (function list-length (List) i64 :no-merge) 8 | (relation list-length-demand (List)) 9 | (rule 10 | ((list-length-demand (Nil))) 11 | ((set (list-length (Nil)) 0)) 12 | :ruleset list-ruleset) 13 | (rule 14 | ((list-length-demand (Cons head tail))) 15 | ((list-length-demand tail)) 16 | :ruleset list-ruleset) 17 | (rule 18 | ( (list-length-demand (Cons head tail)) 19 | (= (list-length tail) tail-length)) 20 | ((set (list-length (Cons head tail)) (+ tail-length 1))) 21 | :ruleset list-ruleset) 22 | 23 | (function list-get (List i64) i64 :no-merge) 24 | (relation list-get-demand (List i64)) 25 | (rule 26 | ( (list-get-demand list 0) 27 | (= list (Cons head tail))) 28 | ((set (list-get list 0) head)) 29 | :ruleset list-ruleset) 30 | (rule 31 | ( (list-get-demand list n) (> n 0) 32 | (= list (Cons head tail))) 33 | ((list-get-demand tail (- n 1))) 34 | :ruleset list-ruleset) 35 | (rule 36 | ( (list-get-demand list n) 37 | (= list (Cons head tail)) 38 | (= item (list-get tail (- n 1)))) 39 | ((set (list-get list n) item)) 40 | :ruleset list-ruleset) 41 | 42 | (constructor list-append (List List) List) 43 | (rewrite (list-append (Nil) list) list :ruleset list-ruleset) 44 | (rewrite (list-append (Cons head tail) list) (Cons head (list-append tail list)) :ruleset list-ruleset) 45 | 46 | ; list-contains Nil _ => false 47 | ; list-contains (Cons item tail) item => true 48 | ; list-contains (Cons head tail) item => assert(head != item); (list-contains tail item) 49 | ; list-contains needs inequality 50 | 51 | (constructor list-set (List i64 i64) List) 52 | (rewrite (list-set (Cons head tail) 0 item) (Cons item tail) :ruleset list-ruleset) 53 | (rewrite (list-set (Cons head tail) i item) (Cons head (list-set tail (- i 1) item)) :when ((> i 0)) :ruleset list-ruleset) 54 | 55 | ; Tests 56 | (let a (Cons 1 (Cons 2 (Nil)))) 57 | (let b (Cons 3 (Nil))) 58 | (let c (Cons 1 (Cons 2 (Cons 3 (Nil))))) 59 | (let d (Cons 1 (Cons 4 (Nil)))) 60 | (let e (list-append a b)) 61 | (let f (list-set a 1 4)) 62 | 63 | (list-length-demand c) 64 | (list-get-demand b 0) 65 | (list-get-demand a 1) 66 | 67 | (run-schedule (saturate (run list-ruleset))) 68 | 69 | (check (= e c)) 70 | (check (= (list-length c) 3)) 71 | (check (= (list-get b 0) 3)) 72 | (check (= (list-get a 1) 2)) 73 | (check (= f d)) 74 | -------------------------------------------------------------------------------- /tests/looking_up_global.egg: -------------------------------------------------------------------------------- 1 | (function f () i64 :no-merge) 2 | (set (f) 0) 3 | (let x (f)) 4 | 5 | (function g () i64 :no-merge) 6 | (fail (let y (g))) -------------------------------------------------------------------------------- /tests/looking_up_nonconstructor_in_rewrite_good.egg: -------------------------------------------------------------------------------- 1 | (function f (i64) i64 :no-merge) 2 | (datatype E 3 | (Sum i64 i64)) 4 | (rewrite 5 | (Sum 5 (+ 6 (f 7))) 6 | (Sum 3 4) 7 | ) 8 | (run 1) -------------------------------------------------------------------------------- /tests/map.egg: -------------------------------------------------------------------------------- 1 | (sort MyMap (Map i64 String)) 2 | 3 | (let my_map1 (map-insert (map-empty) 1 "one")) 4 | (let my_map2 (map-insert my_map1 2 "two")) 5 | 6 | (check (= "one" (map-get my_map1 1))) 7 | (query-extract my_map2) -------------------------------------------------------------------------------- /tests/math-microbenchmark.egg: -------------------------------------------------------------------------------- 1 | (datatype Math 2 | (Diff Math Math) 3 | (Integral Math Math) 4 | 5 | (Add Math Math) 6 | (Sub Math Math) 7 | (Mul Math Math) 8 | (Div Math Math) 9 | (Pow Math Math) 10 | (Ln Math) 11 | (Sqrt Math) 12 | 13 | (Sin Math) 14 | (Cos Math) 15 | 16 | (Const i64) 17 | (Var String)) 18 | 19 | (rewrite (Add a b) (Add b a)) 20 | (rewrite (Mul a b) (Mul b a)) 21 | (rewrite (Add a (Add b c)) (Add (Add a b) c)) 22 | (rewrite (Mul a (Mul b c)) (Mul (Mul a b) c)) 23 | 24 | (rewrite (Sub a b) (Add a (Mul (Const -1) b))) 25 | ;; (rewrite (Div a b) (Mul a (Pow b (Const -1))) :when ((is-not-zero b))) 26 | 27 | (rewrite (Add a (Const 0)) a) 28 | (rewrite (Mul a (Const 0)) (Const 0)) 29 | (rewrite (Mul a (Const 1)) a) 30 | 31 | (rewrite (Sub a a) (Const 0)) 32 | 33 | (rewrite (Mul a (Add b c)) (Add (Mul a b) (Mul a c))) 34 | (rewrite (Add (Mul a b) (Mul a c)) (Mul a (Add b c))) 35 | 36 | (rewrite (Mul (Pow a b) (Pow a c)) (Pow a (Add b c))) 37 | (rewrite (Pow x (Const 1)) x) 38 | (rewrite (Pow x (Const 2)) (Mul x x)) 39 | 40 | (rewrite (Diff x (Add a b)) (Add (Diff x a) (Diff x b))) 41 | (rewrite (Diff x (Mul a b)) (Add (Mul a (Diff x b)) (Mul b (Diff x a)))) 42 | 43 | (rewrite (Diff x (Sin x)) (Cos x)) 44 | (rewrite (Diff x (Cos x)) (Mul (Const -1) (Sin x))) 45 | 46 | (rewrite (Integral (Const 1) x) x) 47 | (rewrite (Integral (Cos x) x) (Sin x)) 48 | (rewrite (Integral (Sin x) x) (Mul (Const -1) (Cos x))) 49 | (rewrite (Integral (Add f g) x) (Add (Integral f x) (Integral g x))) 50 | (rewrite (Integral (Sub f g) x) (Sub (Integral f x) (Integral g x))) 51 | (rewrite (Integral (Mul a b) x) 52 | (Sub (Mul a (Integral b x)) 53 | (Integral (Mul (Diff x a) (Integral b x)) x))) 54 | (Integral (Ln (Var "x")) (Var "x")) 55 | (Integral (Add (Var "x") (Cos (Var "x"))) (Var "x")) 56 | (Integral (Mul (Cos (Var "x")) (Var "x")) (Var "x")) 57 | (Diff (Var "x") (Add (Const 1) (Mul (Const 2) (Var "x")))) 58 | (Diff (Var "x") (Sub (Pow (Var "x") (Const 3)) (Mul (Const 7) (Pow (Var "x") (Const 2))))) 59 | (Add (Mul (Var "y") (Add (Var "x") (Var "y"))) (Sub (Add (Var "x") (Const 2)) (Add (Var "x") (Var "x")))) 60 | (Div (Const 1) 61 | (Sub (Div (Add (Const 1) 62 | (Sqrt (Var "five"))) 63 | (Const 2)) 64 | (Div (Sub (Const 1) 65 | (Sqrt (Var "five"))) 66 | (Const 2)))) 67 | (run 11) 68 | (print-size Add) 69 | (print-size Mul) 70 | 71 | (print-size) 72 | 73 | (print-stats) -------------------------------------------------------------------------------- /tests/math.egg: -------------------------------------------------------------------------------- 1 | (datatype Math 2 | (Diff Math Math) 3 | (Integral Math Math) 4 | 5 | (Add Math Math) 6 | (Sub Math Math) 7 | (Mul Math Math) 8 | (Div Math Math) 9 | (Pow Math Math) 10 | (Ln Math) 11 | (Sqrt Math) 12 | 13 | (Sin Math) 14 | (Cos Math) 15 | 16 | (Const i64) 17 | (Var String)) 18 | 19 | (relation MathU (Math)) 20 | (rule ((= e (Diff x y))) ((MathU e))) 21 | (rule ((= e (Integral x y))) ((MathU e))) 22 | (rule ((= e (Add x y))) ((MathU e))) 23 | (rule ((= e (Sub x y))) ((MathU e))) 24 | (rule ((= e (Mul x y))) ((MathU e))) 25 | (rule ((= e (Div x y))) ((MathU e))) 26 | (rule ((= e (Pow x y))) ((MathU e))) 27 | (rule ((= e (Ln x))) ((MathU e))) 28 | (rule ((= e (Sqrt x))) ((MathU e))) 29 | (rule ((= e (Sin x))) ((MathU e))) 30 | (rule ((= e (Cos x))) ((MathU e))) 31 | (rule ((= e (Const x))) ((MathU e))) 32 | (rule ((= e (Var x))) ((MathU e))) 33 | 34 | (relation evals-to (Math i64)) 35 | 36 | (rule ((= e (Const c))) ((evals-to e c))) 37 | (rule ((= e (Add a b)) (evals-to a va) (evals-to b vb)) 38 | ((evals-to e (+ va vb)))) 39 | (rule ((= e (Sub a b)) (evals-to a va) (evals-to b vb)) 40 | ((evals-to e (- va vb)))) 41 | (rule ((= e (Mul a b)) (evals-to a va) (evals-to b vb)) 42 | ((evals-to e (* va vb)))) 43 | (rule ((= e (Div a b)) (evals-to a va) (evals-to b vb) (!= vb 0)) 44 | ((evals-to e (/ va vb)))) 45 | (rule ((evals-to x vx)) ((union x (Const vx)))) 46 | 47 | (relation is-const (Math)) 48 | (rule ((evals-to a va)) ((is-const a))) 49 | 50 | (relation is-sym (Math)) 51 | (rule ((= e (Var s))) ((is-sym e))) 52 | 53 | (relation is-not-zero (Math)) 54 | (rule ((evals-to x vx) 55 | (!= vx 0)) 56 | ((is-not-zero x))) 57 | 58 | (relation is-const-or-distinct-var-demand (Math Math)) 59 | (relation is-const-or-distinct-var (Math Math)) 60 | (rule ((is-const-or-distinct-var-demand v w) 61 | (is-const v)) 62 | ((is-const-or-distinct-var v w))) 63 | (rule ((is-const-or-distinct-var-demand v w) 64 | (= v (Var vv)) 65 | (= w (Var vw)) 66 | (!= vv vw)) 67 | ((is-const-or-distinct-var v w))) 68 | 69 | (rewrite (Add a b) (Add b a)) 70 | (rewrite (Mul a b) (Mul b a)) 71 | (rewrite (Add a (Add b c)) (Add (Add a b) c)) 72 | (rewrite (Mul a (Mul b c)) (Mul (Mul a b) c)) 73 | 74 | (rewrite (Sub a b) (Add a (Mul (Const -1) b))) 75 | (rewrite (Div a b) (Mul a (Pow b (Const -1))) :when ((is-not-zero b))) 76 | 77 | (rewrite (Add a (Const 0)) a) 78 | (rewrite (Mul a (Const 0)) (Const 0)) 79 | (rewrite (Mul a (Const 1)) a) 80 | 81 | ;; NOTE: these two rules are different from math.rs, as math.rs does pruning 82 | (rule ((MathU a) (!= a (Const 0))) ((union a (Add a (Const 0))))) 83 | (rule ((MathU a) (!= a (Const 1))) ((union a (Mul a (Const 1))))) 84 | 85 | (rewrite (Sub a a) (Const 0)) 86 | (rewrite (Div a a) (Const 1) :when ((is-not-zero a))) 87 | 88 | (rewrite (Mul a (Add b c)) (Add (Mul a b) (Mul a c))) 89 | (rewrite (Add (Mul a b) (Mul a c)) (Mul a (Add b c))) 90 | 91 | (rewrite (Mul (Pow a b) (Pow a c)) (Pow a (Add b c))) 92 | (rewrite (Pow x (Const 0)) (Const 1) :when ((is-not-zero x))) 93 | (rewrite (Pow x (Const 1)) x) 94 | (rewrite (Pow x (Const 2)) (Mul x x)) 95 | (rewrite (Pow x (Const -1)) (Div (Const 1) x) :when ((is-not-zero x))) 96 | (rewrite (Mul x (Div (Const 1) x)) (Const 1) :when ((is-not-zero x))) 97 | 98 | (rewrite (Diff x x) (Const 1) :when ((is-sym x))) 99 | (rule ((= e (Diff x c)) 100 | (is-sym x)) 101 | ((is-const-or-distinct-var-demand c x))) 102 | (rewrite (Diff x c) (Const 0) :when ((is-sym x) (is-const-or-distinct-var c x))) 103 | 104 | (rewrite (Diff x (Add a b)) (Add (Diff x a) (Diff x b))) 105 | (rewrite (Diff x (Mul a b)) (Add (Mul a (Diff x b)) (Mul b (Diff x a)))) 106 | 107 | (rewrite (Diff x (Sin x)) (Cos x)) 108 | (rewrite (Diff x (Cos x)) (Mul (Const -1) (Sin x))) 109 | 110 | (rewrite (Diff x (Ln x)) (Div (Const 1) x) :when ((is-not-zero x))) 111 | 112 | (rewrite (Diff x (Pow f g)) 113 | (Mul (Pow f g) 114 | (Add (Mul (Diff x f) (Div g f)) 115 | (Mul (Diff x g) (Ln f)))) 116 | :when ((is-not-zero f) 117 | (is-not-zero g))) 118 | 119 | (rewrite (Integral (Const 1) x) x) 120 | (rewrite (Integral (Pow x c) x) 121 | (Div (Pow x (Add c (Const 1))) (Add c (Const 1))) 122 | :when ((is-const c))) 123 | (rewrite (Integral (Cos x) x) (Sin x)) 124 | (rewrite (Integral (Sin x) x) (Mul (Const -1) (Cos x))) 125 | (rewrite (Integral (Add f g) x) (Add (Integral f x) (Integral g x))) 126 | (rewrite (Integral (Sub f g) x) (Sub (Integral f x) (Integral g x))) 127 | (rewrite (Integral (Mul a b) x) 128 | (Sub (Mul a (Integral b x)) 129 | (Integral (Mul (Diff x a) (Integral b x)) x))) 130 | 131 | 132 | (let start-expr2 (Add (Const 1) 133 | (Sub (Var "a") 134 | (Mul (Sub (Const 2) 135 | (Const 1)) 136 | (Var "a"))))) 137 | 138 | (run 6) 139 | 140 | (let end-expr2 (Const 1)) 141 | 142 | (check (= start-expr2 end-expr2)) 143 | 144 | (query-extract start-expr2) -------------------------------------------------------------------------------- /tests/matrix.egg: -------------------------------------------------------------------------------- 1 | 2 | (datatype Dim (Times Dim Dim) (NamedDim String) (Lit i64)) 3 | 4 | (rewrite (Times a (Times b c)) (Times (Times a b) c)) 5 | (rewrite (Times (Times a b) c) (Times a (Times b c)) ) 6 | (rewrite (Times (Lit i) (Lit j)) (Lit (* i j))) 7 | (rewrite (Times a b) (Times b a)) 8 | 9 | (datatype MExpr 10 | (MMul MExpr MExpr) 11 | (Kron MExpr MExpr) 12 | (NamedMat String) 13 | (Id Dim) 14 | ; DSum 15 | ; HStack 16 | ; VStack 17 | ; Transpose 18 | ; Inverse 19 | ; Zero Math Math 20 | ; ScalarMul 21 | ) 22 | 23 | ; alternative encoding (type A) = (Matrix n m) may be more useful for "large story example" 24 | (constructor nrows (MExpr) Dim) 25 | (constructor ncols (MExpr) Dim) 26 | 27 | (rewrite (nrows (Kron A B)) (Times (nrows A) (nrows B))) 28 | (rewrite (ncols (Kron A B)) (Times (ncols A) (ncols B))) 29 | 30 | (rewrite (nrows (MMul A B)) (nrows A)) 31 | (rewrite (ncols (MMul A B)) (ncols B)) 32 | 33 | (rewrite (nrows (Id n)) n) 34 | (rewrite (ncols (Id n)) n) 35 | 36 | (rewrite (MMul (Id n) A) A) 37 | (rewrite (MMul A (Id n)) A) 38 | 39 | (rewrite (MMul A (MMul B C)) (MMul (MMul A B) C)) 40 | (rewrite (MMul (MMul A B) C) (MMul A (MMul B C))) 41 | 42 | (rewrite (Kron A (Kron B C)) (Kron (Kron A B) C)) 43 | (rewrite (Kron (Kron A B) C) (Kron A (Kron B C))) 44 | 45 | (rewrite (Kron (MMul A C) (MMul B D)) (MMul (Kron A B) (Kron C D))) 46 | 47 | 48 | (rewrite (MMul (Kron A B) (Kron C D)) 49 | (Kron (MMul A C) (MMul B D)) 50 | :when 51 | ((= (ncols A) (nrows C)) 52 | (= (ncols B) (nrows D))) 53 | ) 54 | 55 | ; demand 56 | (rule ((= e (MMul A B))) 57 | ((ncols A) 58 | (nrows A) 59 | (ncols B) 60 | (nrows B)) 61 | ) 62 | 63 | (rule ((= e (Kron A B))) 64 | ((ncols A) 65 | (nrows A) 66 | (ncols B) 67 | (nrows B)) 68 | ) 69 | 70 | 71 | (let n (NamedDim "n")) 72 | (let m (NamedDim "m")) 73 | (let p (NamedDim "p")) 74 | 75 | (let A (NamedMat "A")) 76 | (let B (NamedMat "B")) 77 | (let C (NamedMat "C")) 78 | 79 | (union (nrows A) n) 80 | (union (ncols A) n) 81 | (union (nrows B) m) 82 | (union (ncols B) m) 83 | (union (nrows C) p) 84 | (union (ncols C) p) 85 | (let ex1 (MMul (Kron (Id n) B) (Kron A (Id m)))) 86 | (let rows1 (nrows ex1)) 87 | (let cols1 (ncols ex1)) 88 | 89 | (run 20) 90 | 91 | (check (= (nrows B) m)) 92 | (check (= (nrows (Kron (Id n) B)) (Times n m))) 93 | (let simple_ex1 (Kron A B)) 94 | (check (= ex1 simple_ex1)) 95 | 96 | (let ex2 (MMul (Kron (Id p) C) (Kron A (Id m)))) 97 | (run 10) 98 | (fail (check (= ex2 (Kron A C)))) 99 | -------------------------------------------------------------------------------- /tests/merge-during-rebuild.egg: -------------------------------------------------------------------------------- 1 | ; This file tests that non-union merges can be triggered during rebuilds as well 2 | ; as "inline" during a set action. See issue #42 3 | 4 | (datatype N (Node i64)) 5 | (function distance (N N) i64 :merge (min old new)) 6 | 7 | (let a (Node 0)) 8 | (let b (Node 1)) 9 | (let x (Node 2)) 10 | (let y (Node 3)) 11 | (set (distance x y) 1) 12 | (set (distance a b) 2) 13 | 14 | (union a x) 15 | (union b y) 16 | 17 | (run 1) 18 | (check (= (distance x y) 1)) ; fails, the distance has gone up! 19 | -------------------------------------------------------------------------------- /tests/merge-saturates.egg: -------------------------------------------------------------------------------- 1 | (function foo () i64 :merge (min old new)) 2 | 3 | (set (foo) 0) 4 | 5 | ; This should break at iteration 0 because the merge doesn't cause any updates 6 | (rule ((= f (foo))) ((set (foo) 1))) 7 | (run 100) 8 | 9 | 10 | ; This should run for about 50 iterations, because even though the merge doesn't 11 | ; change the value of baz, it has a side effect of expanding the domain of bar. 12 | 13 | ;(function baz (i64) i64 :default 0) 14 | 15 | ;(function bar () i64 :merge (min (baz new) 0)) 16 | 17 | ;(set (bar) 1) 18 | ;(set (bar) 2) 19 | 20 | ;(rule ((= f (baz x)) (< x 50)) 21 | ; ((set (bar) (+ x 1)))) 22 | 23 | ;(run 100) 24 | ;(check (= 0 (baz 50))) 25 | 26 | ; The exploit above is no longer valid due to the removal of default 27 | ; however, can still do with lookups in merge and constructors/relations 28 | 29 | (relation baz (i64)) 30 | 31 | (function const (Unit) i64 :no-merge) 32 | 33 | (set (const ()) 0) 34 | 35 | (function bar () i64 :merge (const (baz new))) 36 | 37 | (set (bar) 0) 38 | (set (bar) 1) 39 | (set (bar) 2) 40 | 41 | (rule ( 42 | (baz x) 43 | (< x 50) 44 | ) ( 45 | (set (bar) (+ x 1)) 46 | )) 47 | 48 | (run 100) 49 | (check (baz 50)) -------------------------------------------------------------------------------- /tests/merge_read.egg: -------------------------------------------------------------------------------- 1 | (function foo () i64 :no-merge) 2 | 3 | (function bar () i64 :merge (foo)) 4 | 5 | (set (bar) 0) 6 | 7 | (fail (set (bar) 1)) -------------------------------------------------------------------------------- /tests/multiset.egg: -------------------------------------------------------------------------------- 1 | (datatype Math (Num i64)) 2 | (sort MathToMath (UnstableFn (Math) Math)) 3 | (sort Maths (MultiSet Math)) 4 | 5 | (let xs (multiset-of (Num 1) (Num 2) (Num 3))) 6 | 7 | ;; verify equal to other ordering 8 | (check (= 9 | (multiset-of (Num 3) (Num 2) (Num 1)) 10 | xs 11 | )) 12 | 13 | ;; verify not equal to different counts 14 | (check (!= 15 | (multiset-of (Num 3) (Num 2) (Num 1) (Num 1)) 16 | xs 17 | )) 18 | 19 | ;; Unclear why check won't work if this is defined inline 20 | (let inserted (multiset-insert xs (Num 4))) 21 | ;; insert 22 | (check (= 23 | (multiset-of (Num 1) (Num 2) (Num 3) (Num 4)) 24 | inserted 25 | )) 26 | 27 | 28 | ;; contains and not contains 29 | (check (multiset-contains xs (Num 1))) 30 | (check (multiset-not-contains xs (Num 4))) 31 | 32 | ;; remove last 33 | (check (= 34 | (multiset-of (Num 1) (Num 3)) 35 | (multiset-remove xs (Num 2)) 36 | )) 37 | ;; remove one of 38 | (check (= (multiset-of (Num 1)) (multiset-remove (multiset-of (Num 1) (Num 1)) (Num 1)))) 39 | 40 | 41 | ;; length 42 | (check (= 3 (multiset-length xs))) 43 | ;; length repeated 44 | (check (= 3 (multiset-length (multiset-of (Num 1) (Num 1) (Num 1))))) 45 | 46 | ;; pick 47 | (check (= (Num 1) (multiset-pick (multiset-of (Num 1))))) 48 | 49 | ;; map 50 | (constructor square (Math) Math) 51 | (rewrite (square (Num x)) (Num (* x x))) 52 | 53 | (let squared-xs (unstable-multiset-map (unstable-fn "square") xs)) 54 | (run 1) 55 | (check (= 56 | (multiset-of (Num 1) (Num 4) (Num 9)) 57 | squared-xs 58 | )) 59 | 60 | ;; sum 61 | (check (= 62 | (multiset-sum (multiset-of (Num 1) (Num 2) (Num 3)) (multiset-of (Num 1) (Num 2) (Num 4))) 63 | (multiset-of (Num 1) (Num 4) (Num 2) (Num 3) (Num 2) (Num 1)) 64 | )) 65 | 66 | ;; verify that sum computes length 67 | (check (= 68 | (multiset-length (multiset-sum (multiset-of (Num 1) (Num 2) (Num 3)) (multiset-of (Num 1) (Num 2) (Num 4)))) 69 | 6 70 | )) 71 | -------------------------------------------------------------------------------- /tests/name-resolution.egg: -------------------------------------------------------------------------------- 1 | (datatype Math 2 | (Add Math Math) 3 | (Num i64)) 4 | 5 | (let zero (Num 0)) 6 | 7 | 8 | ;; zero here refers to the function/constant zero, not a free variable 9 | (rewrite (Add zero x) x) 10 | 11 | (let a (Add (Num 0) (Num 3))) 12 | (let b (Add (Num 7) (Num 9))) 13 | (let c (Num 16)) 14 | (union b c) 15 | 16 | ;; crash if we merge two numbers 17 | (rule ( 18 | (= (Num x) (Num y)) 19 | (!= x y) 20 | )( 21 | (panic "ahhh") 22 | )) 23 | 24 | 25 | (run 10) -------------------------------------------------------------------------------- /tests/no-messages/README.md: -------------------------------------------------------------------------------- 1 | These tests are run without saving or printing messages, to more accurately profile the performance when its embedded 2 | in a larger application and we use other ways to get the output, like the extraction report. 3 | -------------------------------------------------------------------------------- /tests/path-union.egg: -------------------------------------------------------------------------------- 1 | (datatype Node 2 | (mk i64)) 3 | 4 | (relation edge (Node Node)) 5 | (relation path (Node Node)) 6 | 7 | (rule ((edge x y)) 8 | ((path x y))) 9 | 10 | (rule ((path x y) (edge y z)) 11 | ((path x z))) 12 | 13 | (edge (mk 1) (mk 2)) 14 | (edge (mk 2) (mk 3)) 15 | (edge (mk 5) (mk 6)) 16 | 17 | (union (mk 3) (mk 5)) 18 | 19 | (run 10) 20 | (check (edge (mk 3) (mk 6))) 21 | (check (path (mk 1) (mk 6))) -------------------------------------------------------------------------------- /tests/path.egg: -------------------------------------------------------------------------------- 1 | (relation path (i64 i64)) 2 | (relation edge (i64 i64)) 3 | 4 | (rule ((edge x y)) 5 | ((path x y))) 6 | 7 | (rule ((path x y) (edge y z)) 8 | ((path x z))) 9 | 10 | (edge 1 2) 11 | (edge 2 3) 12 | (edge 3 4) 13 | (check (edge 1 2)) 14 | (fail (check (path 1 2))) 15 | (run 3) 16 | 17 | (print-function path 100) 18 | (check (path 1 4)) 19 | (fail (check (path 4 1))) 20 | -------------------------------------------------------------------------------- /tests/pathproof.egg: -------------------------------------------------------------------------------- 1 | ; proofs of connectivity are paths 2 | (datatype Proof 3 | (Trans i64 Proof) 4 | (Edge i64 i64)) 5 | 6 | ; We enhance the path relation to carry a proof field 7 | (relation path (i64 i64 Proof)) 8 | (relation edge (i64 i64)) 9 | 10 | (edge 2 1) 11 | (edge 3 2) 12 | (edge 1 3) 13 | 14 | (rule ((edge x y)) 15 | ((path x y (Edge x y)))) 16 | (rule ((edge x y) (path y z p)) 17 | ((path x z (Trans x p)))) 18 | 19 | ; We consider equal all paths tha connect same points. 20 | ; Smallest Extraction will extract shortest path. 21 | (rule ((path x y p1) (path x y p2)) 22 | ((union p1 p2))) 23 | 24 | (run 3) 25 | (check (path 3 1 (Trans 3 (Edge 2 1)))) 26 | ; Would prefer being able to check 27 | ;(check (path 1 2 _)) 28 | ; or extract 29 | ;(query-extract (path 1 4 ?p)) 30 | (print-function path 100) -------------------------------------------------------------------------------- /tests/points-to.egg: -------------------------------------------------------------------------------- 1 | ; Identifiers represented as strings, keep some newtypes around to aid clarity 2 | (datatype ClassT (Class String)) 3 | (datatype FieldT (Field String)) 4 | 5 | (datatype Stmt 6 | (New String ClassT) 7 | ; Assign dst src 8 | (Assign String String) 9 | ; Store dst field src 10 | (Store String FieldT String) 11 | ; Load dst src field 12 | (Load String String FieldT)) 13 | 14 | (relation VarPointsTo (String ClassT)) 15 | (relation HeapPointsTo (ClassT FieldT ClassT)) 16 | 17 | ; New variables point to classes they're initialized as 18 | (rule ((= x (New a b))) ((VarPointsTo a b))) 19 | 20 | ; If I assign v1 <- v2 and v2 points to a class c2, then v1 points to class c2 21 | ; as well 22 | (rule ((= x (Assign v1 v2)) (VarPointsTo v2 c2)) 23 | ((VarPointsTo v1 c2))) 24 | 25 | ; If c1.f points to c2, and v2 points to class c1, then assigning v1 <- v2.f 26 | ; means v1 points to c2 27 | (rule ((= x (Load v1 v2 f)) 28 | (VarPointsTo v2 c1) 29 | (HeapPointsTo c1 f c2)) 30 | ((VarPointsTo v1 c2))) 31 | 32 | ; If v1 points to class c1, and v2 to c2, and if v1.f <- v2, then c1.f points to 33 | ; c2 34 | (rule ((= x (Store v1 f v2)) 35 | (VarPointsTo v1 c1) 36 | (VarPointsTo v2 c2)) 37 | ((HeapPointsTo c1 f c2))) 38 | 39 | ; Example in "From Datalog to Flix" 40 | ; l1: ClassA o1 = new ClassA(); 41 | ; l2: ClassB o2 = new ClassB(); 42 | ; l3: ClassB o3 = o2; 43 | ; l4: o2.f = o1; 44 | ; l5: Object r = o3.f; 45 | 46 | (let A (Class "A")) 47 | (let B (Class "B")) 48 | (let f (Field "f")) 49 | 50 | (let l1 (New "o1" A)) 51 | (let l2 (New "o2" B)) 52 | (let l3 (Assign "o3" "o2")) 53 | (let l4 (Store "o2" f "o1")) 54 | (let l5 (Load "r" "o3" f)) 55 | 56 | (run 3) 57 | 58 | (check (VarPointsTo "o1" A)) 59 | (check (VarPointsTo "o2" B)) 60 | 61 | (check (VarPointsTo "o3" B)) 62 | (check (HeapPointsTo B f A)) 63 | (check (VarPointsTo "r" A)) -------------------------------------------------------------------------------- /tests/primitives.egg: -------------------------------------------------------------------------------- 1 | (check (= (+ 2 2) 4)) 2 | (check (= (- 2 1) 1)) 3 | (check (= (- 1 2) -1)) 4 | (check (< 1 2)) 5 | (check (> 1 -2)) -------------------------------------------------------------------------------- /tests/prims.egg: -------------------------------------------------------------------------------- 1 | ; A nasty, imperative implementation of Prim's algorithm... in egglog! 2 | ; https://en.wikipedia.org/wiki/Prim%27s_algorithm 3 | 4 | ; Weighted edge (vertex 1 * vertex 2 * weight) 5 | (datatype edge (Edge i64 i64 i64)) 6 | (relation edge-exists (edge)) 7 | 8 | (relation mytrue ()) 9 | (mytrue) 10 | (let infinity 99999999) ; close enough 11 | 12 | ; ==== PROBLEM INSTANCES ==== 13 | 14 | ; Graph 1 15 | ; (1)--2--(2) 16 | ; \ | 17 | ; 1 2 18 | ; \ | 19 | ; (3)--3--(4) 20 | (ruleset graph1) 21 | (rule ((mytrue)) 22 | ((edge-exists (Edge 1 2 2)) 23 | (edge-exists (Edge 1 4 1)) 24 | (edge-exists (Edge 2 4 2)) 25 | (edge-exists (Edge 3 4 3))) 26 | :ruleset graph1) 27 | 28 | ; Graph 2 29 | ; (1)-2-(2) (3) 30 | ; |\ /| / | 31 | ; | 3 5 | 4 | 32 | ; 5 X 2 / 5 33 | ; | / \ |/ | 34 | ; (4)-4-(5)-7-(6) 35 | (ruleset graph2) 36 | (rule ((mytrue)) 37 | ((edge-exists (Edge 1 2 1)) 38 | (edge-exists (Edge 1 4 5)) 39 | (edge-exists (Edge 1 5 3)) 40 | (edge-exists (Edge 2 4 5)) 41 | (edge-exists (Edge 2 5 2)) 42 | (edge-exists (Edge 3 5 4)) 43 | (edge-exists (Edge 3 6 5)) 44 | (edge-exists (Edge 4 5 4)) 45 | (edge-exists (Edge 5 6 7))) 46 | :ruleset graph2) 47 | 48 | ; ==== "INIT" RULESET ==== 49 | 50 | (ruleset init) 51 | 52 | ; Graph is undirected 53 | (rule ((= e (Edge x y weight))) 54 | ((union e (Edge y x weight))) 55 | :ruleset init) 56 | 57 | ; Whether a vertex is included *so far* (this changes). Returns 0 or 1. 58 | (function vertex-included (i64) i64 :merge (max old new)) 59 | 60 | ; All vertices default to being not included (note vertex-included's :merge) 61 | (rule ((edge-exists (Edge x y weight))) 62 | ((set (vertex-included x) 0)) 63 | :ruleset init) 64 | 65 | ; Keep track of the current iteration 66 | (function current-iteration () i64 :merge (max old new)) 67 | 68 | ; Map iteration to best edge found so far 69 | (function iteration-to-best-edge (i64) edge :merge new) 70 | (function iteration-to-best-edge-weight (i64) i64 :merge new) 71 | 72 | (rule ((mytrue)) 73 | ((set (vertex-included 1) 1) ; Initially just include vertex 1 74 | (set (current-iteration) 0) 75 | (set (iteration-to-best-edge-weight 0) infinity)) 76 | :ruleset init) 77 | 78 | ; === "CHOOSE BEST EDGE" RULESET === 79 | 80 | (relation edge-in-mst (edge)) ; whether an edge is in our solution 81 | 82 | (ruleset choose-best-edge) 83 | (rule ((= i (current-iteration)) 84 | (edge-exists (Edge x y weight)) 85 | (= 1 (vertex-included x)) 86 | (= 0 (vertex-included y)) 87 | (< weight (iteration-to-best-edge-weight i))) 88 | ((set (iteration-to-best-edge-weight i) weight) 89 | (set (iteration-to-best-edge i) (Edge x y weight))) 90 | :ruleset choose-best-edge) 91 | 92 | ; === "FINISH ITERATION" RULESET === 93 | 94 | (ruleset finish-iteration) 95 | (rule ((= i (current-iteration)) 96 | (= (Edge x y weight) (iteration-to-best-edge i))) 97 | ((edge-in-mst (Edge x y weight)) ; incorporate chosen best edge 98 | (set (vertex-included x) 1) ; mark its vertices as included 99 | (set (vertex-included y) 1) 100 | (set (current-iteration) (+ i 1)) ; advance iteration 101 | (set (iteration-to-best-edge-weight (+ i 1)) infinity)) 102 | :ruleset finish-iteration) 103 | 104 | ; === RUN VIA SCHEDULE === 105 | 106 | (run-schedule 107 | (saturate init graph1) ; change to graph2 to see other example 108 | (saturate (saturate choose-best-edge) finish-iteration) 109 | ) 110 | 111 | ; === PRINT RESULTS === 112 | 113 | ; (print-function edge-in-mst) ; this is not very helpful 114 | 115 | ; Just copy canonical edges to solution 116 | (relation solution (i64 i64 i64)) 117 | 118 | (ruleset finalize) 119 | (rule ((edge-in-mst (Edge x y weight)) (< x y)) 120 | ((solution x y weight)) 121 | :ruleset finalize) 122 | (run-schedule (saturate finalize)) 123 | 124 | (print-function solution 100) ; this is better 125 | -------------------------------------------------------------------------------- /tests/push-pop.egg: -------------------------------------------------------------------------------- 1 | (function foo () i64 :merge (max old new)) 2 | 3 | (set (foo) 1) 4 | (check (= (foo) 1)) 5 | 6 | (push) 7 | (set (foo) 2) 8 | (check (= (foo) 2)) 9 | (pop) 10 | 11 | (check (= (foo) 1)) -------------------------------------------------------------------------------- /tests/rat-pow-eval.egg: -------------------------------------------------------------------------------- 1 | (let zero (bigrat (bigint 0) (bigint 1))) 2 | (let zero-alt (bigrat (bigint 0) (bigint -1))) 3 | (check (= zero zero-alt)) 4 | (check (= zero (* (bigrat (bigint -1) (bigint 1)) zero-alt))) 5 | 6 | (let one (bigrat (bigint 1) (bigint 1))) 7 | (let two (bigrat (bigint 2) (bigint 1))) 8 | 9 | (let four (bigrat (bigint 4) (bigint 1))) 10 | (let fourth (bigrat (bigint 1) (bigint 4))) 11 | (check (!= four fourth)) 12 | 13 | (let neg-one (bigrat (bigint -1) (bigint 1))) 14 | (let neg-one-alt (bigrat (bigint 1) (bigint -1))) 15 | (check (= neg-one neg-one-alt)) 16 | 17 | (let neg-two (* neg-one two)) 18 | 19 | 20 | ; 1 = 0^0 (zero-to-zero edge case) 21 | (check (= one (pow zero zero))) 22 | (check (= one (pow zero zero-alt))) 23 | ; 0 = 0^2 (a positive power) 24 | (check (= zero (pow zero two))) 25 | ; 1/0 error condition 26 | (fail (pow zero neg-one)) 27 | (fail (pow zero neg-two)) 28 | 29 | ; 4 = 2^2 30 | (check (= four (pow two two))) 31 | ; 1/4 == 4^-1 32 | (check (= fourth (pow four neg-one))) 33 | ; 1/4 = 2^-2 34 | (check (= fourth (pow two neg-two))) 35 | ; 1 = 1^-2 36 | (check (= one (pow one neg-two))) 37 | ; 1 = 2^0 38 | (check (= one (pow two zero))) 39 | ; 1 = (-2)^0 40 | (check (= one (pow neg-two zero))) 41 | 42 | ; rational powers are forbidden! 43 | (fail (pow zero fourth)) 44 | (fail (pow two fourth)) 45 | 46 | 47 | ; big numbers 48 | (let sixty-four (bigrat (bigint 64) (bigint 1))) 49 | (let sixty-three (- sixty-four one)) 50 | (let max-i64 (- (pow two sixty-three) one )) 51 | (let max-u64 (pow two sixty-four)) 52 | 53 | ; max power is max-i64 54 | (check (= one (pow one max-i64))) 55 | ; adding one more to the power should fail 56 | (fail (check (= one (pow one (+ one max-i64) )))) 57 | (fail (pow two max-u64)) 58 | -------------------------------------------------------------------------------- /tests/repro-define.egg: -------------------------------------------------------------------------------- 1 | (datatype Nat 2 | (S Nat)) 3 | (constructor ZeroConst () Nat) 4 | (let Zero (ZeroConst)) 5 | 6 | (let two (S (S Zero))) 7 | 8 | (union two (S (S (S Zero)))) 9 | (check (= two (S (S (S Zero))))) 10 | -------------------------------------------------------------------------------- /tests/repro-desugar-143.egg: -------------------------------------------------------------------------------- 1 | ;; To test on issue #143 2 | (rule ((= x 1) (= y x)) ()) 3 | (rule ((= x 1) (= y x) (= z y)) ()) 4 | 5 | (function f (i64) i64 :no-merge) 6 | 7 | (set (f 0) 0) 8 | 9 | ;; a funky id rule 10 | (rule ((f x) (= x y) (= z y) (= a (f z))) ((set (f (+ z 1)) (+ a 1)))) 11 | 12 | (run 20) 13 | 14 | (print-function f 100) 15 | (check (= (f 10) 10)) 16 | 17 | (datatype Value (Num i64)) 18 | (constructor fib (Value) Value) 19 | 20 | ;; a funky fibonacci that test on more complex case and user defined datatype 21 | (rule ((= (Num a) (fib (Num x))) 22 | (= (Num b) (fib (Num y))) 23 | (= x1 x) 24 | (= y1 y) 25 | (= a1 a) 26 | (= b1 b) 27 | (= x1 x2) 28 | (= y1 y2) 29 | (= a1 a2) 30 | (= b1 b2) 31 | (= 1 (- x2 y2))) 32 | ((let n (+ x 1)) (let sum (+ a1 b2)) (union (fib (Num n)) (Num sum)))) 33 | 34 | (union (fib (Num 1)) (Num 1)) 35 | (union (fib (Num 2)) (Num 1)) 36 | 37 | (run 20) 38 | 39 | (print-function fib 100) 40 | (check (= (fib (Num 10)) (Num 55))) 41 | -------------------------------------------------------------------------------- /tests/repro-empty-query.egg: -------------------------------------------------------------------------------- 1 | (function foo () i64 :merge (min old new)) 2 | 3 | (rule () ((set (foo) 4))) 4 | 5 | (set (foo) 10) 6 | 7 | (run 3) 8 | 9 | (check (= (foo) 4)) -------------------------------------------------------------------------------- /tests/repro-equal-constant.egg: -------------------------------------------------------------------------------- 1 | (function foo () i64 :merge (min old new)) 2 | 3 | (rule ((= (foo) 5)) ((set (foo) 4))) 4 | 5 | (set (foo) 10) 6 | 7 | (run 3) 8 | 9 | (check (!= (foo) 4)) -------------------------------------------------------------------------------- /tests/repro-equal-constant2.egg: -------------------------------------------------------------------------------- 1 | (function foo () i64 :merge (min old new)) 2 | 3 | (rule ((= (foo) 10)) ((set (foo) 4))) 4 | 5 | (set (foo) 10) 6 | 7 | (run 3) 8 | 9 | (check (= (foo) 4)) -------------------------------------------------------------------------------- /tests/repro-noteqbug.egg: -------------------------------------------------------------------------------- 1 | (datatype r (R i64)) 2 | (union (R 1) (R 2)) 3 | 4 | (check (= (R 1) (R 2))) 5 | (fail (check (!= (R 1) (R 2)))) 6 | 7 | (run 0) 8 | 9 | 10 | (check (= (R 1) (R 2))) 11 | (fail (check (!= (R 1) (R 2)))) 12 | -------------------------------------------------------------------------------- /tests/repro-primitive-query.egg: -------------------------------------------------------------------------------- 1 | (datatype Math 2 | (Num i64)) 3 | 4 | (Num 1) 5 | (Num 2) 6 | 7 | (rule ((Num ?a) 8 | (Num ?b) 9 | (= (+ ?a ?b) 5)) 10 | ((panic "should not have matched"))) 11 | 12 | (run 100) -------------------------------------------------------------------------------- /tests/repro-querybug.egg: -------------------------------------------------------------------------------- 1 | (datatype list 2 | (Cons i64 list)) 3 | (constructor EmptyConst () list) 4 | (let Empty (EmptyConst)) 5 | 6 | (relation eq (list list)) 7 | 8 | (eq Empty Empty) 9 | 10 | ; Oddly, this version works: 11 | ; (rule ((= x (Cons x1 rest1)) (= y (Cons x2 rest2)) (= 0 (- x1 x2)) (eq rest1 rest2)) 12 | (rule ((= x (Cons x1 rest1)) (= y (Cons x2 rest2)) (= x1 x2) (eq rest1 rest2)) 13 | ((eq (Cons x1 rest1) (Cons x2 rest2)))) 14 | 15 | (let mylist (Cons 1 Empty)) 16 | 17 | (run 100) 18 | -------------------------------------------------------------------------------- /tests/repro-querybug2.egg: -------------------------------------------------------------------------------- 1 | (datatype Nat 2 | (Num i64) 3 | (OtherNum i64)) 4 | 5 | 6 | 7 | (rule ((= y 2)) 8 | ((union (OtherNum y) (Num y)))) 9 | 10 | (Num 2) 11 | 12 | 13 | (run 100) 14 | 15 | (check (= (OtherNum 2) (Num 2))) 16 | -------------------------------------------------------------------------------- /tests/repro-querybug3.egg: -------------------------------------------------------------------------------- 1 | (datatype VarT) 2 | (datatype Term) 3 | (constructor App (Term Term) Term) 4 | (constructor Lam (VarT Term) Term) 5 | (constructor Var (VarT) Term) 6 | (constructor Let (VarT Term Term) Term) 7 | (constructor Add (Term Term) Term) 8 | (constructor Num (i64) Term) 9 | (constructor CaseSplit (Term Term Term) Term) 10 | (constructor Cons (Term Term) Term) 11 | (constructor Nil () Term) 12 | (constructor V (String) VarT) 13 | (sort StringSet (Set VarT)) 14 | (function freer (Term) StringSet :merge (set-intersect old new)) 15 | 16 | ;;(rule ((= e (App e1 e2)) 17 | ;; (= (freer e1) fv1) 18 | ;; (= (freer e2) fv2)) 19 | ;; ((set (freer e) (set-union fv1 fv2)))) 20 | 21 | (rule ((= e (App e1 e2)) 22 | (= fvar1 (freer e1)) 23 | (= fvar1 fv1) 24 | (= fvar2 (freer e2)) 25 | (= fvar2 fv2)) 26 | ((set (freer e) (set-union fv1 fv2)))) 27 | (rule ((= e (Var v))) ((set (freer e) (set-insert (set-empty) v)))) 28 | (constructor sum () Term :cost 1000) 29 | (union (sum) (Lam (V "xs") (CaseSplit (Var (V "xs")) (Num 0) (Lam (V "x") (Lam (V "xs'") (Add (Var (V "x")) (App (sum) (Var (V "xs'"))))))))) 30 | (set (freer (sum)) (set-empty)) 31 | (run 100) 32 | -------------------------------------------------------------------------------- /tests/repro-querybug4.egg: -------------------------------------------------------------------------------- 1 | (sort Nat) 2 | (constructor Num (i64) Nat) 3 | (constructor OtherNum (i64) Nat) 4 | (rule ((= fvar5__ 2) (= fvar6__ fvar5__) (= y fvar5__)) 5 | ((union (OtherNum fvar5__) (Num fvar5__)))) 6 | 7 | (Num 2) 8 | (run 100) 9 | (check (= (OtherNum 2) (Num 2))) -------------------------------------------------------------------------------- /tests/repro-should-saturate.egg: -------------------------------------------------------------------------------- 1 | 2 | (function MyMap () i64 :merge (min old new)) 3 | 4 | (set (MyMap) 1) 5 | 6 | (rule ((MyMap)) 7 | ((set (MyMap) 1) 8 | (set (MyMap) 2))) 9 | 10 | (run-schedule (saturate (run))) 11 | -------------------------------------------------------------------------------- /tests/repro-silly-panic.egg: -------------------------------------------------------------------------------- 1 | (datatype KAT 2 | (par KAT KAT) 3 | ) 4 | (constructor AConst () KAT) 5 | (let A (AConst)) 6 | 7 | (rewrite (par p p) p) 8 | (rule ((= r (par q r)) (= q (par q r))) ((union r q))) 9 | 10 | ; tests 11 | (let q (par A A)) 12 | (run 10) -------------------------------------------------------------------------------- /tests/repro-typechecking-schedule.egg: -------------------------------------------------------------------------------- 1 | (rule () ()) 2 | 3 | ;; This should type check 4 | (run-schedule 5 | (seq (run :until (= a 1)) 6 | (run :until (= a "s")))) 7 | -------------------------------------------------------------------------------- /tests/repro-unsound-htutorial.egg: -------------------------------------------------------------------------------- 1 | (datatype Math 2 | (Num i64) 3 | (Var String) 4 | (Add Math Math) 5 | (Div Math Math) 6 | (Mul Math Math)) 7 | 8 | (let z (Var "z")) 9 | 10 | (Add (Var "x") (Var "y")) 11 | 12 | (rewrite (Add a z) a) 13 | 14 | (run 2) 15 | 16 | (fail (check (= (Var "x") (Add (Var "x") (Var "y"))))) -------------------------------------------------------------------------------- /tests/repro-unsound.egg: -------------------------------------------------------------------------------- 1 | 2 | (datatype HerbieType (Type String)) 3 | (datatype Math (Num HerbieType i64) (Var HerbieType String) (Fma HerbieType Math Math Math) (If HerbieType Math Math Math) (Less HerbieType Math Math) (LessEq HerbieType Math Math) (Greater HerbieType Math Math) (GreaterEq HerbieType Math Math) (Eq HerbieType Math Math) (NotEq HerbieType Math Math) (Add HerbieType Math Math) (Sub HerbieType Math Math) (Mul HerbieType Math Math) (Div HerbieType Math Math) (Pow HerbieType Math Math) (Atan2 HerbieType Math Math) (Hypot HerbieType Math Math) (And HerbieType Math Math) (Or HerbieType Math Math) (Not HerbieType Math) (Neg HerbieType Math) (Sqrt HerbieType Math) (Cbrt HerbieType Math) (Fabs HerbieType Math) (Ceil HerbieType Math) (Floor HerbieType Math) (Round HerbieType Math) (Log HerbieType Math) (Exp HerbieType Math) (Sin HerbieType Math) (Cos HerbieType Math) (Tan HerbieType Math) (Atan HerbieType Math) (Asin HerbieType Math) (Acos HerbieType Math) (Expm1 HerbieType Math) (Log1p HerbieType Math) (Sinh HerbieType Math) (Cosh HerbieType Math) (Tanh HerbieType Math) (PI HerbieType) (E HerbieType) (INFINITY HerbieType) (TRUE HerbieType) (FALSE HerbieType)) 4 | (let r-zero 0) 5 | (let r-one 1) 6 | (let r-two 2) 7 | (let r-three 3) 8 | (let r-four 4) 9 | (let r-neg-one -1) 10 | (relation universe (Math HerbieType)) 11 | (rule ((= t (Expm1 ty a))) ((universe t ty))) 12 | (rewrite (Mul ty a b) (Mul ty b a)) 13 | 14 | (rewrite (Sub ty x x) (Num ty r-zero)) 15 | 16 | (rewrite (Mul ty x (Num ty r-one)) x) 17 | 18 | (rewrite (Div ty x (Num ty r-one)) x) 19 | 20 | (rewrite (Neg ty x) (Sub ty (Num ty r-zero) x)) 21 | 22 | (rewrite (Neg ty x) (Mul ty (Num ty r-neg-one) x)) 23 | 24 | (rule ((universe t ty)) ((union t (Mul ty (Num ty r-one) t)))) 25 | 26 | (rewrite (Div ty (Sub ty a b) c) 27 | (Sub ty (Div ty a c) (Div ty b c))) 28 | 29 | 30 | (rewrite (Div ty (Mul ty a b) (Mul ty c d)) ;; not defined if c or d is zero 31 | (Mul ty (Div ty a c) (Div ty b d))) 32 | 33 | 34 | ;; errors if a or b errors 35 | (rewrite (Add ty a b) 36 | (If ty 37 | (NotEq ty (Sub ty a b) (Num ty r-zero)) ;; errors if a or b errors 38 | (Div ty 39 | (Sub ty (Mul ty a a) (Mul ty b b)) 40 | (Sub ty a b)) 41 | (Add ty a b))) 42 | 43 | 44 | (rewrite (Sub ty (Div ty a b) (Div ty c d)) ;; errors when b = 0 or d = 0 45 | (Div ty (Sub ty (Mul ty a d) (Mul ty b c)) 46 | (Mul ty b d))) ;; errors when b = 0 or d = 0 47 | 48 | 49 | (rewrite (Sub ty (Mul ty x y) z) 50 | (Fma ty x y (Neg ty z))) 51 | 52 | 53 | (rewrite (Expm1 ty x) 54 | (Sub ty (Exp ty x) (Num ty r-one))) 55 | 56 | 57 | 58 | 59 | (let eggvar1 (Div (Type "binary64") (Expm1 (Type "binary64") (Add (Type "binary64") (Var (Type "binary64") "h0") (Var (Type "binary64") "h0"))) (Expm1 (Type "binary64") (Var (Type "binary64") "h0")))) 60 | 61 | (run 10) 62 | 63 | (check (= (Num ty n) (Num ty m)) (!= n m)) 64 | -------------------------------------------------------------------------------- /tests/repro-vec-unequal.egg: -------------------------------------------------------------------------------- 1 | (datatype Math 2 | (Num i64)) 3 | 4 | (sort MathVec (Vec Math)) 5 | 6 | (let v1 (vec-of (Num 1) (Num 2))) 7 | (let v2 (vec-of (Num 2) (Num 2))) 8 | 9 | (fail (check (= v1 v2))) 10 | 11 | 12 | (sort IVec (Vec i64)) 13 | 14 | (let v3 (vec-of 1 2)) 15 | (let v4 (vec-of 2 2)) 16 | 17 | (fail (check (= v3 v4))) -------------------------------------------------------------------------------- /tests/resolution.egg: -------------------------------------------------------------------------------- 1 | ; Resolution theorem proving 2 | ; 3 | ; Traditional resolution theorem provers maintain a clause database 4 | ; of formulas in Conjunction Normal Form (CNF a big And of Ors). 5 | ; Each clause is a set of positive and negative literals 6 | ; The prover saturates this set by taking two clauses 7 | ; {a}\/c1 {not a}\/c2 and creating a new clause c1 \/ c2. 8 | ; Clauses also are pruned by simplications, unit propagation, 9 | ; and subsumption. 10 | ; These systems use sophisticated term indexing to find matching clauses 11 | 12 | ; A natural question is whether egglog's saturation and term indexing gives 13 | ; a leg up towards building one of these systems. A programmable one even, 14 | ; with built in support for equality reasoning 15 | 16 | ; Resolution is provided by a join 17 | ; unit propagation is an equation solving process and egraph substitution 18 | ; Clause Simplification is provided by rewrite rules 19 | 20 | ; This encoding seems about right but is unsatisfying 21 | ; Using AC to encode the set nature of clauses is inefficient 22 | 23 | ; An important aspect of these provers that seems challenging to encode shallowly 24 | ; is that the match also occurs modulo _unification_. 25 | ; The unification variables of each clause are not globally scoped, really 26 | ; they are scoped outside the body of each clase in an implicit \forall 27 | ; This encoding as it stands really only supports ground atoms modulo equality 28 | 29 | (datatype Bool) 30 | (constructor TrueConst () Bool) 31 | (let True (TrueConst)) 32 | (constructor FalseConst () Bool) 33 | (let False (FalseConst)) 34 | (constructor myor (Bool Bool) Bool) 35 | (constructor negate (Bool) Bool) 36 | 37 | ; clauses are assumed in the normal form (or a (or b (or c False))) 38 | 39 | (union (negate False) True) 40 | (union (negate True) False) 41 | 42 | ; "Solving" negation equations 43 | (rule ((= (negate p) True)) ((union p False))) 44 | (rule ((= (negate p) False)) ((union p True))) 45 | 46 | ; canonicalize associtivity. "append" for clauses 47 | ; terminate with false 48 | (rewrite (myor (myor a b) c) (myor a (myor b c))) 49 | ; commutativity 50 | (rewrite (myor a (myor b c)) (myor b (myor a c))) 51 | 52 | ;absorption 53 | (rewrite (myor a (myor a b)) (myor a b)) 54 | (rewrite (myor a (myor (negate a) b)) True) 55 | 56 | ; simplification 57 | (rewrite (myor False a) a) 58 | (rewrite (myor a False) a) 59 | (rewrite (myor True a) True) 60 | (rewrite (myor a True) True) 61 | 62 | ; unit propagation 63 | ; This is kind of interesting actually. 64 | ; Looks a bit like equation solving 65 | 66 | ; The following is not valid egglog but could be? 67 | ;(rewrite p True 68 | ; :when ((= True (or p False)))) 69 | 70 | (rule ((= True (myor p False))) ((union p True))) 71 | 72 | ; resolution 73 | ; This counts on commutativity to bubble everything possible up to the front of the clause. 74 | (rule ((= True (myor a as)) (= True (myor (negate a) bs))) 75 | ((union (myor as bs) True))) 76 | 77 | ; example predicate 78 | (constructor p (i64) Bool) 79 | (let p0 (p 0)) 80 | (let p1 (p 1)) 81 | (let p2 (p 2)) 82 | ;(union (or p0 (or p1 (or p2 False))) True) 83 | ;(union (or (negate p0) (or p1 (or (negate p2) False))) True) 84 | (union (myor p1 (myor (negate p2) False)) True) 85 | (union (myor p2 (myor (negate p0) False)) True) 86 | (union (myor p0 (myor (negate p1) False)) True) 87 | (union p1 False) 88 | (union (myor (negate p0) (myor p1 (myor p2 False))) True) 89 | (run 10) 90 | 91 | 92 | (check (!= True False)) 93 | (check (= p0 False)) 94 | (check (= p2 False)) 95 | 96 | ; we could turn the original axioms into _patterns_ in all possible directions. 97 | ; Which is kind of compelling 98 | ; (rule ((or (pat x))) ) 99 | ; or let a unification expansion happen and use thos 100 | 101 | 102 | -------------------------------------------------------------------------------- /tests/schedule-demo.egg: -------------------------------------------------------------------------------- 1 | ; Step with alternating feet, left before right 2 | (relation left (i64)) 3 | (relation right (i64)) 4 | 5 | (left 0) 6 | (right 0) 7 | 8 | (ruleset step-left) 9 | (rule ((left x) (right x)) 10 | ((left (+ x 1))) 11 | :ruleset step-left) 12 | 13 | (ruleset step-right) 14 | (rule ((left x) (right y) (= x (+ y 1))) 15 | ((right x)) 16 | :ruleset step-right) 17 | 18 | (run-schedule 19 | (repeat 10 20 | (saturate step-right) 21 | (saturate step-left))) 22 | 23 | ; We took 10 steps with the left, but the right couldn't go the first round, 24 | ; so we took only 9 steps with the right. 25 | (check (left 10)) 26 | (check (right 9)) 27 | (fail (check (left 11))) 28 | (fail (check (right 10))) 29 | -------------------------------------------------------------------------------- /tests/set.egg: -------------------------------------------------------------------------------- 1 | (sort ISetBase (Set i64)) 2 | 3 | ; Test set-of 4 | (check (= (set-of 1 2) (set-insert (set-insert (set-empty) 1) 2))) 5 | (check (= (set-of 1 2) (set-insert (set-insert (set-empty) 2) 1))) 6 | 7 | ; Test set-union 8 | (check (= (set-union (set-of 1 2) (set-of 3 4)) (set-of 1 2 3 4))) 9 | 10 | ; Test set-length 11 | (check (= 0 (set-length (set-empty)))) 12 | (check (= 1 (set-length (set-of 1 1 1)))) 13 | (check (= 2 (set-length (set-of 1 -1 1 1)))) 14 | 15 | ; Test set-get 16 | (check (= 1 (set-get (set-of 1 -1 2 4 1) 0))) 17 | (check (= 2 (set-get (set-of 1 -1 2 4 1) 1))) 18 | (check (= 4 (set-get (set-of 1 -1 2 4 1) 2))) 19 | (check (= -1 (set-get (set-of 1 -1 2 4 1) 3))) 20 | 21 | ; Test set-remove 22 | (check (= (set-remove (set-of 1 2 3) 3) (set-of 1 2))) 23 | 24 | ; Reify set 25 | (sort ISet) 26 | (constructor IS (ISetBase) ISet) 27 | 28 | (function ISet-get (ISet i64) i64 :no-merge) 29 | (rule ((IS x) (> (set-length x) 0)) 30 | ((set (ISet-get (IS x) 0) (set-get x 0)))) 31 | (rule ((ISet-get (IS x) j) 32 | (= i (+ j 1)) (< i (set-length x))) 33 | ((set (ISet-get (IS x) i) (set-get x i)))) 34 | 35 | (let myset (IS (set-of 2 4 1 4 -1))) 36 | (run 100) 37 | (check (= 1 (ISet-get myset 0))) 38 | (check (= 2 (ISet-get myset 1))) 39 | (check (= 4 (ISet-get myset 2))) 40 | (check (= -1 (ISet-get myset 3))) 41 | -------------------------------------------------------------------------------- /tests/set_sort_function.egg: -------------------------------------------------------------------------------- 1 | (sort Foo) 2 | (function bar () Foo :no-merge) 3 | (constructor baz () Foo) 4 | (constructor qux () Foo) 5 | (constructor quux () Foo) 6 | 7 | (set (bar) (baz)) 8 | (union (baz) (qux)) 9 | (set (bar) (qux)) 10 | (fail (set (bar) (quux))) -------------------------------------------------------------------------------- /tests/stratified.egg: -------------------------------------------------------------------------------- 1 | (relation path (i64 i64)) 2 | (relation edge (i64 i64)) 3 | 4 | (rule ((edge x y)) 5 | ((path x y))) 6 | 7 | (edge 1 2) 8 | (edge 2 3) 9 | (edge 3 4) 10 | (check (edge 1 2)) 11 | (run 3) 12 | (check (path 1 2)) 13 | 14 | (ruleset path-rules) 15 | 16 | (rule ((path x y) (edge y z)) 17 | ((path x z)) 18 | :ruleset path-rules) 19 | 20 | (edge 3 8) 21 | (run path-rules 1) 22 | (check (path 1 3)) 23 | 24 | 25 | 26 | ; Should fail 27 | ; (check (path 1 4)) 28 | ; (check (path 3 8)) 29 | -------------------------------------------------------------------------------- /tests/string.egg: -------------------------------------------------------------------------------- 1 | ; Tests for the string sort 2 | 3 | ; Concatenation 4 | (check (= (+ "a" "bc" "de") "abcde")) 5 | ; Counting the number of substring occurances 6 | (check (= (count-matches "ab ab" "ab") 2)) 7 | ; replacing a substring 8 | (check (= (replace "ab ab" "ab" "cd") "cd cd")) 9 | -------------------------------------------------------------------------------- /tests/string_quotes.csv: -------------------------------------------------------------------------------- 1 | abc 2 | -------------------------------------------------------------------------------- /tests/string_quotes.egg: -------------------------------------------------------------------------------- 1 | (function f () String :no-merge) 2 | (input f "tests/string_quotes.csv") 3 | (check (= (f) "abc")) 4 | -------------------------------------------------------------------------------- /tests/subsume-relation.egg: -------------------------------------------------------------------------------- 1 | (datatype V (A) (B) (C)) 2 | (relation R (V V)) 3 | 4 | (A) (B) (C) 5 | (subsume (R (A) (B))) 6 | (R (A) (C)) 7 | (union (B) (C)) 8 | 9 | (check (= (R (A) (B)) ())) 10 | -------------------------------------------------------------------------------- /tests/subsume.egg: -------------------------------------------------------------------------------- 1 | ;; Let's pretend that we are optimizing mathematical expressions, but for some reason on our compiler 2 | ;; multiplying by three is very expensive. So we want to rewrite those forms to three additions instead, and always 3 | ;; extract that form. 4 | 5 | (datatype Math 6 | (Num i64) 7 | (Var String) 8 | (Add Math Math) 9 | (Mul Math Math)) 10 | 11 | 12 | (rewrite (Mul (Num 3) x) (Add x (Add x x)) :subsume) 13 | 14 | (let x (Mul (Num 2) (Mul (Num 3) (Var "x")))) 15 | 16 | (run 10) 17 | 18 | ; When X is extracted, we get the optimized form, where the * 3 is expanded out 19 | (check (= x (Mul (Num 2) (Add (Var "x") (Add (Var "x") (Var "x")))))) 20 | (extract x) 21 | ; Will be (Mul (Num 2) (Add (Var "x") (Add (Var "x") (Var "x")))) 22 | 23 | ; Even though it can't be extracted, we can still check that x equal 2 * (3 * x) 24 | (check (= x (Mul (Num 2) (Mul (Num 3) (Var "x"))))) 25 | 26 | ; Also if we make multiplication commutative and run that run, we won't get that result either 27 | ; since the original expr has been subsumed when it was replaced with the addition 28 | (rewrite (Mul x y) (Mul y x)) 29 | (run 10) 30 | (extract x) 31 | -------------------------------------------------------------------------------- /tests/terms.rs: -------------------------------------------------------------------------------- 1 | use egglog::*; 2 | 3 | // This file tests the public API to terms. 4 | 5 | #[test] 6 | fn test_termdag_public() { 7 | let mut td = TermDag::default(); 8 | let x = td.var("x".into()); 9 | let seven = td.lit(7.into()); 10 | let f = td.app("f".into(), vec![x, seven]); 11 | assert_eq!(td.to_string(&f), "(f x 7)"); 12 | } 13 | 14 | #[test] 15 | #[should_panic] 16 | fn test_termdag_malicious_client() { 17 | // here is an example of how TermIds can be misused by passing 18 | // them into the wrong DAG. 19 | 20 | let mut td = TermDag::default(); 21 | let x = td.var("x".into()); 22 | // at this point, td = [0 |-> x] 23 | // snapshot the current td 24 | let td2 = td.clone(); 25 | let y = td.var("y".into()); 26 | // now td = [0 |-> x, 1 |-> y] 27 | let f = td.app("f".into(), vec![x.clone(), y.clone()]); 28 | // f is Term::App("f", [0, 1]) 29 | assert_eq!(td.to_string(&f), "(f x y)"); 30 | // recall that td2 = [0 |-> x] 31 | // notice that f refers to index 1, so this crashes: 32 | td2.to_string(&f); 33 | } 34 | -------------------------------------------------------------------------------- /tests/test-combined-steps.egg: -------------------------------------------------------------------------------- 1 | ; Step with alternating feet, left before right 2 | (relation left (i64)) 3 | (relation right (i64)) 4 | (relation middle (i64)) 5 | 6 | (left 0) 7 | (right 0) 8 | 9 | (ruleset step-left) 10 | (rule ((left x) (right x)) 11 | ((left (+ x 1))) 12 | :ruleset step-left) 13 | 14 | (ruleset step-right) 15 | (rule ((left x) (right y) (= x (+ y 1))) 16 | ((right x)) 17 | :ruleset step-right) 18 | 19 | (ruleset step-middle) 20 | (rule ((left x)) 21 | ((middle x)) 22 | :ruleset step-middle) 23 | 24 | (unstable-combined-ruleset 25 | my-combination 26 | step-left step-right 27 | step-middle) 28 | 29 | (run-schedule (repeat 1 my-combination)) 30 | 31 | (check (left 1)) 32 | (check (right 0)) 33 | ;; middle didn't observe anything except original step 34 | (check (middle 0)) 35 | (fail (check (left 2))) 36 | (fail (check (right 1))) 37 | (fail (check (middle 1))) 38 | (fail (check (middle 2))) 39 | 40 | 41 | (run-schedule 42 | (repeat 9 43 | (saturate step-right) 44 | my-combination 45 | (saturate step-right))) 46 | 47 | (check (left 10)) 48 | (check (right 10)) 49 | ;; middle didn't get a chance to observe (left 10) 50 | (check (middle 9)) 51 | (fail (check (middle 10))) 52 | (fail (check (left 11))) 53 | (fail (check (right 11))) 54 | -------------------------------------------------------------------------------- /tests/test-combined.egg: -------------------------------------------------------------------------------- 1 | (relation edge (i64 i64)) 2 | (relation path (i64 i64)) 3 | 4 | 5 | (ruleset myrules1) 6 | (rule ((edge x y)) 7 | ((path x y)) 8 | :ruleset myrules1) 9 | (ruleset myrules2) 10 | (rule ((path x y) (edge y z)) 11 | ((path x z)) 12 | :ruleset myrules2) 13 | 14 | (unstable-combined-ruleset myrules-combined 15 | myrules1 myrules2) 16 | 17 | 18 | (edge 0 1) 19 | (edge 1 2) 20 | (edge 2 3) 21 | (edge 2 4) 22 | 23 | (run-schedule 24 | (repeat 3 myrules-combined)) 25 | 26 | 27 | (check (path 0 1)) 28 | (check (path 0 2)) 29 | (check (path 0 3)) 30 | (check (path 0 4)) 31 | (check (path 1 2)) 32 | (check (path 1 3)) 33 | (check (path 1 4)) 34 | -------------------------------------------------------------------------------- /tests/towers-of-hanoi.egg: -------------------------------------------------------------------------------- 1 | (datatype Stack 2 | (Empty) 3 | (Cons i64 Stack)) 4 | 5 | (function Config (Stack Stack Stack) i64 :merge (min old new)) 6 | 7 | ;; move from first stack 8 | (rule ((= len (Config (Cons x a) b c))) 9 | ((set (Config a (Cons x b) c) (+ len 1)) 10 | (set (Config a b (Cons x c)) (+ len 1)))) 11 | 12 | ;; move from second stack 13 | (rule ((= len (Config a (Cons x b) c))) 14 | ((set (Config (Cons x a) b c) (+ len 1)) 15 | (set (Config a b (Cons x c)) (+ len 1)))) 16 | 17 | ;; move from third stack 18 | (rule ((= len (Config a b (Cons x c)))) 19 | ((set (Config (Cons x a) b c) (+ len 1)) 20 | (set (Config a (Cons x b) c) (+ len 1)))) 21 | 22 | (let e (Empty)) 23 | 24 | 25 | ;; initial state [123 _ _] with path "length" 0 26 | (set (Config (Cons 1 (Cons 2 (Cons 3 e))) e e) 0) 27 | 28 | ;; find all reachable states 29 | (run 1000000) 30 | 31 | ;; print first 10 tuples 32 | (print-function Config 10) 33 | (print-size Config) 34 | 35 | ;; how to long to move to state [_ _ 123] 36 | (query-extract (Config e e (Cons 1 (Cons 2 (Cons 3 e))))) 37 | 38 | ;; actually do the assertion 39 | (check (= 5 (Config e e (Cons 1 (Cons 2 (Cons 3 e)))))) -------------------------------------------------------------------------------- /tests/tricky-type-checking.egg: -------------------------------------------------------------------------------- 1 | ;;;;;;;;;;;;;;;;;; 2 | ;; From repro-constraineq 3 | 4 | ;; repro-constraineq 5 | (push) 6 | (rule ((= x 1) (= y x) (= z y)) ()) 7 | (run 1) 8 | (pop) 9 | 10 | ;; repro-constraineq2 11 | (push) 12 | (rule ((= x 1) (= y x)) ()) 13 | (run 1) 14 | (pop) 15 | 16 | ;; repro-constraineq3 17 | (push) 18 | (relation f (i64)) 19 | 20 | (rule ((= x 1) 21 | (= x 2)) 22 | ((f x))) 23 | 24 | (run 1) 25 | (print-function f 10) 26 | (pop) 27 | 28 | ;;;;;;;;;;;;;;;;;; 29 | ;; Atoms need to be order-insensitive 30 | 31 | ;; Issue #196 32 | (push) 33 | (relation R (i64)) 34 | 35 | (rule 36 | ((= x y) 37 | (= y 1)) 38 | ((R x))) 39 | (run 1) 40 | (check (R 1)) 41 | (pop) 42 | 43 | (push) 44 | (relation R (i64)) 45 | 46 | (rule 47 | ((= x (+ y 1)) 48 | (= y 1)) 49 | ((R x))) 50 | (run 1) 51 | (check (R 2)) 52 | (pop) 53 | 54 | ;; Issue #80 55 | (push) 56 | (datatype TYPE) 57 | (datatype TERM) 58 | (constructor type (TERM) TYPE) 59 | (constructor Ob () TYPE) 60 | (constructor Hom (TERM TERM) TYPE) 61 | 62 | (constructor id (TERM) TERM) 63 | (rule ((type (id A))) 64 | ((type A))) 65 | (rewrite (type (id A)) 66 | (Hom A A) 67 | :when ((= (type A) (Ob)))) 68 | 69 | (constructor compose (TERM TERM) TERM) 70 | (rule ((type (compose f g))) 71 | ((type f) 72 | (type g))) 73 | (rewrite (type (compose f g)) 74 | (Hom A C) 75 | :when ((= (type f) (Hom A B)) 76 | (= (type g) (Hom B C)))) 77 | 78 | (birewrite (compose (compose f g) h) 79 | (compose f (compose g h)) 80 | :when ((= (type A) (Ob)) 81 | (= (type B) (Ob)) 82 | (= (type C) (Ob)) 83 | (= (type D) (Ob)) 84 | (= (type f) (Hom A B)) 85 | (= (type g) (Hom B C)) 86 | (= (type h) (Hom C D)))) 87 | (birewrite (compose f (id B)) f 88 | :when ((= (type A) (Ob)) 89 | (= (type B) (Ob)) 90 | (= (type f) (Hom A B)))) 91 | (birewrite (compose (id A) f) f 92 | :when ((= (type A) (Ob)) 93 | (= (type B) (Ob)) 94 | (= (type f) (Hom A B)))) 95 | 96 | (constructor AConst () TERM) 97 | (let A (AConst)) 98 | (constructor BConst () TERM) 99 | (let B (BConst)) 100 | (constructor fConst () TERM) 101 | (let f (fConst)) 102 | (constructor gConst () TERM) 103 | (let g (gConst)) 104 | (let fog (compose g f)) 105 | (union (type f) (Hom A B)) 106 | (union (type g) (Hom B A)) 107 | (union (type A) (Ob)) 108 | (union (type B) (Ob)) 109 | (type fog) 110 | (run 10) 111 | (print-function type 10) 112 | (check (= (type f) 113 | (type (compose (id A) 114 | (compose f (id B)))))) 115 | (check (= (type fog) 116 | (Hom B B))) 117 | (pop) 118 | 119 | 120 | ;;;;;;;;;;;;;;;;;; 121 | ;; Finding the right type in case of container types and primitives 122 | 123 | ;; Issue #113 124 | 125 | (push) 126 | (sort MyMap (Map i64 String)) 127 | (sort MyMap1 (Map i64 i64)) 128 | 129 | (let my_map1 (map-insert (map-empty) 1 "one")) 130 | (pop) 131 | 132 | (push) 133 | (sort MyMap1 (Map i64 i64)) 134 | (sort MyMap (Map i64 String)) 135 | 136 | (let my_map1 (map-insert (map-empty) 1 "one")) 137 | (pop) 138 | 139 | -------------------------------------------------------------------------------- /tests/type-constraints-tests.egg: -------------------------------------------------------------------------------- 1 | (datatype Operand) 2 | (sort VecOperandBase (Vec Operand)) 3 | (datatype VecOperand (VO VecOperandBase)) 4 | (sort VecVecOperandBase (Vec VecOperand)) 5 | 6 | (rule 7 | ((= v1 (vec-of)) 8 | (= v2 (VO v1)) 9 | (= v3 (vec-of v2))) 10 | ()) 11 | -------------------------------------------------------------------------------- /tests/typecheck.egg: -------------------------------------------------------------------------------- 1 | ; type checking for simply typed lambda calculus 2 | 3 | (datatype Type 4 | (TArr Type Type) ; t1 -> t2 5 | ) 6 | (constructor TUnitConst () Type) 7 | (let TUnit (TUnitConst)) 8 | 9 | (datatype Expr 10 | (Lam String Type Expr) ; lam x : t . e 11 | (App Expr Expr) 12 | (Var String) 13 | ) 14 | (constructor MyUnitConst () Expr) 15 | (let MyUnit (MyUnitConst)) 16 | 17 | (datatype Ctx 18 | (Cons String Type Ctx) 19 | ) 20 | (constructor NilConst () Ctx) 21 | (let Nil (NilConst)) 22 | 23 | ; ctx |- expr : type 24 | (constructor typeof (Ctx Expr) Type) 25 | 26 | ; ctx |- () : unit 27 | (rewrite (typeof ctx MyUnit) TUnit) 28 | 29 | ; ctx; x: t |- x : t 30 | (rewrite (typeof (Cons x t ctx) (Var x)) t) 31 | 32 | ; ctx |- f :- t1 -> t2 33 | ; ctx |- e : t1 34 | ; ----------------- 35 | ; ctx |- f e : t2 36 | 37 | (rule ( 38 | (= (typeof ctx (App f e)) t2) 39 | )( 40 | (typeof ctx f) 41 | (typeof ctx e) 42 | )) 43 | 44 | (rule ( 45 | (= (typeof ctx (App f e)) t1) 46 | (= (typeof ctx f) (TArr (typeof ctx e) t2)) 47 | )( 48 | (union t1 t2) 49 | )) 50 | 51 | ; ctx |- x : t 52 | ; ------------------ y != x 53 | ; ctx; y: t |- x : t 54 | 55 | (rewrite (typeof (Cons y ty ctx) (Var x)) 56 | (typeof ctx (Var x)) 57 | :when ((!= x y))) 58 | 59 | ; ctx; x: t1 |- e : t2 60 | ; ------------------------------ 61 | ; ctx |- lam x: t1. e : t1 -> t2 62 | 63 | ; rhs of rewrite creates demand 64 | (rewrite (typeof ctx (Lam x t1 e)) 65 | (TArr t1 (typeof (Cons x t1 ctx) e))) 66 | 67 | ; TEST 68 | ; ---- 69 | 70 | ; lam x : unit, f : unit -> unit . f x 71 | (let e 72 | (Lam "x" TUnit 73 | (Lam "f" (TArr TUnit TUnit) 74 | (App (Var "f") (Var "x"))))) 75 | 76 | ; lam x : unit . x 77 | (let id (Lam "x" TUnit (Var "x"))) 78 | (let t-id (typeof Nil id)) 79 | 80 | ; (e () id) = () 81 | (let app-unit-id (App (App e MyUnit) id)) 82 | (let t-app (typeof Nil app-unit-id)) 83 | 84 | (let free (Lam "x" TUnit (Var "y"))) 85 | (let t-free-ill (typeof Nil free)) 86 | (let t-free-1 (typeof (Cons "y" TUnit Nil) free)) 87 | (let t-free-2 (typeof (Cons "y" (TArr (TArr TUnit TUnit) TUnit) Nil) free)) 88 | 89 | (run 15) 90 | 91 | (query-extract t-id) 92 | (check (= t-id (TArr TUnit TUnit))) 93 | 94 | (query-extract t-app) 95 | (check (= t-app TUnit)) 96 | 97 | (query-extract t-free-1) 98 | (check (= t-free-1 (TArr TUnit TUnit))) 99 | (query-extract t-free-2) 100 | (check (= t-free-2 (TArr TUnit (TArr (TArr TUnit TUnit) TUnit)))) 101 | ; this will err 102 | ; (query-extract t-free-ill) 103 | -------------------------------------------------------------------------------- /tests/unify.egg: -------------------------------------------------------------------------------- 1 | (datatype Expr 2 | (Mul Expr Expr) 3 | (Var String) 4 | (Lit i64) 5 | ) 6 | 7 | ; Assume injectivity of Mul for unification 8 | (rule ((= (Mul a b) (Mul c d))) 9 | ((union a c) 10 | (union b d))) 11 | 12 | ;; (relation False (i64)) 13 | ; If any Literal make equal to something it can't be, false is derived 14 | ;(rule ((= (Lit i) (Lit j)) (!= i j)) 15 | ; ((False 0))) 16 | (rule ((= (Lit i) (Mul a b))) 17 | ((panic "Literal cannot be equal to a product"))) 18 | 19 | (union (Mul (Var "a") (Var "a")) 20 | (Mul (Lit 1) (Lit 2))) 21 | 22 | 23 | (run 3) 24 | (check (= (Var "a") (Lit 1))) 25 | (check (= (Lit 2) (Lit 1))) 26 | ; (check (False 0)) ;; this should fail because we don't want prove false -------------------------------------------------------------------------------- /tests/unstable-fn.egg: -------------------------------------------------------------------------------- 1 | (datatype Math 2 | (Num i64) 3 | (Var String) 4 | (Add Math Math) 5 | (Mul Math Math)) 6 | 7 | (rewrite (Mul (Num x) (Num y)) (Num (* x y))) 8 | 9 | (datatype MathList 10 | (Nil) 11 | (Cons Math MathList)) 12 | 13 | (sort MathFn (UnstableFn (Math) Math)) 14 | 15 | 16 | (constructor square (Math) Math) 17 | (rewrite (square x) (Mul x x)) 18 | 19 | (let square-fn (unstable-fn "square" )) 20 | 21 | ;; test that we can call a function 22 | (let squared-3 (unstable-app square-fn (Num 3))) 23 | (check (= squared-3 (square (Num 3)))) 24 | 25 | ;; test that we can apply a function to a list 26 | 27 | (constructor list-map-math (MathList MathFn) MathList) 28 | (rewrite (list-map-math (Nil) fn) (Nil)) 29 | (rewrite (list-map-math (Cons x xs) fn) (Cons (unstable-app fn x) (list-map-math xs fn))) 30 | 31 | (let x (Cons (Num 1) (Cons (Num 2) (Cons (Num 3) (Nil))))) 32 | (let squared-x (list-map-math x square-fn)) 33 | (run-schedule (saturate (run))) 34 | (check (= squared-x (Cons (Num 1) (Cons (Num 4) (Cons (Num 9) (Nil)))))) 35 | 36 | ;; Test that we can partially apply a function in a rewrite rule 37 | 38 | (constructor list-multiply-by (MathList Math) MathList) 39 | (rewrite (list-multiply-by l i) (list-map-math l (unstable-fn "Mul" i))) 40 | 41 | (let doubled-x (list-multiply-by x (Num 2))) 42 | (run-schedule (saturate (run))) 43 | (check (= doubled-x (Cons (Num 2) (Cons (Num 4) (Cons (Num 6) (Nil)))))) 44 | 45 | ;; Test we can define a higher order compose function 46 | 47 | (constructor composed-math (MathFn MathFn Math) Math) 48 | (rewrite (composed-math f g v) (unstable-app f (unstable-app g v))) 49 | 50 | (let square-of-double (unstable-fn "composed-math" square-fn (unstable-fn "Mul" (Num 2)))) 51 | 52 | (let squared-doubled-x (list-map-math x square-of-double)) 53 | (run-schedule (saturate (run))) 54 | (check (= squared-doubled-x (Cons (Num 4) (Cons (Num 16) (Cons (Num 36) (Nil)))))) 55 | 56 | 57 | ;; See that it supports primitive values as well 58 | (sort i64Fun (UnstableFn (i64) i64)) 59 | 60 | (constructor composed-i64-math (MathFn i64Fun i64) Math) 61 | (rewrite (composed-i64-math f g v) (unstable-app f (Num (unstable-app g v)))) 62 | 63 | (let res (composed-i64-math square-fn (unstable-fn "*" 2) 4)) 64 | (run-schedule (saturate (run))) 65 | (check (= res (Num 64))) 66 | 67 | ;; Verify that function parsing works with a function with no args 68 | (sort TestNullaryFunction (UnstableFn () Math)) 69 | ;; Verify that we know the type of a function based on the string name 70 | (extract (unstable-fn "square")) 71 | -------------------------------------------------------------------------------- /tests/until.egg: -------------------------------------------------------------------------------- 1 | ; A simple group 2 | (datatype G) 3 | (constructor IConst () G) 4 | (let I (IConst)) 5 | (constructor AConst () G) 6 | (let A (AConst)) 7 | (constructor BConst () G) 8 | (let B (BConst)) 9 | 10 | (constructor g* (G G) G) 11 | (constructor inv (G) G) 12 | (birewrite (g* (g* a b) c) (g* a (g* b c))) ; assoc 13 | (rewrite (g* I a) a) ; idl 14 | (rewrite (g* a I) a) ; idr 15 | 16 | ; A is cyclic of period 4 17 | (rewrite (g* A (g* A (g* A A))) I) 18 | 19 | (let A2 (g* A A)) 20 | (let A4 (g* A2 A2)) 21 | (let A8 (g* A4 A4)) 22 | 23 | ; non terminating rule 24 | (relation allgs (G)) 25 | (rule ((allgs x)) ((allgs (g* B x)))) 26 | (allgs A) 27 | 28 | ; if you remove :until, this will take a very long time 29 | (run 10000 :until (= A8 I)) 30 | (check (= A8 I)) 31 | (check (!= B A)) 32 | (check (!= I A)) 33 | ; If you need multiple stop conditions, consider using a (relation relation stop (unit)) 34 | ; With rules filling it in with different stop conditions of interest. 35 | -------------------------------------------------------------------------------- /tests/vec.egg: -------------------------------------------------------------------------------- 1 | (sort IVec (Vec i64)) 2 | 3 | ; Test vec-of 4 | (check (= (vec-of 1 2) (vec-push (vec-push (vec-empty) 1) 2))) 5 | 6 | ; Test vec-append 7 | (check (= (vec-append (vec-of 1 2) (vec-of 3 4)) (vec-of 1 2 3 4))) 8 | 9 | ; Test vec-pop 10 | (check (= (vec-pop (vec-of 1 2 3)) (vec-of 1 2))) 11 | 12 | ; Test vec-not-contains 13 | (check (vec-not-contains (vec-of 1 2 3) 4)) 14 | 15 | ; Test vec-contains 16 | (check (vec-contains (vec-of 1 2 3) 2)) 17 | 18 | ; Test length 19 | (check (= (vec-length (vec-of 1 2 3)) 3)) 20 | 21 | ; Test vec-get 22 | (check (= (vec-get (vec-of 1 2 3) 1) 2)) 23 | 24 | ; Test vec-set 25 | (check (= (vec-set (vec-of 1 2 3) 1 4) (vec-of 1 4 3))) 26 | -------------------------------------------------------------------------------- /web-demo/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition = "2021" 3 | name = "web-demo" 4 | version = "0.4.0" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies.egglog] 10 | default-features = false 11 | features = ["serde", "graphviz", "wasm-bindgen"] 12 | path = ".." 13 | 14 | [dependencies] 15 | wee_alloc = "0.4.5" 16 | 17 | log = "0.4.19" 18 | wasm-logger = "0.2" 19 | serde_json = "1.0" 20 | console_error_panic_hook = "0.1.7" 21 | js-sys = "0.3" 22 | wasm-bindgen = "0.2" 23 | web-sys = { version = "0.3.64", features = [ 24 | # "Blob", 25 | # "BlobPropertyBag", 26 | # "console", 27 | "MessageEvent", # "Url", 28 | # "Window", 29 | # "Location", 30 | # "Document", 31 | # "HtmlElement", 32 | # "Node", 33 | # "Text", 34 | "Worker", 35 | "DedicatedWorkerGlobalScope", 36 | ] } 37 | -------------------------------------------------------------------------------- /web-demo/examples.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import json 4 | import sys 5 | from pathlib import Path 6 | 7 | if len(sys.argv) <= 1: 8 | print("ERROR: give some files as input") 9 | sys.exit(1) 10 | 11 | files = sorted(sys.argv[1:]) 12 | 13 | result = {} 14 | for filename in files: 15 | with open(filename) as f: 16 | name = Path(filename).stem 17 | result[name] = f.read() 18 | 19 | json.dump(result, sys.stdout, indent=2) -------------------------------------------------------------------------------- /web-demo/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::unused_unit)] // weird clippy bug with wasm-bindgen 2 | use egglog::SerializeConfig; 3 | use log::{Level, Log, Metadata, Record}; 4 | use wasm_bindgen::prelude::*; 5 | use web_sys::console; 6 | 7 | #[global_allocator] 8 | static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; 9 | 10 | #[wasm_bindgen(getter_with_clone)] 11 | pub struct Result { 12 | pub text: String, 13 | pub dot: String, 14 | pub json: String, 15 | } 16 | 17 | #[wasm_bindgen] 18 | pub fn run_program(input: &str) -> Result { 19 | let mut egraph = egglog::EGraph::default(); 20 | match egraph.parse_and_run_program(Some("web-demo.egg".into()), input) { 21 | Ok(outputs) => { 22 | let serialized = egraph.serialize(SerializeConfig { 23 | max_functions: Some(40), 24 | max_calls_per_function: Some(40), 25 | ..Default::default() 26 | }); 27 | let json = serde_json::to_string(&serialized).unwrap(); 28 | Result { 29 | text: outputs.join("\n"), 30 | dot: serialized.to_dot(), 31 | json, 32 | } 33 | } 34 | Err(e) => Result { 35 | text: e.to_string(), 36 | dot: "".to_string(), 37 | json: "{}".to_string(), 38 | }, 39 | } 40 | } 41 | 42 | #[wasm_bindgen(start)] 43 | pub fn start() { 44 | init(); 45 | console_error_panic_hook::set_once(); 46 | } 47 | 48 | /// The log styles 49 | struct Style { 50 | lvl_trace: String, 51 | lvl_debug: String, 52 | lvl_info: String, 53 | lvl_warn: String, 54 | lvl_error: String, 55 | } 56 | 57 | impl Style { 58 | fn new() -> Self { 59 | let base = String::from("color: white; padding: 0 3px; background:"); 60 | Style { 61 | lvl_trace: format!("{} gray;", base), 62 | lvl_debug: format!("{} blue;", base), 63 | lvl_info: format!("{} green;", base), 64 | lvl_warn: format!("{} orange;", base), 65 | lvl_error: format!("{} darkred;", base), 66 | } 67 | } 68 | 69 | fn get_lvl_style(&self, lvl: Level) -> &str { 70 | match lvl { 71 | Level::Trace => &self.lvl_trace, 72 | Level::Debug => &self.lvl_debug, 73 | Level::Info => &self.lvl_info, 74 | Level::Warn => &self.lvl_warn, 75 | Level::Error => &self.lvl_error, 76 | } 77 | } 78 | } 79 | 80 | // This is inspired by wasm_logger 81 | struct WebDemoLogger { 82 | style: Style, 83 | } 84 | 85 | impl Log for WebDemoLogger { 86 | fn enabled(&self, _metadata: &Metadata<'_>) -> bool { 87 | true 88 | } 89 | 90 | fn log(&self, record: &Record<'_>) { 91 | if self.enabled(record.metadata()) { 92 | let style = &self.style; 93 | let s = format!( 94 | "{}\n{}\n", 95 | style.get_lvl_style(record.level()), 96 | record.level(), 97 | record.args(), 98 | ); 99 | log(record.level().as_str(), &s); 100 | } 101 | } 102 | 103 | fn flush(&self) {} 104 | } 105 | 106 | #[wasm_bindgen] 107 | extern "C" { 108 | #[wasm_bindgen] 109 | fn log(level: &str, s: &str); 110 | } 111 | 112 | pub fn init() { 113 | let max_level = Level::Debug; 114 | let wl = WebDemoLogger { 115 | style: Style::new(), 116 | }; 117 | 118 | match log::set_boxed_logger(Box::new(wl)) { 119 | Ok(_) => log::set_max_level(max_level.to_level_filter()), 120 | Err(e) => console::error_1(&JsValue::from(e.to_string())), 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /web-demo/static/base64.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Convert between Uint8Array and Base64 strings 3 | * Allows for any encoded JS string to be converted (as opposed to atob()/btoa() which only supports latin1) 4 | * 5 | * Original implementation by madmurphy on MDN 6 | * @see https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding#Solution_1_–_JavaScript%27s_UTF-16_%3E_base64 7 | */ 8 | 9 | function b64ToUint6(nChr) { 10 | return nChr > 64 && nChr < 91 11 | ? nChr - 65 12 | : nChr > 96 && nChr < 123 13 | ? nChr - 71 14 | : nChr > 47 && nChr < 58 15 | ? nChr + 4 16 | : nChr === 43 17 | ? 62 18 | : nChr === 47 19 | ? 63 20 | : 0 21 | } 22 | 23 | export function decodeToArray(base64string, blockSize) { 24 | var sB64Enc = base64string.replace(/[^A-Za-z0-9\+\/]/g, ''), 25 | nInLen = sB64Enc.length, 26 | nOutLen = blockSize 27 | ? Math.ceil(((nInLen * 3 + 1) >>> 2) / blockSize) * blockSize 28 | : (nInLen * 3 + 1) >>> 2, 29 | aBytes = new Uint8Array(nOutLen) 30 | 31 | for ( 32 | var nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; 33 | nInIdx < nInLen; 34 | nInIdx++ 35 | ) { 36 | nMod4 = nInIdx & 3 37 | nUint24 |= b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << (18 - 6 * nMod4) 38 | if (nMod4 === 3 || nInLen - nInIdx === 1) { 39 | for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) { 40 | aBytes[nOutIdx] = (nUint24 >>> ((16 >>> nMod3) & 24)) & 255 41 | } 42 | nUint24 = 0 43 | } 44 | } 45 | 46 | return aBytes 47 | } 48 | 49 | function uint6ToB64(nUint6) { 50 | return nUint6 < 26 51 | ? nUint6 + 65 52 | : nUint6 < 52 53 | ? nUint6 + 71 54 | : nUint6 < 62 55 | ? nUint6 - 4 56 | : nUint6 === 62 57 | ? 43 58 | : nUint6 === 63 59 | ? 47 60 | : 65 61 | } 62 | 63 | export function encodeFromArray(bytes) { 64 | var eqLen = (3 - (bytes.length % 3)) % 3, 65 | sB64Enc = '' 66 | 67 | for ( 68 | var nMod3, nLen = bytes.length, nUint24 = 0, nIdx = 0; 69 | nIdx < nLen; 70 | nIdx++ 71 | ) { 72 | nMod3 = nIdx % 3 73 | /* Uncomment the following line in order to split the output in lines 76-character long: */ 74 | /* 75 | if (nIdx > 0 && (nIdx * 4 / 3) % 76 === 0) { sB64Enc += "\r\n"; } 76 | */ 77 | nUint24 |= bytes[nIdx] << ((16 >>> nMod3) & 24) 78 | if (nMod3 === 2 || bytes.length - nIdx === 1) { 79 | sB64Enc += String.fromCharCode( 80 | uint6ToB64((nUint24 >>> 18) & 63), 81 | uint6ToB64((nUint24 >>> 12) & 63), 82 | uint6ToB64((nUint24 >>> 6) & 63), 83 | uint6ToB64(nUint24 & 63) 84 | ) 85 | nUint24 = 0 86 | } 87 | } 88 | 89 | return eqLen === 0 90 | ? sB64Enc 91 | : sB64Enc.substring(0, sB64Enc.length - eqLen) + (eqLen === 1 ? '=' : '==') 92 | } 93 | 94 | /** 95 | * URL-safe variants of Base64 conversion functions (aka base64url) 96 | * @see https://tools.ietf.org/html/rfc4648#section-5 97 | */ 98 | 99 | export function encodeFromArrayUrlSafe(bytes) { 100 | return encodeURIComponent( 101 | encodeFromArray(bytes) 102 | .replace(/\+/g, '-') 103 | .replace(/\//g, '_') 104 | ) 105 | } 106 | 107 | export function decodeToArrayUrlSafe(base64string) { 108 | return decodeToArray( 109 | decodeURIComponent(base64string) 110 | .replace(/-/g, '+') 111 | .replace(/_/g, '/') 112 | ) 113 | } 114 | -------------------------------------------------------------------------------- /web-demo/static/worker.js: -------------------------------------------------------------------------------- 1 | importScripts("web_demo.js") 2 | console.log("I'm in the worker") 3 | 4 | let { run_program } = wasm_bindgen; 5 | async function work() { 6 | await wasm_bindgen("web_demo_bg.wasm"); 7 | 8 | // Set callback to handle messages passed to the worker. 9 | self.onmessage = async event => { 10 | try { 11 | logbuffer = []; 12 | let result = run_program(event.data); 13 | console.log("Got result from worker", result); 14 | // Can't send the result directly, since it contains a reference to the 15 | // wasm memory. Instead, we send the dot and text separately. 16 | self.postMessage({ dot: result.dot, text: result.text, log: logbuffer, json: result.json }); 17 | } catch (error) { 18 | console.log(error); 19 | self.postMessage({ dot: "", text: "Something panicked! Check the console logs...", log: logbuffer, json: "{}" }); 20 | } 21 | }; 22 | } 23 | 24 | logbuffer = []; 25 | function log(level, str) { 26 | logbuffer.push([level, str]); 27 | } 28 | 29 | work() 30 | --------------------------------------------------------------------------------