engine

package
v0.27.9 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 4, 2026 License: MIT Imports: 28 Imported by: 0

Documentation

Overview

Package engine's argument-shaping layer: how a call's operands are reduced to the values a builtin receives — a scalar slot, a flattened range, or the mixed shapes the recurring parameter specs describe.

The clipboard block: how a copied range travels as plain TSV text. ParseBlock is the one definition of how that text becomes a grid again; the paste half of the model lives in paste.go.

The text folds: CONCAT and TEXTJOIN, whose operands flatten from ranges, so a single argument may be a whole column. Split from builtins_text.go for size; they share the byte budget that keeps a fold from building what it would then refuse.

Package engine's regex family: the three REGEX* builtins over Go's RE2 syntax, kept apart from the plain text builtins because a bad pattern is a value error rather than a text operation.

Comparison, where the two languages part company three ways.

This language compares by value and by type: text against text by code point, a boolean as 1 or 0, and a mixed comparison not at all. Excel compares everything, in a total order that puts every number below every text and every text below every boolean, and its text comparison ignores case.

So the same three-operator expression can read differently for three unrelated reasons, and each is announced only where the two orderings actually produce different answers — which is far less often than the shapes suggest. `=TRUE<0` is FALSE under 1/0 folding and FALSE under Excel's ranking; the reasons differ and the answer does not, so there is nothing to tell anybody.

Package engine's compute entry points: the public Compute family, and the rendering that turns a computed value matrix back into a text grid.

Windowed evaluation (spec 016 area 6b): ComputeRows runs the one resolver over a sparse budgeted memo with dependencies read lazily through the block cache. The capability type and its census policy live in windowed.go.

View directives: the `#.` lines that declare what a viewport does with the grid — which rows and columns are hidden, which carry headers, which stay anchored while the rest scrolls (SPECIFICATION §3).

A directive is a projection of the grid, never an input to it: nothing here is reachable from a formula, and deleting every directive leaves the data byte-identical and computing identically. The line is a TSV row, so a key and its value are ordinary fields; only the value is a language, parsed by internal/tsvt against the shared grammar.

Package engine's lazy-dispatch families: the calls whose arguments are NOT evaluated eagerly — clock readings, conditionals that must not evaluate the untaken branch, kind inspectors, and the text family.

The Excel divergences, announced.

Five of this language's behaviours differ from Excel deliberately (SPECIFICATION §5.2): `^` binds tighter than a unary sign and associates to the right, where Excel does the opposite of both; arithmetic and comparison refuse text where Excel coerces it; and text comparison is case-sensitive where Excel's ignores case. Each is a defensible choice, and each is a place a spreadsheet user's muscle memory produces a value they did not mean.

So the language says so. Check reports every occurrence at authoring time, naming what Excel would do and what to write instead. A divergence a tool announces is a lesson; one it hides is a bug.

Two constraints keep this honest, because an advisory nobody trusts is worse than none:

  • Nothing is reported unless it is provable from the text alone. Whether a *reference* holds text is a runtime fact, so `A1="yes"` is not flagged — it diverges only for some contents, and a checker that fired on the most common formula in any spreadsheet would train its reader to ignore it. Where the operands are literals, the divergence is proven, not guessed.
  • Authored parentheses silence the precedence findings. `-(A1^2)` is the remedy this file recommends; flagging it would make the advice impossible to take. See tsvt.Binary.IsGrouped.

The findings are advisory: the sheet computes exactly as it did before they existed. The point is that the author is told once, while writing, instead of by a wrong total months later.

The source-preserving document layer: a Document is a Sheet plus the file's physical line layout, so comment and shebang lines — which ReadTSV drops from the grid — survive editing and serialization. Every frontend that writes a .tsvt back out goes through Document.Text; no frontend serializes a grid (capability engine-doc-text).

Directives on a document: reading the view a sheet declares, and keeping the declarations true through a structural edit (SPECIFICATION §3, work order 009).

Only a directive line whose value actually moved is rewritten. Every other line — prose, a comment, a directive the edit did not touch — comes back byte-identical, because a rewrite must say exactly what the edit did and no more.

Duplicating a row or a column.

A duplicate is a fill of one line onto a new one, plus the grid shift an insert performs — so it shares fill.go's rebasing machinery and differs in what it does to the grid's shape. Keeping it here makes that difference the subject of its own file: fill.go changes cells inside the grid it was given, this one changes how many lines the grid has.

The sheet edit language (work order 011): an edits document is a TSV stream of semantic operations applied to a Document as a pure left fold. Comment lines are metadata (`#.base`) or prose; every data line is one op mirroring a Document method, its arguments verbatim TSV fields in the language's own address forms. An Edits value round-trips verbatim: Text returns the exact bytes parsed.

The op table: one parser per wire op.

edits.go owns the envelope — how a batch is framed, revision-checked and folded over a document. This file owns what each line MEANS: the operand grammar of every op, and the table that is the edit surface itself. The two change for different reasons, which is why they are separate files: adding an op touches only this one, and changing how a batch is applied touches only the other.

Fill and duplicate: the copy-time half of the reference model (spec 007). Fill copies one cell across a target span with Excel fill semantics — each unpinned reference coordinate shifts by the target's offset from the source, `$`-pinned coordinates hold — and DuplicateRow/DuplicateCol compose the existing insert with a one-line fill, so a whole line duplicates with its references rebased and the rest of the grid shifted exactly as inserts already shift it. Like the structural quartet (structural.go), everything is an AST transform re-serialized through the canonical renderer.

Writing a computed number.

Every number a formula produces passes through here on its way into a cell, and the cell is a document: what is written gets committed, diffed, pasted back in, and read by the next tool. So the rule is not "make it look nice" — it is "write text that names the value it came from".

Binary floating point makes those goals pull apart. 0.1+0.2 is genuinely 0.30000000000000004, and printing all seventeen digits reports an artefact of the representation as though it were information. Trimming to fifteen significant digits fixes that, and is what every spreadsheet does. But the trimming has to be applied as a decimal PLACE count: rounding the value and re-expanding it writes 1152921504606850000 for 2^60, which is a different number wearing four digits the value never had. Losing a fraction is a bounded, explicable loss; inventing an integer digit is not.

TSV serialization for the sheet engine: reading a .tsvt grid into a Grid of raw cell strings and writing a computed Grid back out. The A1 reference resolution and formula evaluation live in model.go and eval.go; this file is just the tab-separated line format. (The package doc is in model.go.)

Authored parentheses.

The parser records that an author wrote parentheses (tsvt.Binary.IsGrouped), and this is where that record is honoured. It is not a formatting preference: grouping is how the author said which reading was meant, and the Excel-divergence checker treats a grouped power as the explicit form that needs no diagnostic. If a rewrite dropped the parentheses, the checker would re-report a divergence the author had already resolved — the tool undoing the fix it asked for. So parentheses survive parsing, rewriting, and rendering.

Package engine's import shaping: the strict rectangle rules each IMPORT* result must satisfy. A shape mismatch is #IMPORT!, never a best-effort salvage — a silently reshaped import would put wrong numbers in a grid that looks right.

Package engine's JSON decoder: a document text becomes the immutable jsonNode tree the JSON family navigates. Member order is preserved because jsonset must round-trip a document without reordering it.

Package engine's JSON path layer: the dotted/indexed path syntax (a.b[0].c) compiled to steps, and the walk that resolves them against a document.

Package engine's JSON renderer: a jsonNode tree back to compact JSON text, preserving member order and number literals exactly as they were read.

The lazy cell backing a windowed evaluation reads through (spec 016 6b): on-demand block reads with a shared failure latch, and the sparse-memo computer construction that carries it.

Materialization: the one scan-and-read path every document size shares (spec 016). A source is indexed with the engine's own single definitions — scanLine terminates lines, IsCommentLine classifies them — and its grid rows are parsed into cells through the strided block reader. A fully materialized sheet is simply this path with an unbounded cache; the windowed and lazy modes to come are the same path under policy, never a second implementation.

The compute pass's memoization: phases for cycle detection, the memo seam, and its two forms — dense slabs for the resident pass, a budget-bounded sparse map for the windowed pass (spec 016 area 6b).

Package engine is the tsvsheet spreadsheet engine: a .tsvt file IS the sheet — a TAB-separated grid whose cells are literal values or `=formulas`, and whose formulas address other cells by A1 reference (`B2`, `D2:D4`) exactly like a conventional spreadsheet. It parses the grid, evaluates every formula in dependency order (memoized, with cycle detection), and emits the computed grid. The expression sublanguage and its AST come from internal/tsvt (the ANTLR-generated parser); this package resolves A1 references and evaluates.

This is the implementation behind the thin public github.com/tsvsheet/go-tsvsheet facade, which re-exports engine's surface unchanged; callers use that package.

Package engine's operator layer: the arithmetic and comparison semantics behind the §5.2 binary operators, kept apart from the function registry that dispatches named calls.

What the other spreadsheet would compute.

A divergence is only worth announcing when the two languages actually answer differently, and for an expression built from literals that is decidable: evaluate it under this language's reading and under Excel's, and compare. The alternative — matching the shape of the expression — announces `=+2^2`, `=-2^3` and `=-0^2`, all of which read the same in both, and a checker that cries wolf is one whose reader stops reading.

Two details make the comparison honest rather than merely arithmetic:

  • An overflow is an error, not a number. `=200^200` is #NUM! here and in Excel, so two readings that both overflow AGREE even though the floats they overflow to (+Inf and -Inf) differ. Comparing raw floats reported `=-200^200` as divergent when both languages show the same error.
  • The error propagates from the inside out. `=0^200^200` is #NUM! here because 200^200 overflows before the outer power is reached, while Excel groups it as (0^200)^200 and gets 0 — a real divergence that comparing only the final values missed, because 0^(+Inf) is 0.

Paste: the paste half of the copy-time reference model (spec 010). Paste places a clipboard block (block.go) with every formula rebased by the single delta target−origin, using the same AST transform Fill applies per target (fill.go). Like the rest of the family, everything re-serializes through the canonical renderer.

Pasting into a selection.

paste.go places a block once, at a point. This file answers the other question a spreadsheet asks of a clipboard: what happens when the target is a SELECTION rather than a cell. The block tiles the span when it divides it exactly, lands once at the top-left when it does not, and the span itself is bounded — the work and the allocation are O(area) while the request is four integers, so an unbounded selection would be an out-of-memory the caller has no way to catch.

Package engine's structural-edit algebra: how a row or column insert/delete maps every existing line index to its new one, expressed as pure transforms so the reference rewriting and the grid surgery share one definition.

The census policy and the windowed document (spec 016 area 6): a source of any size opens through the one indexed path; the census decides which capability comes back. At or under the resident budget the caller gets the same fully resident Sheet Parse builds — editable, computable, byte-for-byte today's contract. Over it the caller gets a WindowedSheet: view/compute-only by ruling, serving bounded row windows through the block cache while the source stays on disk. One implementation either way; the type is the policy made visible.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DirectivesOf added in v0.17.0

func DirectivesOf(lines []SourceLine) ([]Directive, []Diagnostic)

DirectivesOf reads the directives from a document's physical lines. A line is a directive only when its first field names a known key; anything else is prose, ignored in silence, so `#.a note` costs nothing while `#.hide` alone is reported.

func FormatValue added in v0.5.0

func FormatValue(v Value) string

FormatValue renders v as its canonical computed-cell text — byte-identical to what WriteTSV emits for that value in a computed grid. A 2-D array value reduces to its scalar-context (top-left) value before formatting.

func IsCommentLine added in v0.14.0

func IsCommentLine(at LineNumber, text SourceLine) bool

IsCommentLine reports whether the line at 1-based position at is one the grid skips: a first-line `#!` shebang, a `#.` directive-or-comment line, or a legacy `# ` hash-space comment. Everything else is data — including `#N/A` (hash then a letter) and `#<TAB>x` (hash then a TAB), so a mistyped marker shows up as a visible row rather than silently shifting A1 addresses.

This is the single definition of the rule; frontends call it rather than re-testing the prefixes themselves.

func OpenSheet added in v0.27.3

func OpenSheet(src ByteSource, limits Limits) (Sheet, *WindowedSheet, error)

OpenSheet loads a source of any size under limits: one scan builds the census, and the resident budget decides the capability. Exactly one of the returns is populated: an in-budget source materializes into a Sheet identical to Parse's; an over-budget one stands up a WindowedSheet without materializing anything.

func ResolveView added in v0.17.0

func ResolveView(ds []Directive, ext Extent) (View, []Diagnostic)

ResolveView derives the view from directives and the grid's extent. Items union; an item naming positions the grid does not have selects nothing, because ambiguity in a view resolves toward showing more data, never less.

func WouldStartCommentLine added in v0.25.1

func WouldStartCommentLine(at Address, text CellText) bool

WouldStartCommentLine reports whether text placed in a row's first cell would make the serialized line a comment — which would delete that row from the grid on the next read, silently shifting every address below it. An edit that would do this is refused rather than written, because the language has no escape for a leading marker (SPECIFICATION §3); the shebang form is refused in any row, since a row can become line 1 under a later edit.

func WriteTSV

func WriteTSV(w io.Writer, g Grid) error

WriteTSV writes the grid as tab-separated rows, each terminated by a newline. A write failure surfaces as constants.ErrWriteFile. Callers wanting buffering pass a bufio.Writer; WriteTSV writes each row directly so a write error is reported at its source.

A row whose first cell would serialize to a comment-reading line (a leading `#!`, `#.`, or `# ` — WouldStartCommentLine's rule) is refused as constants.ErrInvalidValue rather than written: the language has no escape for a leading marker, so the emitted line would read back as a comment and the row would silently vanish on the next read — data loss dressed as output. (FuzzReadTSV found the shape: a document may legally hold `#!x` as data below its comment lines, but the bare-grid format cannot represent it.)

Types

type Address

type Address struct {
	Row int `json:"row"` // 0-based
	Col int `json:"col"` // 0-based
}

Address is a cell coordinate in spreadsheet notation (`F4`): column letters plus a 1-based row. It carries 0-based indices internally.

func ParseAddress

func ParseAddress(s AddressText) (Address, error)

ParseAddress parses spreadsheet notation (`A1`, `F4`, `AA10`) into an Address. The column is one or more ASCII uppercase letters, the row a positive integer; anything else is constants.ErrInvalidValue.

func (Address) String

func (a Address) String() string

String renders the Address in spreadsheet notation.

type AddressText

type AddressText string

AddressText is spreadsheet-address source text (`A1`, `F4`) accepted by ParseAddress. It is exported so callers in other packages can convert their string input at the call site.

type BlockText added in v0.24.0

type BlockText string

BlockText is clipboard-block source text: TAB-separated cells on newline-separated rows, as a copied range travels through a clipboard.

type ByteSource added in v0.27.3

type ByteSource struct {
	ReadAt readAtSource
	Size   int64
}

ByteSource is an any-size byte source: a file, a spooled stream, or in-memory bytes, with its length. Size is trusted: bytes past it are not read, and a Size shorter than the actual data truncates at that byte — a caller pairing a stat with an open owns that pairing.

type CellInfo

type CellInfo struct {
	Text      string
	Address   Address
	IsFormula bool
}

CellInfo describes one non-empty cell: its address, source text, and whether it is a formula — the projection the parse command emits.

type CellText added in v0.25.1

type CellText string

CellText is a cell's source text as an editor supplies it.

type ComputeOptions

type ComputeOptions struct {
	At      time.Time
	Fetcher Fetcher
	Loader  Loader
	Base    Path
	Limits  Limits
	Tick    Tick
}

ComputeOptions configures a compute pass. Loader and Base enable embedded sub-sheets; a zero Loader disables SHEET (it resolves to #REF!).

type Diagnostic

type Diagnostic struct {
	Cell    string `json:"cell,omitempty"`
	Message string `json:"message"`
	Line    int    `json:"line,omitempty"`
	IsFatal bool   `json:"fatal"`
}

Diagnostic is an advisory finding about a sheet: an unknown function call in a formula cell (which computes to #NAME?), or a view directive the parser rejects. A cell finding carries Cell; a directive finding carries Line, the 1-based physical line, because a directive occupies a line and no row.

func Check

func Check(s Sheet) []Diagnostic

Check reports the static diagnostics of a parsed sheet: each unknown function call. Syntax errors are already rejected by Parse, and every reference the narrowed grammar admits is a valid A1 form, so Check never reports those.

type Directive added in v0.17.0

type Directive struct {
	Value tsvt.DirectiveValue
	Key   Key
	At    LineNumber
}

Directive is one key/value pair read from a `#.` line, carrying the physical line it came from so a diagnostic can name it.

type Document added in v0.6.0

type Document struct {
	// contains filtered or unexported fields
}

Document is a parsed .tsvt file with its line layout retained. The zero value is an empty document. Document is immutable: editing operations return a new Document.

func Apply added in v0.25.0

func Apply(d Document, e Edits, limits Limits) (Document, error)

Apply left-folds e's ops over d. When e names a base revision that is not d's, the whole batch is refused (constants.ErrEditsBase); when any op is refused, the whole batch is rejected with constants.ErrEditsApply wrapping the cause and naming the op's line — d is immutable, so nothing is ever partially applied. Nothing outside (d, e, limits) influences the result.

func ParseDocument added in v0.6.0

func ParseDocument(src []byte) (Document, error)

ParseDocument reads a .tsvt file like Parse, additionally recording the physical line layout so comment and shebang lines are preserved by Text.

func ParseDocumentWith added in v0.27.4

func ParseDocumentWith(src []byte, limits Limits) (Document, error)

ParseDocumentWith is ParseDocument under the same residency budget as ParseWith (spec 018): the census gate refuses an over-resident document before the layout or any cell materializes.

func (Document) DeleteCol added in v0.6.0

func (d Document) DeleteCol(at Address) Document

DeleteCol returns a new document with column at.Col removed; column operations never touch the line layout.

func (Document) DeleteRow added in v0.6.0

func (d Document) DeleteRow(at Address) Document

DeleteRow returns a new document with row at.Row removed (Sheet.DeleteRow semantics); comment lines are never deleted by a row deletion.

func (Document) Directives added in v0.19.0

func (d Document) Directives() ([]Directive, []Diagnostic)

Directives reads the view directives this document declares, with the diagnostics for any that cannot be read.

func (Document) DuplicateCol added in v0.13.0

func (d Document) DuplicateCol(at Address) Document

DuplicateCol returns a new document with column at.Col duplicated to its right; column operations never touch the line layout.

func (Document) DuplicateRow added in v0.13.0

func (d Document) DuplicateRow(at Address) Document

DuplicateRow returns a new document with row at.Row duplicated below itself (Sheet.DuplicateRow semantics); comments keep the between-row gap they were written in. A no-op on the sheet is a no-op on the document.

func (Document) Extent added in v0.19.0

func (d Document) Extent() Extent

Extent reports the grid's size, which edge-anchored items resolve against.

func (Document) Fill added in v0.13.0

func (d Document) Fill(from Address, to Span) Document

Fill returns a new document with Sheet.Fill applied; rows the grid grew by are appended to the layout after any trailing comments, as SetCell's growth is.

func (Document) InsertCol added in v0.6.0

func (d Document) InsertCol(at Address) Document

InsertCol returns a new document with a blank column inserted before at.Col; column operations never touch the line layout.

func (Document) InsertRow added in v0.6.0

func (d Document) InsertRow(at Address) Document

InsertRow returns a new document with a blank row inserted before at.Row (Sheet.InsertRow semantics); comments keep the between-row gap they were written in. A no-op on the sheet is a no-op on the document.

func (Document) Paste added in v0.24.0

func (d Document) Paste(at, origin Address, block Grid, limits Limits) (Document, error)

Paste returns a new document with Sheet.Paste applied — a clipboard block placed at at with its formulas rebased by at−origin; rows the grid grew by are appended to the layout after any trailing comments, as Fill's growth is. Paste is atomic: on error the document is unchanged.

func (Document) PasteInto added in v0.26.0

func (d Document) PasteInto(target Span, origin Address, block Grid, limits Limits) (Document, error)

PasteInto returns a new document with Sheet.PasteInto applied — the block tiling an exactly-divisible span (each tile rebased to its own position) or placed once at the span's top-left; growth appends layout markers as Paste's growth does. Atomic: on error the document is unchanged.

func (Document) SetCell added in v0.6.0

func (d Document) SetCell(at Address, text string, limits Limits) (Document, error)

SetCell returns a new document with the cell at addr replaced (Sheet.Set semantics); rows the grid grew by are appended to the layout, after any trailing comments — they did not exist when those comments were written.

func (Document) Sheet added in v0.6.0

func (d Document) Sheet() Sheet

Sheet returns the parsed sheet the document wraps.

func (Document) Text added in v0.6.0

func (d Document) Text() []byte

Text serializes the document canonically: lines in layout order — comments verbatim, grid rows tab-joined — every line newline-terminated. For input already in canonical form, ParseDocument followed by Text is byte-identity.

func (Document) View added in v0.19.0

func (d Document) View() (View, []Diagnostic)

View resolves this document's directives against its own extent: which rows and columns are hidden, carry headers, or stay anchored.

type Edits added in v0.25.0

type Edits struct {
	// contains filtered or unexported fields
}

Edits is a parsed edits document. The zero value is empty. Edits is immutable: parsing returns a value whose Text is the exact input.

func ParseEdits added in v0.25.0

func ParseEdits(src []byte) (Edits, error)

ParseEdits reads an edits document: `#.` and legacy comment lines carry metadata or prose, blank lines are prose, and every other line is one op. Any malformed line is a specific sentinel carrying its 1-based line number.

func ParseEditsWith added in v0.27.5

func ParseEditsWith(src []byte, limits Limits) (Edits, error)

ParseEditsWith is ParseEdits under a residency budget (spec 018): a batch whose line count (each line is at most one op) exceeds Limits' effective resident ceiling refuses with ErrDocTooLarge before any op parses — each op touches at least one cell, so a batch larger than the cells a document may hold is over budget by construction, and no caller pays an unbounded op-slice allocation it did not raise its budget to accept.

func (Edits) Base added in v0.25.0

func (e Edits) Base() RevisionHex

Base returns the `#.base` revision the document was authored against, or "" when it names none (an unconditional batch). A batch naming several bases keeps the last, matching the metadata rule that a repeated key is a re-declaration; position carries no meaning, so a base line below the ops still governs the whole batch.

func (Edits) Len added in v0.25.0

func (e Edits) Len() int

Len is the number of operations (comment and metadata lines are not ops).

func (Edits) Text added in v0.25.0

func (e Edits) Text() []byte

Text returns the edits document's exact source bytes.

type ErrorValue

type ErrorValue string

ErrorValue is a spreadsheet error value — a cell value, not a Go error. It propagates through expressions per ADR 0003 (rules 3, 8, 12, 14).

const (
	ErrRef    ErrorValue = "#REF!"
	ErrValue  ErrorValue = "#VALUE!"
	ErrName   ErrorValue = "#NAME?"
	ErrDiv    ErrorValue = "#DIV/0!"
	ErrCirc   ErrorValue = "#CIRC!"
	ErrNA     ErrorValue = "#N/A"
	ErrNum    ErrorValue = "#NUM!"
	ErrNull   ErrorValue = "#NULL!"
	ErrSpill  ErrorValue = "#SPILL!"
	ErrImport ErrorValue = "#IMPORT!"
	ErrLimit  ErrorValue = "#LIMIT!"
)

The error values. #REF! (out-of-grid), #VALUE! (type), #NAME? (unknown function), #DIV/0! (division by zero), #CIRC! (a formula whose evaluation depends on itself), #N/A (lookup miss / NA()), #NUM! (numeric domain), #NULL! (empty range intersection), #SPILL! (blocked dynamic-array spill), #IMPORT! (a content-typed import failed — disabled, denied, or a bad handshake), #LIMIT! (a reference or result larger than the configured cell budget).

type Expr added in v0.5.0

type Expr struct {
	// contains filtered or unexported fields
}

Expr is one compiled bare expression — the text that would follow `=` in a formula cell — detached from any sheet. It is an immutable value: compile once with CompileExpr, then evaluate against any number of grids, including concurrently, without re-parsing.

func CompileExpr added in v0.5.0

func CompileExpr(src []byte) (Expr, error)

CompileExpr parses and compiles one bare expression (no leading `=`). A malformed expression is constants.ErrSyntax carrying line/column detail. Compilation is grid-independent; the result evaluates against any Grid.

func (Expr) Eval added in v0.5.0

func (e Expr) Eval(g Grid, opts ComputeOptions) Value

Eval evaluates the expression against g with the semantics of a formula cell in a sheet over that grid: A1 references resolve into g with the same literal coercion, range, dynamic-array, and error-value semantics; an out-of-grid reference is #REF!; volatile functions read opts.At; opts.Limits bounds allocations; and opts.Loader / opts.Fetcher gate SHEET and IMPORT* exactly as in a compute pass — Eval and ComputeWith share the pass computer, so they cannot diverge. Evaluation failures are error values, never Go errors.

type Extent added in v0.17.0

type Extent struct {
	Rows int
	Cols int
}

Extent is the grid's size. Edge-anchored items — `count(…)` and a `-1` endpoint — resolve against it, which is why a view is derived rather than stored.

type FetchResult

type FetchResult struct {
	ContentType MediaType
	URL         ImportURL
	Body        []byte
}

FetchResult is a Fetcher's response: the raw body and the media type the server declared, which must match the requested Accept for the handshake to succeed (ADR 0006 §2).

URL is the location the fetcher actually reached, which may differ from the source the sheet named: a relative source is resolved against the operator's data base by the fetcher, so the engine cannot know it. Purely informational — it feeds EXPLAIN so an author can see where a value came from, and nothing in the compute path reads it. A fetcher that leaves it empty is valid.

type Fetcher

type Fetcher interface {
	Fetch(url ImportURL, accept MediaType) (FetchResult, error)
}

Fetcher retrieves the content-typed import at url, sending accept as the requested media type. The engine holds only this interface; the concrete net/http fetcher, allowlist, and caching are injected by a frontend. A nil Fetcher disables imports (every IMPORT* is #IMPORT!).

type Grid

type Grid [][]string

Grid is a rectangular value grid indexed [row][col], 0-based. Cells are raw strings: a literal's own text on input, or a formula cell's computed value after ComputeAt.

func ParseBlock added in v0.24.0

func ParseBlock(text BlockText) Grid

ParseBlock reads a clipboard block: CRLF and lone CR normalize to LF, exactly one trailing newline is ignored, rows split on LF and cells on TAB. Every line is data — a clipboard block has no comment or directive semantics, unlike a .tsvt file. An empty text is a single empty cell (the TSV serialization of one empty cell IS the empty string), so pasting it clears its target.

func ReadTSV

func ReadTSV(r io.Reader) (Grid, error)

ReadTSV reads a tab-separated value grid. Rows are newline-separated; a trailing newline does not add an empty row. Full-line comments are skipped and do not occupy a grid row, per IsCommentLine: a leading `#!` on the first line (a shebang, so a .tsvt can be `chmod +x` and run via `#!/usr/bin/env tsvsheet`), any `#.` directive-or-comment line, and any legacy `# ` hash-space line. An error-value cell like `#N/A` is data, not a comment. A read failure surfaces as ErrReadInput.

type ImportURL

type ImportURL string

ImportURL is the location an IMPORT* function fetches — the (already evaluated) string value of its single argument.

type Key added in v0.17.0

type Key int

Key is a view-directive key: which class of view state a value configures.

const (
	KeyHide Key = iota
	KeyHeader
	KeyFreeze
)

The three keys, one per class — a projection, a structure declaration, and a viewport hint. A grid has two axes, and which one a directive means lives in its value rather than in a key per axis.

type Limits

type Limits struct {
	ResultCells   int // cells in one array formula result (e.g. SEQUENCE)
	GridDim       int // the highest row or column index the grid may grow to (Set)
	ResultBytes   int // bytes in one string formula result (e.g. REPT)
	SpanCells     int // cells one written reference's rectangle may cover; 0 falls back to ResultCells
	ResidentCells int // cells a document may hold and still load fully resident (editable); 0 falls back to ResultCells
	TouchedCells  int // distinct cells one windowed evaluation may visit across its dependency walks; 0 falls back to ResultCells
}

Limits bounds the sizes an untrusted sheet may drive an allocation to.

func BrowserLimits

func BrowserLimits() Limits

BrowserLimits are the tighter ceilings the WASM build applies, sized for a browser tab rather than a workstation. SpanCells sits well above ResultCells deliberately: reading a large in-grid range (a whole-column SUM) is ordinary spreadsheet use the grid dimension already permits, while a large array RESULT (a SEQUENCE spill) writes that many new cells — the two budgets bound different costs and must not share a ceiling.

func DefaultLimits

func DefaultLimits() Limits

DefaultLimits are generous for real spreadsheets while still bounding OOM.

func (Limits) EffectiveResidentCells added in v0.27.5

func (l Limits) EffectiveResidentCells() int64

EffectiveResidentCells is the resolved resident ceiling a caller may vet against BEFORE loading (spec 018): the zero value falls back to DefaultLimits, then ResidentCells falls back to ResultCells — exactly the policy OpenSheet and the bounded parses apply, exported so no frontend re-implements the fallback.

type LineNumber added in v0.14.0

type LineNumber int

LineNumber is a 1-based physical line position in a .tsvt source file, as opposed to a grid row: comment lines occupy a line but no row.

type Loader

type Loader func(base, ref Path) (Sheet, Path, error)

Loader resolves the sheet referenced by ref, relative to the embedding sheet's own path base, returning the parsed sub-sheet and its resolved path (used for cycle detection and as the base for the sub-sheet's own SHEET calls). The frontend injects it, keeping the engine filesystem-free; a resolution or containment failure is reported as an error and surfaces as #REF!.

type MediaType

type MediaType string

MediaType is a content-typed import's RFC 6838 media type — the Accept header an IMPORT* function requests, which the response Content-Type must match.

func (MediaType) Accept added in v0.6.2

func (m MediaType) Accept() string

Accept is the negotiation list an IMPORT* request sends for this vendor media type: the vendor type preferred, the standard tabular types admitted with descending quality (ADR 0010 §1). Frontends set it as the Accept header.

type Path

type Path string

Path identifies a sheet to a Loader: the reference written in a SHEET(...) call, and (as the loader's result) the sheet's own resolved path.

type RevisionHex added in v0.25.0

type RevisionHex string

RevisionHex is a document's content address: the lowercase-hex SHA-256 of its canonical Text bytes. Equal revision ⇔ byte-equal document.

func Revision added in v0.25.0

func Revision(d Document) RevisionHex

Revision content-addresses d.

type Selection added in v0.17.0

type Selection map[int]bool

Selection is a set of 1-based positions on one axis: the rows a directive hides, the columns it freezes.

type Sheet

type Sheet struct {
	// contains filtered or unexported fields
}

Sheet is a parsed spreadsheet grid of literal and formula cells.

func Parse

func Parse(src []byte) (Sheet, error)

Parse reads a .tsvt grid: each TAB-separated field is a literal, or — when it begins with `=` — a formula compiled from the expression that follows. A malformed formula is a syntax error naming its cell. The bytes are indexed and read through the one materialization path every source shares (spec 016).

func ParseWith added in v0.27.4

func ParseWith(src []byte, limits Limits) (Sheet, error)

ParseWith is Parse under a residency budget (spec 018): the census scan — O(index) memory, no cell touched — refuses a document whose cell count exceeds Limits.ResidentCells (single-ceiling fallback like its siblings) with ErrDocTooLarge BEFORE anything materializes, so no caller pays an unbounded allocation it did not raise its budget to accept. An in-budget document parses exactly as Parse does. Parse itself stays unbounded for embedders that pre-vet their sources; every ecosystem load path routes through the bounded forms.

func (Sheet) Cells

func (s Sheet) Cells() []CellInfo

Cells returns every non-empty cell of the sheet as CellInfo, in row-major order.

func (Sheet) Compute

func (s Sheet) Compute() Grid

Compute evaluates every formula in dependency order and returns the value grid: literal cells pass through verbatim, formula cells are replaced by their computed value. Volatile functions (TODAY/NOW) sample the wall clock once for the whole pass.

func (Sheet) ComputeAt

func (s Sheet) ComputeAt(at time.Time) Grid

ComputeAt is Compute with the clock injected, so volatile functions are deterministic within a pass (and testable). It computes every cell's value, then renders — spilling dynamic-array results into empty neighbours.

func (Sheet) ComputeAtTick added in v0.8.0

func (s Sheet) ComputeAtTick(at time.Time, tick Tick) Grid

ComputeAtTick evaluates the sheet against clock at with the recompute-pass ordinal tick injected for tick()/frame(). A frontend that re-renders a volatile sheet increments tick each pass; ComputeAt uses 0.

func (Sheet) ComputeWith

func (s Sheet) ComputeWith(opts ComputeOptions) Grid

ComputeWith computes the sheet with an injected sheet loader, so SHEET(...) cells embed other sheets. The sheet's own Base path seeds cycle detection; the injected Limits (or DefaultLimits when unset) bound every allocation.

func (Sheet) DeleteCol

func (s Sheet) DeleteCol(at Address) Sheet

DeleteCol returns a new sheet with column at.Col removed; references to it become #REF! and references to its right shift left. A column past every row is a no-op. Only the column coordinate of at is used.

func (Sheet) DeleteRow

func (s Sheet) DeleteRow(at Address) Sheet

DeleteRow returns a new sheet with row at.Row removed; references to it become #REF! and references below it shift up. An out-of-range row is a no-op. Only the row coordinate of at is used.

func (Sheet) Dependents

func (s Sheet) Dependents(at Address) []Address

Dependents returns every formula cell whose references cover `at`, in row-major order — the reverse edge of Precedents.

func (Sheet) DuplicateCol added in v0.13.0

func (s Sheet) DuplicateCol(at Address) Sheet

DuplicateCol returns a new sheet with column at.Col duplicated to its right: the existing InsertCol shifts every reference past the new column, then each row that has a source cell fills its inserted blank with the source rebased one column right (pins hold). Rows too short to reach the column stay untouched, mirroring InsertCol. An out-of-range column is a no-op. Only the column coordinate of at is used.

func (Sheet) DuplicateRow added in v0.13.0

func (s Sheet) DuplicateRow(at Address) Sheet

DuplicateRow returns a new sheet with row at.Row duplicated below itself: the existing InsertRow shifts every reference past the new line, then the source row fills the blank line with its references rebased one row down (pins hold). The duplicate keeps the source row's exact width. An out-of-range row is a no-op. Only the row coordinate of at is used.

func (Sheet) EmbeddedGrid

func (s Sheet) EmbeddedGrid(at Address, opts ComputeOptions) (Path, Grid, bool)

EmbeddedGrid resolves the sub-sheet embedded by a SHEET(...) cell and returns its resolved path and its own computed grid — the projection a frontend renders as a nested sheet inside the cell. ok is false when the cell is not a top-level SHEET call or the reference cannot be resolved.

func (Sheet) Fill added in v0.13.0

func (s Sheet) Fill(from Address, to Span) Sheet

Fill returns a new sheet with the cell at from copied into every cell of to: a literal copies verbatim, a formula rebases each reference by the target's offset (pinned coordinates hold; a coordinate rebased off the grid renders as #REF!; cross-sheet references copy unshifted). The target equal to from is skipped, so a span containing the source fills around it. Targets beyond the grid grow it, as Set does. A negative from or span corner is a no-op, mirroring the structural quartet; a from beyond the grid is an empty source and clears its targets. Fill takes no Limits: it is a trusted-caller surface, and the untrusted boundary — the edit language — bounds the span's corners and area before delegating here (withinGrid in edits_ops.go).

func (Sheet) HasImports

func (s Sheet) HasImports() bool

HasImports reports whether any formula calls an IMPORT* function, so a frontend can offer a manual "refresh imports" control. Imports are NOT clock-volatile and are deliberately absent from IsVolatile — they must never ride the isnow refresh ticker (ADR 0006 §6).

func (Sheet) InsertCol

func (s Sheet) InsertCol(at Address) Sheet

InsertCol returns a new sheet with a blank column inserted before at.Col; every reference to a column at or right of it shifts right. A negative column is a no-op (mirroring DeleteCol), never a slice-bounds panic. Only the column coordinate of at is used.

func (Sheet) InsertRow

func (s Sheet) InsertRow(at Address) Sheet

InsertRow returns a new sheet with a blank row inserted before at.Row; every reference to a row at or below it shifts down to follow its data. A negative row is a no-op (mirroring DeleteRow), never a slice-bounds panic. Only the row coordinate of at is used.

func (Sheet) IsVolatile

func (s Sheet) IsVolatile() bool

IsVolatile reports whether any formula wraps an expression in volatile(…), the sole marker that a cell's computed value changes over time and a frontend should recompute. The clock functions today/now/isnow are volatile only when wrapped — nothing is volatile without volatile().

func (Sheet) Paste added in v0.24.0

func (s Sheet) Paste(at, origin Address, block Grid, limits Limits) (Sheet, error)

Paste returns a new sheet with block — a grid of raw cell texts, as copied with its top-left at origin — placed with its top-left at at: every formula rebases by the single delta at−origin with Fill's semantics (unpinned coordinates shift, `$`-pinned hold, a coordinate rebased off the grid renders #REF!, cross-sheet references copy unshifted); a literal copies verbatim; an empty block cell clears its target, and a ragged block pads its short rows with empty cells, so a paste always overwrites its whole rectangular footprint. The grid grows to fit, bounded by limits as Set is. Paste is atomic: a malformed formula is a syntax error naming its target cell and the sheet is unchanged. An empty block is a no-op.

func (Sheet) PasteInto added in v0.26.0

func (s Sheet) PasteInto(target Span, origin Address, block Grid, limits Limits) (Sheet, error)

PasteInto returns a new sheet with block placed over the normalized target span. When the span's rows AND columns are exact multiples of the block's dimensions, the block TILES the span — each tile rebased by its own delta (tile position − origin), exactly as separate pastes would rebase — which is how one copied row spread-pastes onto every selected row. Any other span places the block once at the span's top-left (Paste semantics), so a selection that does not fit the block never half-fills. Atomic: an error in any tile leaves the sheet unchanged.

func (Sheet) Precedents

func (s Sheet) Precedents(at Address) []Span

Precedents returns the cell and range references the formula at `at` reads, as resolved spans in source order. A literal cell, an address off the grid, or a formula with no references returns nil.

func (Sheet) Set

func (s Sheet) Set(addr Address, text string, limits Limits) (Sheet, error)

Set returns a new sheet with the cell at addr replaced by text (a literal or a formula), growing the grid to reach an out-of-bounds position. The injected limits bound how far the grid may grow. A malformed formula is a syntax error and the sheet is unchanged (Set is immutable, so the caller simply keeps the old value).

func (Sheet) Source

func (s Sheet) Source() Grid

Source returns the sheet's cell source texts (literals and "=formulas") as a grid — what an editor shows and what is saved back to the .tsvt file.

func (Sheet) VolatileSchedules added in v0.8.0

func (s Sheet) VolatileSchedules() []string

VolatileSchedules returns one refresh-cadence spec per volatile(…) call across the sheet: an explicit string second argument if present, else the pattern of a wrapped isnow("pattern"), else "" (the frontend's default cadence). A frontend unions the set to the soonest next instant.

type SheetCensus added in v0.27.3

type SheetCensus struct {
	Rows     int
	MaxWidth int
	Cells    int64
	Formulas int64
}

SheetCensus is what one open-time scan learned about a document — the numbers the load policy and a frontend's chrome read.

func Census added in v0.27.5

func Census(src ByteSource) (SheetCensus, error)

Census reports a source's totals from one index scan — O(index) memory, nothing materialized. It is the cheap pre-flight a frontend runs to decide or refuse before buffering or parsing anything (spec 018): the CLI vets a file's cell count against its budget in tens of megabytes where buffering first would transiently hold the whole file.

type SourceLine added in v0.14.0

type SourceLine string

SourceLine is one physical line of .tsvt source, newline already stripped.

type Span

type Span struct {
	From Address `json:"from"`
	To   Address `json:"to"`
}

Span is a rectangular reference target resolved to 0-based addresses: a single cell (From == To) or a range (From is the top-left, To the bottom-right as written). It is the projection a frontend highlights.

type Tick added in v0.8.0

type Tick int

Tick is a recompute-pass ordinal, injected for tick()/frame(): a frontend re-rendering a volatile sheet passes an incrementing value each pass.

type Trace

type Trace struct {
	Cell    string        `json:"cell"`
	Value   string        `json:"value"`
	Formula string        `json:"formula,omitempty"`
	Inputs  []TraceInput  `json:"inputs,omitempty"`
	Imports []TraceImport `json:"imports,omitempty"`
	Notes   []string      `json:"notes,omitempty"`
}

Trace explains how one cell was produced: its value, the formula (empty for a literal), the resolved value of each cell the formula reads, and — when the formula imports — where each import went and whether it succeeded. Notes carries the same Excel divergences Check reports, so a reader who has not run the checker still learns why a cell reads the way it does.

func Explain

func Explain(s Sheet, at Address) (Trace, error)

Explain computes the sheet and describes the cell at at: its value, and — when the cell is a formula — that formula and each reference it reads. Imports are disabled on this path (no Fetcher), so an IMPORT* cell is #IMPORT!; use ExplainWith to trace a sheet that imports.

func ExplainWith added in v0.22.0

func ExplainWith(s Sheet, at Address, opts ComputeOptions) (Trace, error)

ExplainWith is Explain with an injected compute environment, so a sheet whose cells embed sub-sheets or import external data traces with those resolved rather than erroring.

type TraceImport added in v0.22.0

type TraceImport struct {
	Source string `json:"source"`
	URL    string `json:"url,omitempty"`
	Error  string `json:"error,omitempty"`
}

TraceImport is one IMPORT* call a formula performs: the source exactly as the sheet wrote it, the URL that source actually resolved to, and the reason it failed. Every import failure is the same opaque #IMPORT! in the grid by design, so this is the only place an author can learn WHICH failure it was — a denied host and a traversal above the data base look identical in a cell.

type TraceInput

type TraceInput struct {
	Ref   string `json:"ref"`
	Value string `json:"value"`
}

TraceInput is one reference a formula reads, with its resolved value.

type Value

type Value struct {
	// contains filtered or unexported fields
}

Value is an evaluated cell value: empty, number, string, boolean, date, error, or a 2-D array (a dynamic-array result that spills, or reduces to its top-left value in a scalar context). isRefused marks a wholesale range-resolution refusal (see refusalValue); it never survives past the cell that produced it (read strips it via asCellResult).

func (Value) String

func (v Value) String() string

String renders a Value as its cell text: empty is "", a number is formatted without a trailing zero fraction, a string is itself, an error is its code.

type View added in v0.17.0

type View struct {
	HiddenRows Selection `json:"hidden_rows"`
	HiddenCols Selection `json:"hidden_cols"`
	HeaderRows Selection `json:"header_rows"`
	HeaderCols Selection `json:"header_cols"`
	FreezeRows Selection `json:"freeze_rows"`
	FreezeCols Selection `json:"freeze_cols"`
}

View is what a viewport does with the grid: sets of positions, never counts, so a host renders it without re-deriving anything.

type WindowedSheet added in v0.27.3

type WindowedSheet struct {
	// contains filtered or unexported fields
}

WindowedSheet is the over-budget capability: bounded row windows over an indexed source. Values share the underlying reader (the mutex-guarded cache lives behind sheetSource's pointer), so it copies safely; OpenSheet returns a pointer only as the is-it-windowed signal.

func (WindowedSheet) CachedCells added in v0.27.3

func (w WindowedSheet) CachedCells() int64

CachedCells reports the cells currently resident in the windowed block cache — bounded by the resident budget, and honest telemetry for a frontend's status line.

func (WindowedSheet) Census added in v0.27.3

func (w WindowedSheet) Census() SheetCensus

Census reports the windowed document's totals.

func (WindowedSheet) ComputeRows added in v0.27.3

func (w WindowedSheet) ComputeRows(from, n int, opts ComputeOptions) (Grid, error)

ComputeRows evaluates the window [from, from+n): literals as their values, formulas through the one resolver over a sparse memo bounded by the touched-cells budget — the design's cumulative bound on values, memory, and source I/O alike; past the budget a cell answers #LIMIT!. The caller's ComputeOptions govern the pass exactly as they do a resident ComputeWith: its Limits, Loader/Base, Fetcher, clock, and Tick. An array-producing formula renders its top-left value (windows do not spill), except an anchor the document's own semantics refuses, which shows the resident render's #SPILL!. A source failure during evaluation fails the call as ErrReadInput rather than serving partial data. Volatile draws (rand and kin) are made in this window's evaluation order: one window at a fixed At reproduces exactly, but different windows over the same cells may draw differently — windowed evaluation has no whole-document order to share.

func (WindowedSheet) Rows added in v0.27.3

func (w WindowedSheet) Rows(from, n int) (Grid, error)

Rows returns the source texts of the window [from, from+n), clipped to the grid on both ends — the viewport read: literals and formulas as written, nothing computed. Formula evaluation for a window arrives with ComputeRows (area 6b); a caller needing whole-document computation raises the resident budget instead (the owner's ruling: budgets are policy, never ceilings).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL