VBA extraction¶
Deterministic parsing of VBA macros is an opt-in extra:
pip install 'xl-marinade[vba]'
It's backed by oletools, a
static-analysis library for Office file formats — nothing in the [vba] path
executes the macro. Without the extra installed, extract still runs normally;
it simply produces no VBA nodes or edges.
What gets captured¶
For every VBA project embedded in the workbook, extraction captures:
- Modules — standard, class, form, and document modules, each with its full source text and a SHA-256 hash of that source.
- Procedures — every Sub, Function, and property accessor, with its
signature, parameters, body, and a hash of its normalized body (so two
procedures that are byte-different but logically identical can be recognized
as such). Event handlers (e.g.
Worksheet_Change) are flagged as such, and conditionally-compiled twins (the#If Win64 ... #Else ...pattern used for 32-bit/64-bit API calls) are both kept rather than one overwriting the other. - The call graph — which procedures call which others, including calls made indirectly through the object model (reading or writing a property).
- Cell references — which ranges, named ranges, and tables each procedure reads from or writes to.
These become nodes and edges in the same graph that cells and bindings live in (see Bindings & the graph), so a procedure that writes to a range shows up as a dependency of that range, right alongside the formulas that also feed it.
Deterministic paste provenance¶
A common macro pattern in cashflow and pricing workbooks copies a "template"
row of formulas into an output block, one row per iteration, as a
paste-as-values operation (PasteSpecial Paste:=xlPasteValues, or the
equivalent .Value = .Value assignment). The destination cells end up holding
constants, not formulas — so the formula graph alone can't trace from the output
block back to the template row it was copied from; that lineage only exists in
the macro.
Extraction recovers it: static analysis of the VBA source recognizes these paste patterns and synthesizes a bridging edge from the output block back to the template row it was copied from, tagged with the name of the procedure responsible. The result is fully deterministic and requires no execution or network access — the same macro source always produces the same bridging edges — so a value that "changes by magic" via a macro stays traceable to the specific procedure that wrote it.
Where LLM enrichment fits¶
Static analysis can't resolve every reference — a range built up from string
concatenation or computed at runtime, for example. The optional --enrich flag
layers an LLM-assisted pass on top to infer those dynamic references. It's
strictly additive to the deterministic VBA extraction above, requires the
separate [llm] extra and an API key, and never runs unless you explicitly ask
for it. See LLM add-on.
Next¶
- Bindings & the graph — the graph VBA nodes and edges join.
- Install — the
[vba]extra alongside[llm]. - LLM add-on — opt-in enrichment for dynamic VBA references.