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// fn_is.go: the IS* predicates.
4//
5// These are what somebody wrapped a formula in to stop a sheet showing
6// an error, so they are the functions an imported workbook is most
7// likely to use around the ones it already used. Without them the wrap
8// itself becomes the error: `=IF(ISBLANK(A1),"",B1/A1)` imports
9// faithfully and evaluates to #NAME? in every row.
10//
11// Ordinary Funcs, not nodeFuncs, and that is a real decision rather
12// than the easy one. evalCallArgs preserves both distinctions these
13// need: KindEmpty survives flattening (it is the zero ValueKind
14// precisely so a blank cell stays distinguishable from a zero — see
15// eval.go), and evalCall does NOT short-circuit an error argument, so
16// an error reaches ISERROR as a value rather than replacing the call.
17// The one thing flattening loses is a range's shape, and none of these
18// takes a range.
19
20func init() {
21 Register("ISBLANK", fnIsBlank)
22 Register("ISNUMBER", fnIsNumber)
23 Register("ISTEXT", fnIsText)
24 Register("ISERROR", fnIsError)
25}
26
27// oneArg is the shared argument check. Exactly one value, never a
28// range: `ISBLANK(A1:A3)` flattens to three arguments here, and Excel
29// answers that with an array this engine has no representation for. One
30// honest #VALUE! is better than silently testing the first cell and
31// reporting on two the caller never mentioned.
32func oneArg(args []Value) (Value, bool) {
33 if len(args) != 1 {
34 return Value{}, false
35 }
36 return args[0], true
37}
38
39// fnIsBlank is TRUE only for a genuinely empty cell.
40//
41// Not for the empty string, and the difference is the whole point of the
42// function: `=IF(A1="","",…)` and `=IF(ISBLANK(A1),"",…)` disagree on a
43// cell holding a formula that returned "", which is exactly the case
44// somebody reached for ISBLANK to tell apart.
45func fnIsBlank(args []Value) Value {
46 v, ok := oneArg(args)
47 if !ok {
48 return errValue(ErrValue)
49 }
50 return boolValue(v.Kind == KindEmpty)
51}
52
53// fnIsNumber. A boolean is not a number here, matching Excel: TRUE
54// coerces to 1 in arithmetic but ISNUMBER(TRUE) is FALSE.
55func fnIsNumber(args []Value) Value {
56 v, ok := oneArg(args)
57 if !ok {
58 return errValue(ErrValue)
59 }
60 return boolValue(v.Kind == KindNum)
61}
62
63func fnIsText(args []Value) Value {
64 v, ok := oneArg(args)
65 if !ok {
66 return errValue(ErrValue)
67 }
68 return boolValue(v.Kind == KindStr)
69}
70
71// fnIsError is TRUE for every error kind, #N/A included.
72//
73// Excel has ISERR too, which is ISERROR minus #N/A. It is deliberately
74// not here: the pair is easy to register and easy to get backwards, and
75// nothing has asked for the distinction. Adding it later costs one line;
76// shipping it now with the wrong one of the two doing the wrong thing
77// costs somebody a wrong answer they have no reason to check.
78func fnIsError(args []Value) Value {
79 v, ok := oneArg(args)
80 if !ok {
81 return errValue(ErrValue)
82 }
83 return boolValue(v.Kind == KindErr)
84}
85
86// boolValue is the counterpart of errValue for the predicate family.
87func boolValue(b bool) Value { return Value{Kind: KindBool, Bool: b} }
88