oficina / sheets Public

Clone
git clone https://amadan.net/oficina/sheets

Plain git — no account needed to clone.

Download

Download this file

1package calc
2
3// Func is a built-in spreadsheet function: it receives every argument's
4// value already evaluated, with a range argument flattened to one Value
5// per cell (row-major), and returns the call's result.
6//
7// Task 7 wires the dispatch only; funcTable started empty. Task 8 owns the
8// actual function bodies (SUM, IF, VLOOKUP, ...) in fn_*.go and
9// registry.go, and populates funcTable from there via the exported
10// Register — same package, so registry.go needs no access to funcTable
11// beyond what's already unexported here.
12//
13// A few functions (NOW/TODAY, VLOOKUP/INDEX/MATCH) cannot be expressed as
14// a plain Func — see clockFuncTable and nodeFuncTable below — so
15// evalCall checks three tables, not one; Func itself keeps this exact
16// signature for the ~150 ordinary functions the plan describes.
17type Func func(args []Value) Value
18
19var funcTable = map[string]Func{}
20
21// evalCall dispatches a function call: look up t.Name (already upper-cased
22// by the parser) in whichever of the three function tables has it, and
23// invoke it. An unresolved name — including a reference-shaped word the
24// lexer treated as a call because '(' followed it (token.go's
25// followedByCallParen — e.g. A1(2) parses as Call{Name:"A1"}) — is
26// #NAME?.
27//
28// Argument values reach an ordinary Func exactly as evaluated, including
29// any that are KindErr — evalCall does not short-circuit on an error
30// argument the way evalBinary and evalUnary short-circuit on an error
31// operand. That is deliberate, not an oversight: IFERROR (task 8) needs
32// the raw error value to inspect, so blanket propagation here would break
33// the one place a formula is allowed to look at an error instead of
34// becoming one. Each registered Func decides for itself whether an error
35// argument means "become that error" (most functions) or something else
36// (IFERROR).
37func evalCall(t *Call, env Env) Value {
38 if fn, ok := funcTable[t.Name]; ok {
39 return fn(evalCallArgs(t.Args, env))
40 }
41 if fn, ok := clockFuncTable[t.Name]; ok {
42 return fn(env, evalCallArgs(t.Args, env))
43 }
44 if fn, ok := nodeFuncTable[t.Name]; ok {
45 return fn(t.Args, env)
46 }
47 return Value{Kind: KindErr, Err: ErrName}
48}
49
50// evalCallArgs flattens every argument node into the shared []Value an
51// ordinary Func or clockFunc receives — the same eager, error-preserving
52// evaluation evalCall has always done, just factored out so both dispatch
53// branches that need it don't repeat the loop.
54func evalCallArgs(nodes []Node, env Env) []Value {
55 var args []Value
56 for _, a := range nodes {
57 args = append(args, evalCallArg(a, env)...)
58 }
59 return args
60}
61
62// evalCallArg evaluates one call argument into one or more Values: a
63// range expands to its cells in row-major order (via env.CellValue
64// directly — not by re-entering Eval, but env.CellValue is the same
65// dependency-aware lookup a *Ref goes through, so this is not a way to
66// dodge cycle detection or a graph's dirty-set scoping), anything else
67// evaluates to exactly one Value through Eval.
68//
69// A range over maxRangeCells resolves to a single #REF! argument rather
70// than materializing it — see rangeCells.
71func evalCallArg(n Node, env Env) []Value {
72 rng, ok := n.(*Range)
73 if !ok {
74 return []Value{Eval(n, env)}
75 }
76 cells, ok := rangeCells(rng)
77 if !ok {
78 return []Value{{Kind: KindErr, Err: ErrRef}}
79 }
80 out := make([]Value, len(cells))
81 for i, k := range cells {
82 out[i] = env.CellValue(k)
83 }
84 return out
85}
86