Rust adapter: parses source with syn and lowers the
AST into the language-agnostic [cccc_core::ir].
This is a pure library — it depends only on cccc-core and syn, with no
CLI machinery, so embedders pay nothing for clap/ignore/rayon. The cccc-rs
binary lives in the separate cccc-rs-cli crate, which combines
[analyze_source]/[DEFAULT_EXTS] with the shared cccc-cli runner.
This crate contains no scoring logic — it only recognizes the constructs
the engine cares about (functions/methods/closures, if/else, match,
loops, labelled jumps, &&/|| sequences, calls) and emits the matching IR
nodes. All complexity rules live in [cccc_core::engine].
Why a Visit-driven builder
Lowering is driven by syn's [Visit] trait. Its default visit_* methods
traverse the entire AST; we override only the nodes that produce IR, so a
nested function or logical operator appearing in any expression position is
still reached — we never have to enumerate every node kind by hand. The IR
tree is assembled with a stack of "collectors": [Builder::collect] pushes a
fresh child vector, runs a sub-traversal, and pops the nodes it gathered.
Rust-to-IR mapping notes
fn/implmethod / trait default method / closure → [Node::Function].if/else if/else→ [Node::Branch] (chainingelse ifas a nestedBranchso it scores flat).if let/while letare just the same nodes.for/while/loop→ [Node::Loop].match→ [Node::Switch]; a_(or bare binding) arm is thedefault. An arm guard (pat if cond) is visited inside the case body.- labelled
break 'a/continue 'a→ [Node::Jump] (labeled: true). &&/||runs → folded [Node::Logical] (one node per like-operator run).- calls (
f(..),obj.m(..)) → [Node::Call] for recursion detection.
Rust has no ternary (if is an expression instead) and no try/catch
(errors propagate via ?), so no Conditional/Catch nodes are emitted.