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
3import "strings"
4
5func init() {
6 Register("IF", fnIf)
7 Register("IFS", fnIfs)
8 Register("AND", fnAnd)
9 Register("OR", fnOr)
10 Register("NOT", fnNot)
11 Register("IFERROR", fnIfError)
12}
13
14// toBool coerces v the way IF's condition argument (and AND/OR/IFS's
15// operands) do: a boolean as itself, a number as nonzero, an empty cell
16// as false (Excel's own coercion for a blank condition), and text only
17// if it is literally "TRUE" or "FALSE" (case-insensitive) — Excel does
18// not treat an arbitrary string as truthy/falsy in a logical context.
19func toBool(v Value) (bool, ErrKind) {
20 switch v.Kind {
21 case KindBool:
22 return v.Bool, ErrNone
23 case KindNum:
24 return v.Num != 0, ErrNone
25 case KindEmpty:
26 return false, ErrNone
27 case KindStr:
28 switch strings.ToUpper(v.Str) {
29 case "TRUE":
30 return true, ErrNone
31 case "FALSE":
32 return false, ErrNone
33 }
34 return false, ErrValue
35 default:
36 return false, ErrValue
37 }
38}
39
40// fnIf implements IF(condition, value_if_true, [value_if_false]).
41//
42// evalCall has already evaluated every argument by the time this runs —
43// Func receives finished Values, not lazy thunks (see eval_call.go's doc
44// comment on evalCall) — so the branch NOT selected has already been
45// computed too. That costs nothing observable here: fnIf only ever
46// returns the ALREADY-COMPUTED value of the branch it selects, never
47// re-evaluates anything, and formulas have no side effects
48// (eval_arith.go's evalBinary), so IF(FALSE, 1/0, 5) still correctly
49// returns 5 even though 1/0 was computed as #DIV/0! along the way and
50// simply discarded — pinned by a corpus entry precisely because it looks
51// like it should be wrong.
52func fnIf(args []Value) Value {
53 if len(args) != 2 && len(args) != 3 {
54 return errValue(ErrValue)
55 }
56 if args[0].Kind == KindErr {
57 return args[0]
58 }
59 cond, ek := toBool(args[0])
60 if ek != ErrNone {
61 return errValue(ek)
62 }
63 if cond {
64 return args[1]
65 }
66 if len(args) == 3 {
67 return args[2]
68 }
69 return Value{Kind: KindBool, Bool: false}
70}
71
72// fnIfs implements IFS(cond1, value1, cond2, value2, ...): the value
73// paired with the first true condition, scanned left to right. Only the
74// conditions actually inspected before the first true one can fail this
75// call — a later pair's condition (already evaluated by evalCall, per
76// fnIf's doc comment) is never looked at once an earlier one matches. No
77// condition true at all is #N/A, matching Excel.
78func fnIfs(args []Value) Value {
79 if len(args) < 2 || len(args)%2 != 0 {
80 return errValue(ErrValue)
81 }
82 for i := 0; i < len(args); i += 2 {
83 c := args[i]
84 if c.Kind == KindErr {
85 return c
86 }
87 b, ek := toBool(c)
88 if ek != ErrNone {
89 return errValue(ek)
90 }
91 if b {
92 return args[i+1]
93 }
94 }
95 return errValue(ErrNA)
96}
97
98func fnAnd(args []Value) Value {
99 if len(args) == 0 {
100 return errValue(ErrValue)
101 }
102 result := true
103 for _, a := range args {
104 if a.Kind == KindErr {
105 return a
106 }
107 b, ek := toBool(a)
108 if ek != ErrNone {
109 return errValue(ek)
110 }
111 result = result && b
112 }
113 return Value{Kind: KindBool, Bool: result}
114}
115
116func fnOr(args []Value) Value {
117 if len(args) == 0 {
118 return errValue(ErrValue)
119 }
120 result := false
121 for _, a := range args {
122 if a.Kind == KindErr {
123 return a
124 }
125 b, ek := toBool(a)
126 if ek != ErrNone {
127 return errValue(ek)
128 }
129 result = result || b
130 }
131 return Value{Kind: KindBool, Bool: result}
132}
133
134func fnNot(args []Value) Value {
135 if len(args) != 1 {
136 return errValue(ErrValue)
137 }
138 if args[0].Kind == KindErr {
139 return args[0]
140 }
141 b, ek := toBool(args[0])
142 if ek != ErrNone {
143 return errValue(ek)
144 }
145 return Value{Kind: KindBool, Bool: !b}
146}
147
148// fnIfError implements IFERROR(value, value_if_error): the one place a
149// formula is allowed to look at an error argument instead of becoming
150// it, which is exactly why evalCall does not short-circuit on an error
151// argument (see eval_call.go's doc comment on evalCall).
152func fnIfError(args []Value) Value {
153 if len(args) != 2 {
154 return errValue(ErrValue)
155 }
156 if args[0].Kind == KindErr {
157 return args[1]
158 }
159 return args[0]
160}
161