Calibre.FormulaEngine 0.4.0
Calibre.FormulaEngine
Domain-neutral formula calculation engine as a pure .NET library (net10.0, zero runtime dependencies). It knows nothing about salary, leave, loans, or any other domain — consumers (payroll, attendance, lending, accounting) layer their own contracts and content on top.
Pipeline
DSL text → FormulaParser ┐
Excel text → ExcelFormulaParser ┴→ AST (canonical JSON form) → dependency extraction
→ DAG build + topological sort + cycle rejection
→ compile to expression-tree delegates → cached execution (+ calculation trace)
Two authoring surfaces, one AST. FormulaParser reads the engine's own DSL and is frozen.
ExcelFormulaParser reads spreadsheet formula-bar text (dialect excel/1) so a rule pastes in
unchanged: percent literals become PCT, AND/OR become operator chains, ROUNDUP becomes
ROUND's AWAY_FROM_ZERO mode, CEILING(x,1) becomes CEIL. ExcelFormulaPrinter renders any
printable AST back as that text, byte-round-trip guaranteed. Neither surface adds a node kind, an
operator or a function, so canonical JSON and hashes are the same as they always were.
The AST is the canonical artifact: formula text pretty-prints from it, builder UIs emit it, the validator validates it, hosts store and hash it. See Calibre.FormulaEngine.Serialization.AstJsonSerializer for the canonical JSON contract and AstHash for content hashing.
Design laws (identity of this package)
- Pure — no database, no clock, no network, no file or config I/O. The host hydrates an
EvaluationContext(values, quantities, policies, rates, slab tables, dates, resolvers); the engine only computes. Same inputs + same versions = same outputs, forever. - Zero persistence — not even repository interfaces. Hosts own storage.
- Zero domain concepts — no "employee", no "salary". Ever.
- Decimal arithmetic only, explicit rounding at defined points.
- Not Turing-complete — no loops, no mutation, no state. Formulas express math over resolved data; imperative data resolution is the host's job.
- Strict SemVer — the public API surface and the canonical JSON form are contracts; breaking either is a major version bump.
Build
dotnet build Calibre.FormulaEngine.slnx
dotnet test Calibre.FormulaEngine.slnx
dotnet pack src/Calibre.FormulaEngine -c Release -o artifacts
Status
| Stage | State |
|---|---|
| AST node model + canonical JSON + hashing | ✅ (released 0.1.0) |
| Evaluation context object model | ✅ (released 0.1.0) |
| Parser (DSL → AST) | ✅ |
| Dependency extractor, DAG builder, cycle detection | ✅ |
| Expression-tree compiler + delegate cache | ✅ |
| Validator (schema/type checks, percent discipline, lint hooks) + test-vector runner | ✅ |
Excel surface excel/1 (parser + printer), ROUND mode AWAY_FROM_ZERO, context function allow-list (FE015) |
✅ (0.3.0) |
| Executable workbook projection with resolved context, dynamic external expressions and slab lowering | ✅ (0.4.0) |
The full pipeline is implemented. Formula contexts are host-declared: ValidationSchema.Functions
is an allow-list of function names, and a call outside it is FE015 — the engine never names a
surface or a domain.
Executable workbook export
Export.ExcelFormulaProjector.Project(boundAst, context, externalExpression) produces an export
AST for ExcelFormulaPrinter. It keeps references live, resolves injected policy/rate/quantity/date
reads, lowers lookup and marginal slabs, and asks the host to map external reads to expressions.
It never freezes an external calculation result. The host still owns workbook input cells, names,
materialization boundaries, totals and any reverse calculation.
The projection preserves IF and logical short-circuit behavior and slab gaps as errors. Context
names and rounding modes must resolve to literals; unsupported dynamic names fail explicitly.
This changes neither stored ASTs nor the excel/1 editing round-trip contract. Excel uses binary
numeric precision, so a consuming application must check its exported materialized results and
edits in Excel against the authoritative decimal calculation at its supported numeric ranges.
For a host's bounded calculation program, ExcelFormulaPrinter.PrintWorkbook is a separate native
workbook target. Its fixed function surface includes LET/LAMBDA, HSTACK/INDEX and REDUCE/SEQUENCE.
It preserves lexical bindings when mapping free references to workbook cells/ranges. Declared
underscore-prefixed LAMBDAs must be ordered and acyclic; unknown or external-data functions are
refused. storage: true writes OOXML future-function and lexical-parameter prefixes. Target trees
are export artifacts: the source compiler/parser and excel/1 surface still reject these functions.
Use functionName to assign declared functions unique workbook-global names when compiling several
programs into one file. It maps declared calls and function-valued references structurally, preserves
lexical shadowing, and refuses invalid mapped identifiers. Native Excel requires qualification for
dependencies between sheet-local named LAMBDAs; global names avoid that scope ambiguity.
No packages depend on Calibre.FormulaEngine.
.NET 10.0
- No dependencies.