IFS, SWITCH, XOR, IFNA, ISBLANK, ISNUMBER, ISTEXT, ISLOGICAL, ISERROR, ISERR and ISNA.
- commit
- c3dcac1729e400af42ec4cc30af0a3c01b3240cf
- parent
- 1b1b376f3aca34d44babdcae3cc2d8800977ad7b
- author
- Dami <dami.etoile@gmail.com>
| A | internal/ | 87 | ++++++++++++++++++++++++++++++ |
| A | internal/ | 40 | ++++++++++++++ |
@@ -0,0 +1,87 @@
1+package sheet2+3+func init() {4+ define(5+ &FuncDef{Name: "IFS", Args: "condition1, value1, [condition2, value2, ...]", Desc: "The value of the first true condition", Min: 2, Max: -1, step: 2,6+ eval: func(args []Node, get lookup) Value {7+ for i := 0; i+1 < len(args); i += 2 {8+ c, err := numArg(args[i], get)9+ if err != nil {10+ return *err11+ }12+ if c != 0 {13+ return eval(args[i+1], get)14+ }15+ }16+ return ErrNA17+ }, format: func(args []Node, infer func(Node) Format) Format {18+ var vals []Node19+ for i := 1; i < len(args); i += 2 {20+ vals = append(vals, args[i])21+ }22+ return inherit(vals, infer)23+ }},24+ &FuncDef{Name: "SWITCH", Args: "expression, case1, value1, [case2, value2, ...], [default]", Desc: "The value for the first case equal to an expression", Min: 3, Max: -1,25+ eval: func(args []Node, get lookup) Value {26+ x := eval(args[0], get)27+ if x.Kind == Error {28+ return x29+ }30+ i := 131+ for ; i+1 < len(args); i += 2 {32+ c := eval(args[i], get)33+ if c.Kind == Error {34+ return c35+ }36+ if sameKind(x, c) && compare(x, c) == 0 {37+ return eval(args[i+1], get)38+ }39+ }40+ if i < len(args) {41+ return eval(args[i], get)42+ }43+ return ErrNA44+ }},45+ &FuncDef{Name: "XOR", Args: "logical1, [logical2, ...]", Desc: "TRUE if an odd number of arguments are true", Min: 1, Max: -1,46+ eval: logical(func(t, _ int) bool { return t%2 == 1 })},47+ &FuncDef{Name: "IFNA", Args: "value, value_if_na", Desc: "A fallback when a value is #N/A", Min: 2, Max: 2,48+ eval: func(args []Node, get lookup) Value {49+ v := eval(args[0], get)50+ if v == ErrNA {51+ return eval(args[1], get)52+ }53+ return v54+ }, format: inherit},55+ &FuncDef{Name: "ISBLANK", Args: "value", Desc: "TRUE if a cell is empty", Min: 1, Max: 1,56+ eval: is(func(v Value) bool { return v.Kind == Empty })},57+ &FuncDef{Name: "ISNUMBER", Args: "value", Desc: "TRUE if a value is a number", Min: 1, Max: 1,58+ eval: is(func(v Value) bool { return v.Kind == Number })},59+ &FuncDef{Name: "ISTEXT", Args: "value", Desc: "TRUE if a value is text", Min: 1, Max: 1,60+ eval: is(func(v Value) bool { return v.Kind == Text })},61+ &FuncDef{Name: "ISLOGICAL", Args: "value", Desc: "TRUE if a value is TRUE or FALSE", Min: 1, Max: 1,62+ eval: is(func(v Value) bool { return v.Kind == Bool })},63+ &FuncDef{Name: "ISERROR", Args: "value", Desc: "TRUE if a value is any error", Min: 1, Max: 1,64+ eval: is(func(v Value) bool { return v.Kind == Error })},65+ &FuncDef{Name: "ISERR", Args: "value", Desc: "TRUE if a value is an error other than #N/A", Min: 1, Max: 1,66+ eval: is(func(v Value) bool { return v.Kind == Error && v != ErrNA })},67+ &FuncDef{Name: "ISNA", Args: "value", Desc: "TRUE if a value is #N/A", Min: 1, Max: 1,68+ eval: is(func(v Value) bool { return v == ErrNA })},69+ )70+}71+72+// is builds the IS functions, which never return an error themselves.73+func is(test func(Value) bool) func([]Node, lookup) Value {74+ return func(args []Node, get lookup) Value {75+ return boolean(test(eval(args[0], get)))76+ }77+}78+79+// sameKind reports whether two values can be equal in SWITCH and exact80+// lookups: numbers only equal numbers, text only text. A blank is 0 or "".81+func sameKind(a, b Value) bool {82+ ka, kb := a.Kind, b.Kind83+ if ka == Empty || kb == Empty {84+ return true85+ }86+ return ka == kb87+}@@ -0,0 +1,40 @@
1+package sheet2+3+import "testing"4+5+func TestLogicFunctions(t *testing.T) {6+ checkFormulas(t, fixture(t), []fnCase{7+ {`=IFS(A1>20, "big", A1>5, "medium")`, txt("medium")},8+ {`=IFS(A1>20, "big")`, ErrNA},9+ {`=IFS(A4, 1)`, ErrValue},10+ {`=SWITCH(B2, "north", 1, "south", 2)`, num(2)},11+ {`=SWITCH(B4, "north", 1, "south", 2, 0)`, num(0)},12+ {`=SWITCH(B4, "north", 1)`, ErrNA},13+ {`=SWITCH(1, "1", "text", 1, "number")`, txt("number")},14+ {"=XOR(TRUE, FALSE)", boolean(true)},15+ {"=XOR(TRUE, TRUE)", boolean(false)},16+ {"=XOR(A1:A3)", boolean(true)},17+ {`=IFNA(NA(), "none")`, txt("none")},18+ {"=IFNA(1/0, 0)", ErrDiv0},19+ {"=ISBLANK(A5)", boolean(true)},20+ {"=ISBLANK(B6)", boolean(false)}, // empty text isn't blank21+ {"=ISNUMBER(A1)", boolean(true)},22+ {"=ISNUMBER(A4)", boolean(false)},23+ {"=ISNUMBER(F1)", boolean(true)}, // dates are numbers24+ {"=ISTEXT(A4)", boolean(true)},25+ {"=ISLOGICAL(A6)", boolean(true)},26+ {"=ISERROR(1/0)", boolean(true)},27+ {"=ISERROR(NA())", boolean(true)},28+ {"=ISERR(NA())", boolean(false)},29+ {"=ISNA(NA())", boolean(true)},30+ {"=ISNA(1/0)", boolean(false)},31+ })32+}33+34+func TestStepArity(t *testing.T) {35+ for _, in := range []string{`=SUMIFS(C1:C5, B1:B5)`, `=COUNTIFS(B1:B5, "a", C1:C5)`, `=IFS(TRUE, 1, FALSE)`} {36+ if err := New().Set(at("A1"), in); err == nil {37+ t.Errorf("Set(%q) accepted", in)38+ }39+ }40+}