Plan: extract blr::quote module; slim df to its function API
Status: complete (2026-07-09; deviations noted inline below)
Date: 2026-07-09
Context / current state
Today the quote "node" type family lives in three places, all under df:
| Location | Content |
|---|---|
stdlib/wit/df.wit | binaryoperator, binary, function, node + fromcsv/filter/show |
stdlib/src/df.rs | bindgen for world df; Host impl; build_node_expr (node → datafusion Expr) |
stdlib/src/df.blr | pub extern type decls for all four + the three extern fns |
The compiler side already has the quote concept separated:lang/src/runtime/quote.rs registers std::quote::to_nodes (a compile-time
rewrite — see lang/src/compiler/mantle/quote.rs), and its node_type() doc
comment literally says it "mirrors stdlib/wit/df.wit". The guest never imports
quote at runtime: to_nodes(…) is rewritten into an inline node-list literal,
so only std::df appears as a component import (std::X → blr:X/X-interface,lang/src/compiler/mod.rs:371).
Goal: move the four node types into a new blr:quote WIT package / stdlib
module, leaving df with only data-frame + fromcsv/filter/show.
Phase 1 — WIT (stdlib/wit/)
New
quote.wit:package blr:quote { interface quote-interface { variant binaryoperator { addition, concat, division, equal, greater-than, less-than, multiplication, subtraction, } record binary { left: s64, op: binaryoperator, right: s64, } record function { body: s64, param: string, } variant node { binary(binary), function(function), integer(s64), select(string), } } world quote { import blr:quote/quote-interface; export blr:quote/quote-interface; } }The interface must be named
quote-interfacesostd::quote→blr:quote/quote-interfacematchesblr_path_to_component_path.Edit
df.wit: delete the four type defs; adduse blr:quote/quote-interface.{binaryoperator, binary, function, node};
insidedf-interface(needed only becausefilter's signature mentionsnode). Keepdata-frame,fromcsv,filter,showunchanged.Verify bindgen tolerates cross-package
usefrom files in the sharedwit/dir (multiple packages already coexist there —df/fmt/math/sums/std
— so this should be fine;cargo build -p blr-stdlibis the check).
Fallback if it fails: nestquote.witunder adeps/layout and passpath:/deps:to thebindgen!invocations.
Phase 2 — stdlib Rust (stdlib/src/)
- New
quote.rs:wasmtime::component::bindgen!({ world: "quote" })— types only, no host
functions (confirm the generated code compiles with zero fns; likely noHosttrait /add_to_linkeris emitted).- Move
build_node_expr+ its#[cfg(test)]module fromdf.rshere
(importNode/Binaryoperatorfrom the generatedblr::quotepath).
Thedfmodule keeps only its function API.
lib.rs: addpub mod quote;.df.rs: replaceuse crate::df::blr::df::df_interface::{Binaryoperator, Node};with theblr::quotepath (or acrate::quotere-export);filterdelegates tocrate::quote::build_node_expr. Add awith:clause to the df bindgen only
if the cross-package types land somewhere inconvenient — inspect generated
paths first.lang/src/exec.rs: no change expected — the guest imports noblr:quotefunctions (nodes are compile-time), so nothing new to link.
Confirm via the integration tests.
Phase 3 — blr-language module (stdlib/src/*.blr)
- New
quote.blr: the fourpub extern typedecls moved fromdf.blr
(this also makessource(db, "std::quote")resolve if a program imports
the module). df.blr: keep only the threepub extern fndecls.
⚠️filter(frame: DataFrame, predicate: list<node>)now needs a
cross-module type reference, and the grammar only supports unqualifiedTypeExpr::Alias(lang/src/compiler/air/parser.lalrpop:279):- Subtask: add a qualified type form, e.g.
<Identifier> "::" <Identifier> => TypeExpr::Qualified { module, alias },
then resolve it incrust::lowerby looking up type aliases registered
under the imported module's path (imported extern type aliases already
flow into item sources viaitem_source_for_mod,lang/src/compiler/mod.rs:242) and through theexternal_type
conversion. - Fallback (if that is too invasive for v1): leave a temporary
pub extern type node = ...indf.blrwith a TODO — ABI-safe since the
component model matches structurally — and land the grammar work
separately.
- Subtask: add a qualified type form, e.g.
lang/src/runtime/quote.rs: no code change (type shape is identical);
update thenode_type()doc comment to "mirrorsstdlib/wit/quote.wit".QUOTE_OPS = "std::quote"already maps to the right component path.
Phase 4 — Tests & verification
- Existing integration tests must pass unchanged:
df.blr,df_filter.blr
(they onlyimport std::dfand call unqualifiedto_nodes). - New integration test:
import std::quote+import std::dfusingfilter(fromcsv(…), to_nodes(:(…)))(with a.outfile) — exercises the
quote module as an importable, zero-function component module (validates
nucleus emission for a types-only module). - Move the
build_predicate_logical_exprunit test tostdlib/src/quote.rs. - Gate:
cargo fmt,cargo clippy -- -D warnings,cargo test(workspace).
Suggested order
WIT (1) → stdlib Rust (2) → build+test stdlib → grammar subtask + .blr files
(3) → full test suite (4). Each phase leaves the tree compilable; the only
phase with genuine new language work is 3's qualified-type-name subtask, which
has a documented fallback so the rest can land first.
Risks
- WIT cross-package
usein onewit/dir — low risk (multi-package dir
already used); quick build check in Phase 1. world: "quote"with no functions — bindgen output shape unverified;
Phase 2 step 1 may end up as a bindgen against the interface or a types-only
re-export instead.- Qualified type names — the one substantive compiler change (parser +
crustalias resolution +external_typemapping); the fallback keeps the
refactor shippable without it.