Formulon is a headless, Excel-compatible calculation engine — a C++17 core that defaults to the Windows Excel 365 (ja-JP) behavior profile, with every known divergence explicitly tracked against Excel oracle data. The same engine is packaged for the browser (WebAssembly), for Python, and for native command-line use, so a workbook recalculates to the same values wherever it runs.
No Excel installation, no Microsoft runtime, no COM automation required. The WASM build runs in browsers, Node, and Python through wasmtime; native CLI packages currently ship for darwin-arm64, linux-x64, and linux-arm64.
- Strict oracle, not aspirational compatibility. The runtime default is
win-365-ja_JP, and profile-specific oracle suites pin observed Excel behavior. The primary checked-in oracle remains Mac Excel 365 (ja-JP), while Windows Excel 365 (ja-JP) is verified through variant goldens. Outputs are checked for bit-level parity against golden data regenerated from the real product; every accepted divergence (transcendental ulp drift, volatile-function snapshots, Excel quirks where Formulon deliberately keeps a saner answer) is recorded case-by-case intests/divergence.yamlwith a reason and the last verified Excel build. - One C++ core, identical results everywhere. JS-only competitors re-run the logic in the browser and the logic on the server. Formulon ships one engine to every surface (WASM, Python, CLI) so there is no second implementation to drift.
- Strict WASM size budget. Target 1.65 MB uncompressed / 530 KB Brotli, hard ceiling 1.9 MB / 600 KB Brotli. The budget is enforced in CI, not aspirational; features ship within the budget or do not ship.
- Small dependency set. Engine deps:
miniz(zip/deflate),pugixml(XML + XPath 1.0),PCRE2(Excel-compatible regex forREGEX*),double-conversion(Grisu3 shortest-roundtripdtoa). Linear algebra, UTF-8 handling, and most number coercion are in-tree. - Readable, reviewable code.
Expected<T, Error>error handling, RAII,-fno-exceptions -fno-rtti, Google C++ style.
Anywhere a spreadsheet needs to be computed without booting Excel:
- running
.xlsxworkbooks headlessly in batch jobs or data pipelines, - evaluating Excel-style formulas inside a web application, in the browser,
- embedding calculation into internal tools, bots, or notebooks,
- validating formulas and migrating legacy spreadsheets.
Formulon deliberately does not cover:
| Area | Reason |
|---|---|
| VBA execution | Security. vbaProject.bin is preserved byte-for-byte, never executed. |
Legacy .xls (BIFF8, Excel 97–2003) |
Out of scope for Excel 365 compatibility. |
| Chart / drawing rendering | Belongs to a rendering layer, not the engine. |
| PowerQuery (M) / DAX | Separate engine, separate problem domain. |
| Pivot cache recomputation | Structurally preserved; recomputation is out of scope. |
| Spreadsheet UI | A thin UI integration layer is planned; rendering is yours. |
These are permanent non-goals, not "not yet." The scope is finite on purpose.
| Surface | Name | Notes |
|---|---|---|
| npm | @libraz/formulon |
WASM ESM module, type definitions included. Node 18+, browsers, workers. |
| PyPI | formulon |
Python 3.9+ py3-none-any wheel that bundles formulon_capi.wasm plus a pure-Python wrapper. pip resolves the platform-specific wasmtime runtime. |
| GitHub Releases | formulon-cli-<platform-arch> |
Standalone CLI binaries (eval, recalc, dump) for darwin-arm64, linux-x64, linux-arm64. |
All 522 catalogued Excel functions are recognized, but recognition is not the same as full Excel-compatible execution. The function catalog exposes availability explicitly; make function-status reports the current split.
| Availability | Count | Meaning | Examples |
|---|---|---|---|
| Real implementation | 507 | Evaluates inside the normal calculation engine and is covered by unit and/or oracle tests. | Math, statistics, lookup, text, dynamic arrays |
| Implemented, verification ongoing | 0 | Implemented, but still waiting for additional oracle confirmation on Excel edge cases. | None currently |
| Environment-bound | 2 | Depends on host or workbook state, so a fixed golden cannot fully describe it. | INFO, CELL |
| Unavailable stub | 15 | Requires external services, network I/O, COM providers, or OLAP connections that Formulon does not embed; returns a fixed unavailable error surface. | PY, WEBSERVICE, STOCKHISTORY, IMAGE, RTD, TRANSLATE, DETECTLANGUAGE, COPILOT, CUBE* |
92 oracle categories are defined and regenerated from Mac Excel 365 ja-JP, with Windows Excel 365 ja-JP covered by the win-365-ja_JP variant goldens. Current local verification is 5788/5788 unit tests, 3843 primary oracle passes with 80 documented skips, and the recently refreshed Windows 365 focus set (ENCODEURL, FILTERXML, GROUPBY, PIVOTBY, REGEX*, JIS width probes) at 130 passes with 22 documented skips. The remaining skips are explicit divergences, host-service dependencies, volatile/environment-bound cases, or driver limitations, not silent stubs.
New workbooks use the win-365-ja_JP formula profile by default; callers can switch with the profile-id API (mac-365-ja_JP, win-365-ja_JP). English-locale profiles are intentionally not exposed until matching EN oracle data and verified locale-specific behavior are available. A bytecode compiler and stack-machine VM run in parallel with the tree-walker for parity verification. The OOXML reader/writer round-trips sheets, styles, conditional formatting, comments, hyperlinks, merges, data validations, defined names, tables, and pivot tables; an MS-XLSB reader/writer is in place. Workbook-level operations (sheet add / rename / move, row/column insert / delete with formula rewriting, partial recalc, iterative-solver progress callbacks) are wired through the C ABI and exposed in the WASM, Python, and CLI surfaces.
Feedback, issue reports, and oracle divergence reports are very welcome.
The fastest way to help right now is to donate Excel oracle data from your locale. If you have Excel 365 in any locale beyond Mac ja-JP, one command (make oracle-contribute) drives Excel, captures goldens, and walks you through the PR. See CONTRIBUTING.md for the full flow and the rationale for why this is community-driven.