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// Register adds fn to the ordinary function table under name. Every
4// fn_*.go family file calls this once per function from its own init(),
5// so import order never matters — each file is independently
6// self-registering, and the whole set is populated before any test or
7// caller can reach evalCall.
8//
9// name is expected already upper-cased, matching how the parser
10// upper-cases every call name before Eval ever sees one (parseIdentOrCall,
11// parse.go) — Register itself does not upper-case anything, so a family
12// file that registered "sum" instead of "SUM" would silently never be
13// found by a real formula. Two families registering the same name is a
14// programming error caught immediately at init time, not a silent
15// last-write-wins overwrite that only surfaces as a wrong answer later.
16func Register(name string, fn Func) {
17 if _, exists := funcTable[name]; exists {
18 panic("calc: duplicate function registration: " + name)
19 }
20 funcTable[name] = fn
21}
22
23// clockFunc is the signature for a function that needs the current
24// recalculation pass's pinned instant (env.Now()) rather than argument
25// values alone. NOW and TODAY (fn_date.go) are the only two in this
26// task's scope.
27//
28// Func's signature — func(args []Value) Value — has no room for env, and
29// giving NOW/TODAY an ordinary funcTable entry that silently ignored env
30// (returning some fixed or approximate time) would be exactly the bug
31// this task's brief warns about: a registration that reads as "a real
32// registered function" on inspection but only behaves correctly by
33// accident, because something else intercepts the name before ever
34// calling it. Keeping "this function needs the clock" as a type-level
35// fact — a distinct signature, a distinct table evalCall checks
36// explicitly — means there is no ordinary Func entry for NOW/TODAY that
37// could be invoked without one.
38type clockFunc func(env Env, args []Value) Value
39
40var clockFuncTable = map[string]clockFunc{}
41
42// registerClock is Register's counterpart for clockFunc. Unexported: only
43// fn_date.go's two functions need it, so it is not part of the package's
44// public registration contract.
45func registerClock(name string, fn clockFunc) {
46 if _, exists := clockFuncTable[name]; exists {
47 panic("calc: duplicate clock function registration: " + name)
48 }
49 clockFuncTable[name] = fn
50}
51
52// nodeFunc is the signature for a function that needs to see its own
53// argument nodes rather than only the flattened, boundary-free []Value
54// evalCallArgs already produces for an ordinary Func — because an
55// ordinary Func's []Value has thrown away information the function
56// actually needs. Two distinct things get thrown away, and each has its
57// own nodeFunc registrants:
58//
59// - Shape: VLOOKUP, INDEX and MATCH (fn_lookup.go) all turn a
60// row/column offset into a position within a table/array argument,
61// and by the time evalCallArgs has flattened every argument into one
62// shared slice, both a range's width/height AND the boundary between
63// one argument's cells and the next are gone — unrecoverable, not
64// merely inconvenient.
65// - Origin: SUM, AVERAGE, COUNT, MIN and MAX (fn_math.go) all need to
66// know whether a given value was read from a cell reference or typed
67// directly into the formula — Excel treats a logical or text value
68// differently depending on which (fn_math.go's taggedValue doc
69// comment has the concrete example and the Excel documentation
70// wording this is drawn from) — and a flattened []Value has already
71// erased that distinction too.
72//
73// Both kinds see the raw argument AST nodes instead and evaluate what
74// they need themselves, the same way evalCall would.
75type nodeFunc func(argNodes []Node, env Env) Value
76
77var nodeFuncTable = map[string]nodeFunc{}
78
79// registerNode is Register's counterpart for nodeFunc. Unexported for the
80// same reason as registerClock.
81func registerNode(name string, fn nodeFunc) {
82 if _, exists := nodeFuncTable[name]; exists {
83 panic("calc: duplicate node function registration: " + name)
84 }
85 nodeFuncTable[name] = fn
86}
87
88// isRegisteredFunc reports whether name resolves in any of the three
89// dispatch tables evalCall checks — ordinary, clock, or node — so
90// corpus_test.go's TestExclusionsAreRealFunctions can check "is this
91// really a function" without needing to know which table a given name
92// happens to live in.
93func isRegisteredFunc(name string) bool {
94 if _, ok := funcTable[name]; ok {
95 return true
96 }
97 if _, ok := clockFuncTable[name]; ok {
98 return true
99 }
100 if _, ok := nodeFuncTable[name]; ok {
101 return true
102 }
103 return false
104}
105
106// errValue is shorthand for the error Value most functions return on a
107// bad argument count or a coercion failure — argument-count and type
108// errors are #VALUE! throughout this package's function library, per the
109// plan's own convention.
110func errValue(k ErrKind) Value { return Value{Kind: KindErr, Err: k} }
111