Model a spreadsheet as a grid of cells that hold literals or formulas, so every recalculation is exact and testable. Each lesson is one concrete spec with exact addresses and values: A1 maps to column 0 row 0 and AA1 to column 26, "=A1+B1*2" parses to (A1 + (B1 * 2)), SUM(A1:A3) folds a range, the dependency chain A1 to B1 to C1 sorts to a topological order, editing A1 recomputes B1 and C1 but not an unrelated D1, two cells that reference each other are flagged #CIRC! instead of hanging, and a #DIV/0! error propagates to every cell downstream.
Over 39 lessons you build the calculation engine that sits behind a spreadsheet - not a UI, but the library that makes one work: you set cells and formulas, call recalculate, and read back the computed values, all with exact, testable results. A cell is addressed A1-style and holds either a literal (a number, text, or a boolean) or a formula string that starts with `=`.
You start with A1 addressing and a grid of cells, then build a formula pipeline: a tokenizer, a precedence-aware parser that turns `=A1+B1*2` into an abstract syntax tree whose leaves are cell references and ranges, and an evaluator for arithmetic plus a starter function set - SUM, AVERAGE, MIN, MAX, COUNT, and IF. On top of that sits the real heart of a spreadsheet: a dependency graph that records which cells each formula reads, a topological recalculation order computed with Kahn's algorithm so every cell is evaluated after its inputs, incremental recalculation that recomputes only a changed cell's transitive dependents, circular-reference detection that flags a cycle instead of looping forever, and error values (`#DIV/0!`, `#REF!`, `#NAME?`, `#CIRC!`) that propagate to every cell downstream. The capstone recalculates a real sheet, edits an input and asserts exactly which cells recompute, and introduces a cycle that gets flagged rather than hanging.
This is a genuinely working, teaching-grade calculation engine - the same dependency-graph-and-topological-recalc design that VisiCalc, Lotus 1-2-3, and Excel are built on - but it deliberately stops short of a full spreadsheet application: it is a single in-memory sheet with a starter function library, no user interface, no file formats, no cross-sheet or absolute (`$A$1`) references, and no row or column insertion. It is the honest core those products extend with hundreds of functions, persistence, and a grid you can click.
A spreadsheet addresses cells by column letters and row numbers - A1, B7, AA10. Before we can do anything with a cell, we have to turn its column letters into a plain number. Today you convert a column label into a zero-based column index.
Convert a column label like "A", "Z", or "AA" into its zero-based column index.
Every cell in a spreadsheet has an address like A1 or AB10: some column
letters followed by a row number. The letters name the column, and they
count in a peculiar way - A through Z, then AA, AB, … AZ, BA, and so
on. It looks like base-26, but it is not quite: there is no digit that means zero,
so A is 1, Z is 26, and AA is 27 in that one-based counting.
To use columns as array indices we want them zero-based: A is column 0,
Z is column 25, and AA (the 27th column) is index 26. The trick is to fold
the letters as if they were one-based digits (A=1 … Z=26), then subtract one
at the end. That single - 1 is what turns the letter-counting into an index you
can use everywhere else. Tomorrow you build the inverse.
// like base-26, but there is no "zero digit": A..Z are 1..26,// and you subtract 1 at the very end to make it zero-based.func colToIndex(s string) int {n := 0for _, c := range s {n = n*26 + int(c-'A'+1) // fold each letter in}return n - 1}
A genuinely working, teaching-grade calculation engine over a single in-memory sheet - A1 addressing, a precedence-aware formula parser, the SUM/AVERAGE/MIN/MAX/COUNT/IF function set, dependency-graph recalculation via Kahn's algorithm, incremental updates that touch only a changed cell's dependents, circular-reference detection, and error propagation - but it has no string literals inside formulas, no lookup or logical functions beyond IF, and no user interface, file formats, cross-sheet or absolute references, or cell enumeration.
A from-scratch build of a small spreadsheet - parsing formulas, resolving cell references, and recalculating - that mirrors the pipeline this project builds.
The reference for the recalculation order at the heart of this project: repeatedly remove a node with no remaining incoming edges. A leftover node with a nonzero in-degree is exactly a cell caught in a circular reference.
The clearest treatment of tokenizing and parsing with operator precedence. Chapters on scanning and Pratt-style parsing map directly onto the formula parser in Chapter 2.
A spreadsheet's formulas form a DAG - as long as there is no cycle. This is the structure recalculation walks, and the reason a circular reference has to be detected and refused.
VisiCalc's co-inventor on how the first electronic spreadsheet came to be. Useful context for why automatic recalculation - the thing this engine implements - was the whole point.
Essays on the recalculation problem and dependency tracking that a spreadsheet solves - background reading on why naive re-evaluation is wrong and topological order is right.