Skip to content

Egglog AST macros with unquote and unquote splice#947

Draft
oflatt wants to merge 10 commits into
mainfrom
oflatt-egglog-macro-overhaul
Draft

Egglog AST macros with unquote and unquote splice#947
oflatt wants to merge 10 commits into
mainfrom
oflatt-egglog-macro-overhaul

Conversation

@oflatt

@oflatt oflatt commented Jul 13, 2026

Copy link
Copy Markdown
Member

No description provided.

@oflatt oflatt force-pushed the oflatt-egglog-macro-overhaul branch from 9848a4d to e11bc66 Compare July 13, 2026 19:06
oflatt and others added 2 commits July 13, 2026 19:17
Token-tree quasiquotes that route egglog written as Rust tokens through the
parser: `expr!`, `fact!`, `facts!`, `action!`, `actions!`, `command!`,
`egglog!`, `rule!`, `sexp!`, `sexps!`. Adds:

- the `egglog-quote` proc-macro crate,
- `parse.rs` helpers: `atom_to_sexp`, `keyword_to_sexp`, the `ToSexp` trait
  (+ impls for Sexp/&str/String/i64/Expr), `expr_to_sexp`, and an iterative
  `Display for Sexp` (heap work-stack, so deep list chains can't overflow),
- re-exports from the crate root and `prelude`.

The proc-macros replace the old infallible `macro_rules!` `expr!`/`fact!`/
`facts!`/`action!`/`actions!`: they now go through the parser (so `?x`,
`:field`, `...`, `#`-splices, and every registered parser macro work) and
return `Result`. Ported the call sites in tests/benches accordingly.
Upstream's `query`/`rule`/`rust_rule` API is kept; `rule!` is reachable as
`egglog::rule!` but not re-exported into `prelude` (it would collide with the
`rule()` helper fn). `#(expr)` splices unwrap the parens so the generated
`to_sexp(expr, ..)` is `unused_parens`-clean under `-D warnings`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Add a "Writing egglog inline from Rust" section (with a runnable, tested
  example) to the crate-root docs (lib.md).
- Fix pre-existing broken intra-doc links in proofs::proof_format: the private
  `RawProof`/`RawProof::Rule` links become code spans, `Fact` points at
  `crate::ast::Fact`, and the `Propostion` typo is fixed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@codecov-commenter

codecov-commenter commented Jul 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 73.96907% with 202 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.13%. Comparing base (07ae68e) to head (4809dad).

Files with missing lines Patch % Lines
quote-macro/src/lib.rs 72.28% 69 Missing ⚠️
src/ast/parse.rs 25.75% 49 Missing ⚠️
static-macro/src/lib.rs 83.60% 41 Missing ⚠️
src/typechecking.rs 43.24% 21 Missing ⚠️
src/lib.rs 78.57% 15 Missing ⚠️
src/prelude.rs 91.89% 6 Missing ⚠️
egglog-ast/src/tokens.rs 96.66% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #947      +/-   ##
==========================================
- Coverage   86.42%   86.13%   -0.29%     
==========================================
  Files          94       97       +3     
  Lines       29102    29759     +657     
==========================================
+ Hits        25151    25634     +483     
- Misses       3951     4125     +174     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@oflatt oflatt force-pushed the oflatt-egglog-macro-overhaul branch from e11bc66 to fda2a26 Compare July 13, 2026 19:19
@codspeed-hq

codspeed-hq Bot commented Jul 13, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 37 untouched benchmarks
⏩ 227 skipped benchmarks1


Comparing oflatt-egglog-macro-overhaul (f12ccf6) with main (07ae68e)

Open in CodSpeed

Footnotes

  1. 227 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

oflatt and others added 8 commits July 14, 2026 03:17
Give every parsing quasiquote three flavors:
- `x!`            — parse only (optional parser)      -> unresolved AST
- `resolve_x!(eg, …)` — typecheck against the e-graph -> Resolved… (no run)
- `run_x!(eg, …)`     — resolve + execute             -> execution result

Covers expression / query / action(s) / command / program (egglog) / rule.
Returns: `run_egglog!`/`run_command!`/`run_rule!`/`run_action(s)!` ->
`Vec<CommandOutput>`; `run_expr!` -> `(ArcSort, Value)`; `run_query!` ->
`Vec<HashMap<String, Value>>` (matches, with vars derived from the facts so no
explicit `vars` needed). `resolve_*` return the matching `Resolved…` AST.

Naming: since each variant has a distinct name, no parser-vs-e-graph type
dispatch is needed. `run_` returns the *execution result*, not the resolved
AST, so `run` and `resolve` stay distinct.

Hard-renamed `facts!` -> `query!` (and dropped the singular `fact!`): "running
facts" is a query, so `run_query!` returning matches is the natural fit.

Supporting e-graph methods (thin wrappers over existing internals):
`resolve_commands`, `resolve_expr`, `resolve_facts`, `query_all`. Also derived
`Clone` for `Sexp` (needed to build `(rule …)` from spliced parts).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Drop the "no quotes" / "real parser" framing (it implied an alternative the
  reader has no context for); describe the macros in their own terms.
- Trim the lib.md section to a short intro + a small runnable example, and
  point to the `egglog_quote` crate for the full reference.
- Soften jargon ("round-trip" / "splices as identity") in the parse.rs and
  sexp! docs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`rule(egraph, ruleset, facts, actions)` is subsumed by
`run_egglog!(egraph, (rule (<facts>) (<actions>) :ruleset r))` (and `run_rule!`
for the default ruleset) now that the run macros exist — it had no library
callers, only tests/doctests. Removing it also frees the name: `rule!` is now
re-exported into `prelude` alongside `resolve_rule!`/`run_rule!` (it was held
out only to avoid colliding with the fn), so the rule triple is complete and
consistent with the other categories.

Migrated the test/doc call sites to `run_egglog!((rule … :ruleset …))`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Remove `add_sort` / `add_function` / `add_constructor` / `add_relation`:
  0 callers, and each is just `run_egglog!(eg, (sort …))` / `(function …)` /
  `(constructor …)` / `(relation …)` with a `Schema` struct instead of egglog
  syntax. (Two were only ever used by the removed `datatype!`.)
- `add_base_sort` / `add_container_sort` register a Rust sort *type* (no egglog
  text equivalent), so they stay — but drop their redundant explicit
  `span: Span` parameter (every caller passed `span!()`) and use `span!()`
  internally, matching how the rest of the API hides spans.

The remaining Rust-side helpers already fit the new API: `rust_rule` /
`rust_rule_full` / `EGraph::query` take `Facts` (what `query!` produces);
`add_ruleset` / `run_ruleset` are thin typed conveniences used internally by
`query`/`rust_rule`; `exprs` / `sort!` / `vars!` are the low-level builders
that back `rust_rule`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Put the full, user-facing macro guide — the parse / resolve_* / run_* table,
the splice forms, and an example — in `egglog::prelude`, alongside the rest of
the Rust API (custom rules, primitives, sorts). It leads the module now, since
writing egglog inline is the primary way to use egglog from Rust.

- `egglog-quote`'s module doc shrinks to a one-line pointer (it's an
  implementation crate; users read `egglog::prelude`).
- `lib.md` keeps a short intro + example and points at [`prelude`].
- Per-macro docs point to `egglog::prelude` instead of "the module docs".
- Dropped implementation framing ("token-tree", "routes through the parser",
  `ToSexp`/`Sexp` internals) in favor of what the macros let you do.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
egglog_header! now also generates
`pub fn <name>_schema() -> Result<Vec<Command>, egglog::Error>` returning the
header's declarations as commands. A schema can then be declared once (the
header) and used for BOTH compile-time checking (the generated macro) and
runtime emission (the fn), instead of being restated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants