| 1 | package calc |
| 2 | |
| 3 | import "strings" |
| 4 | |
| 5 | func 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. |
| 19 | func 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. |
| 52 | func 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. |
| 78 | func 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 | |
| 98 | func 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 | |
| 116 | func 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 | |
| 134 | func 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). |
| 152 | func 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 | |