diff --git a/calc.go b/calc.go index ba9085e..60ca0cb 100644 --- a/calc.go +++ b/calc.go @@ -357,6 +357,8 @@ type formulaFuncs struct { // HLOOKUP // IF // IFERROR +// IFNA +// IFS // IMABS // IMAGINARY // IMARGUMENT @@ -754,12 +756,12 @@ func (f *File) evalInfixExpFunc(sheet, cell string, token, nextToken efp.Token, } // calcPow evaluate exponentiation arithmetic operations. -func calcPow(rOpd, lOpd string, opdStack *Stack) error { - lOpdVal, err := strconv.ParseFloat(lOpd, 64) +func calcPow(rOpd, lOpd efp.Token, opdStack *Stack) error { + lOpdVal, err := strconv.ParseFloat(lOpd.TValue, 64) if err != nil { return err } - rOpdVal, err := strconv.ParseFloat(rOpd, 64) + rOpdVal, err := strconv.ParseFloat(rOpd.TValue, 64) if err != nil { return err } @@ -769,54 +771,106 @@ func calcPow(rOpd, lOpd string, opdStack *Stack) error { } // calcEq evaluate equal arithmetic operations. -func calcEq(rOpd, lOpd string, opdStack *Stack) error { +func calcEq(rOpd, lOpd efp.Token, opdStack *Stack) error { opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(rOpd == lOpd)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) return nil } // calcNEq evaluate not equal arithmetic operations. -func calcNEq(rOpd, lOpd string, opdStack *Stack) error { +func calcNEq(rOpd, lOpd efp.Token, opdStack *Stack) error { opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(rOpd != lOpd)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) return nil } // calcL evaluate less than arithmetic operations. -func calcL(rOpd, lOpd string, opdStack *Stack) error { - opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(strings.Compare(lOpd, rOpd) == -1)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) +func calcL(rOpd, lOpd efp.Token, opdStack *Stack) error { + if rOpd.TSubType == efp.TokenSubTypeNumber && lOpd.TSubType == efp.TokenSubTypeNumber { + lOpdVal, _ := strconv.ParseFloat(lOpd.TValue, 64) + rOpdVal, _ := strconv.ParseFloat(rOpd.TValue, 64) + opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(lOpdVal < rOpdVal)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + } + if rOpd.TSubType == efp.TokenSubTypeText && lOpd.TSubType == efp.TokenSubTypeText { + opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(strings.Compare(lOpd.TValue, rOpd.TValue) == -1)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + } + if rOpd.TSubType == efp.TokenSubTypeNumber && lOpd.TSubType == efp.TokenSubTypeText { + opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(false)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + } + if rOpd.TSubType == efp.TokenSubTypeText && lOpd.TSubType == efp.TokenSubTypeNumber { + opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(true)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + } return nil } // calcLe evaluate less than or equal arithmetic operations. -func calcLe(rOpd, lOpd string, opdStack *Stack) error { - opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(strings.Compare(lOpd, rOpd) != 1)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) +func calcLe(rOpd, lOpd efp.Token, opdStack *Stack) error { + if rOpd.TSubType == efp.TokenSubTypeNumber && lOpd.TSubType == efp.TokenSubTypeNumber { + lOpdVal, _ := strconv.ParseFloat(lOpd.TValue, 64) + rOpdVal, _ := strconv.ParseFloat(rOpd.TValue, 64) + opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(lOpdVal <= rOpdVal)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + } + if rOpd.TSubType == efp.TokenSubTypeText && lOpd.TSubType == efp.TokenSubTypeText { + opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(strings.Compare(lOpd.TValue, rOpd.TValue) != 1)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + } + if rOpd.TSubType == efp.TokenSubTypeNumber && lOpd.TSubType == efp.TokenSubTypeText { + opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(false)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + } + if rOpd.TSubType == efp.TokenSubTypeText && lOpd.TSubType == efp.TokenSubTypeNumber { + opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(true)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + } return nil } // calcG evaluate greater than or equal arithmetic operations. -func calcG(rOpd, lOpd string, opdStack *Stack) error { - opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(strings.Compare(lOpd, rOpd) == 1)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) +func calcG(rOpd, lOpd efp.Token, opdStack *Stack) error { + if rOpd.TSubType == efp.TokenSubTypeNumber && lOpd.TSubType == efp.TokenSubTypeNumber { + lOpdVal, _ := strconv.ParseFloat(lOpd.TValue, 64) + rOpdVal, _ := strconv.ParseFloat(rOpd.TValue, 64) + opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(lOpdVal > rOpdVal)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + } + if rOpd.TSubType == efp.TokenSubTypeText && lOpd.TSubType == efp.TokenSubTypeText { + opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(strings.Compare(lOpd.TValue, rOpd.TValue) == 1)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + } + if rOpd.TSubType == efp.TokenSubTypeNumber && lOpd.TSubType == efp.TokenSubTypeText { + opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(true)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + } + if rOpd.TSubType == efp.TokenSubTypeText && lOpd.TSubType == efp.TokenSubTypeNumber { + opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(false)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + } return nil } // calcGe evaluate greater than or equal arithmetic operations. -func calcGe(rOpd, lOpd string, opdStack *Stack) error { - opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(strings.Compare(lOpd, rOpd) != -1)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) +func calcGe(rOpd, lOpd efp.Token, opdStack *Stack) error { + if rOpd.TSubType == efp.TokenSubTypeNumber && lOpd.TSubType == efp.TokenSubTypeNumber { + lOpdVal, _ := strconv.ParseFloat(lOpd.TValue, 64) + rOpdVal, _ := strconv.ParseFloat(rOpd.TValue, 64) + opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(lOpdVal >= rOpdVal)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + } + if rOpd.TSubType == efp.TokenSubTypeText && lOpd.TSubType == efp.TokenSubTypeText { + opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(strings.Compare(lOpd.TValue, rOpd.TValue) != -1)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + } + if rOpd.TSubType == efp.TokenSubTypeNumber && lOpd.TSubType == efp.TokenSubTypeText { + opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(true)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + } + if rOpd.TSubType == efp.TokenSubTypeText && lOpd.TSubType == efp.TokenSubTypeNumber { + opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(false)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + } return nil } // calcSplice evaluate splice '&' operations. -func calcSplice(rOpd, lOpd string, opdStack *Stack) error { - opdStack.Push(efp.Token{TValue: lOpd + rOpd, TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) +func calcSplice(rOpd, lOpd efp.Token, opdStack *Stack) error { + opdStack.Push(efp.Token{TValue: lOpd.TValue + rOpd.TValue, TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) return nil } // calcAdd evaluate addition arithmetic operations. -func calcAdd(rOpd, lOpd string, opdStack *Stack) error { - lOpdVal, err := strconv.ParseFloat(lOpd, 64) +func calcAdd(rOpd, lOpd efp.Token, opdStack *Stack) error { + lOpdVal, err := strconv.ParseFloat(lOpd.TValue, 64) if err != nil { return err } - rOpdVal, err := strconv.ParseFloat(rOpd, 64) + rOpdVal, err := strconv.ParseFloat(rOpd.TValue, 64) if err != nil { return err } @@ -826,12 +880,12 @@ func calcAdd(rOpd, lOpd string, opdStack *Stack) error { } // calcSubtract evaluate subtraction arithmetic operations. -func calcSubtract(rOpd, lOpd string, opdStack *Stack) error { - lOpdVal, err := strconv.ParseFloat(lOpd, 64) +func calcSubtract(rOpd, lOpd efp.Token, opdStack *Stack) error { + lOpdVal, err := strconv.ParseFloat(lOpd.TValue, 64) if err != nil { return err } - rOpdVal, err := strconv.ParseFloat(rOpd, 64) + rOpdVal, err := strconv.ParseFloat(rOpd.TValue, 64) if err != nil { return err } @@ -841,12 +895,12 @@ func calcSubtract(rOpd, lOpd string, opdStack *Stack) error { } // calcMultiply evaluate multiplication arithmetic operations. -func calcMultiply(rOpd, lOpd string, opdStack *Stack) error { - lOpdVal, err := strconv.ParseFloat(lOpd, 64) +func calcMultiply(rOpd, lOpd efp.Token, opdStack *Stack) error { + lOpdVal, err := strconv.ParseFloat(lOpd.TValue, 64) if err != nil { return err } - rOpdVal, err := strconv.ParseFloat(rOpd, 64) + rOpdVal, err := strconv.ParseFloat(rOpd.TValue, 64) if err != nil { return err } @@ -856,12 +910,12 @@ func calcMultiply(rOpd, lOpd string, opdStack *Stack) error { } // calcDiv evaluate division arithmetic operations. -func calcDiv(rOpd, lOpd string, opdStack *Stack) error { - lOpdVal, err := strconv.ParseFloat(lOpd, 64) +func calcDiv(rOpd, lOpd efp.Token, opdStack *Stack) error { + lOpdVal, err := strconv.ParseFloat(lOpd.TValue, 64) if err != nil { return err } - rOpdVal, err := strconv.ParseFloat(rOpd, 64) + rOpdVal, err := strconv.ParseFloat(rOpd.TValue, 64) if err != nil { return err } @@ -887,7 +941,7 @@ func calculate(opdStack *Stack, opt efp.Token) error { result := 0 - opdVal opdStack.Push(efp.Token{TValue: fmt.Sprintf("%g", result), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) } - tokenCalcFunc := map[string]func(rOpd, lOpd string, opdStack *Stack) error{ + tokenCalcFunc := map[string]func(rOpd, lOpd efp.Token, opdStack *Stack) error{ "^": calcPow, "*": calcMultiply, "/": calcDiv, @@ -906,7 +960,7 @@ func calculate(opdStack *Stack, opt efp.Token) error { } rOpd := opdStack.Pop().(efp.Token) lOpd := opdStack.Pop().(efp.Token) - if err := calcSubtract(rOpd.TValue, lOpd.TValue, opdStack); err != nil { + if err := calcSubtract(rOpd, lOpd, opdStack); err != nil { return err } } @@ -917,7 +971,8 @@ func calculate(opdStack *Stack, opt efp.Token) error { } rOpd := opdStack.Pop().(efp.Token) lOpd := opdStack.Pop().(efp.Token) - if err := fn(rOpd.TValue, lOpd.TValue, opdStack); err != nil { + + if err := fn(rOpd, lOpd, opdStack); err != nil { return err } } @@ -6056,6 +6111,44 @@ func (fn *formulaFuncs) IFERROR(argsList *list.List) formulaArg { return argsList.Back().Value.(formulaArg) } +// IFNA function tests if an initial supplied value (or expression) evaluates +// to the Excel #N/A error. If so, the function returns a second supplied +// value; Otherwise the function returns the first supplied value. The syntax +// of the function is: +// +// IFNA(value,value_if_na) +// +func (fn *formulaFuncs) IFNA(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "IFNA requires 2 arguments") + } + arg := argsList.Front().Value.(formulaArg) + if arg.Type == ArgError && arg.Value() == formulaErrorNA { + return argsList.Back().Value.(formulaArg) + } + return arg +} + +// IFS function tests a number of supplied conditions and returns the result +// corresponding to the first condition that evaluates to TRUE. If none of +// the supplied conditions evaluate to TRUE, the function returns the #N/A +// error. +// +// IFS(logical_test1,value_if_true1,[logical_test2,value_if_true2],...) +// +func (fn *formulaFuncs) IFS(argsList *list.List) formulaArg { + if argsList.Len() < 2 { + return newErrorFormulaArg(formulaErrorVALUE, "IFS requires at least 2 arguments") + } + for arg := argsList.Front(); arg != nil; arg = arg.Next() { + if arg.Value.(formulaArg).ToBool().Number == 1 { + return arg.Next().Value.(formulaArg) + } + arg = arg.Next() + } + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) +} + // NOT function returns the opposite to a supplied logical value. The syntax // of the function is: // diff --git a/calc_test.go b/calc_test.go index abf6afa..144811c 100644 --- a/calc_test.go +++ b/calc_test.go @@ -34,22 +34,34 @@ func TestCalcCellValue(t *testing.T) { {nil, nil, nil, "Feb", "South 2", 45500}, } mathCalc := map[string]string{ - "=2^3": "8", - "=1=1": "TRUE", - "=1=2": "FALSE", - "=1<2": "TRUE", - "=3<2": "FALSE", - "=2<=3": "TRUE", - "=2<=1": "FALSE", - "=2>1": "TRUE", - "=2>3": "FALSE", - "=2>=1": "TRUE", - "=2>=3": "FALSE", - "=1&2": "12", - "=15%": "0.15", - "=1+20%": "1.2", - `="A"="A"`: "TRUE", - `="A"<>"A"`: "FALSE", + "=2^3": "8", + "=1=1": "TRUE", + "=1=2": "FALSE", + "=1<2": "TRUE", + "=3<2": "FALSE", + "=1<\"-1\"": "TRUE", + "=\"-1\"<1": "FALSE", + "=\"-1\"<\"-2\"": "TRUE", + "=2<=3": "TRUE", + "=2<=1": "FALSE", + "=1<=\"-1\"": "TRUE", + "=\"-1\"<=1": "FALSE", + "=\"-1\"<=\"-2\"": "TRUE", + "=2>1": "TRUE", + "=2>3": "FALSE", + "=1>\"-1\"": "FALSE", + "=\"-1\">-1": "TRUE", + "=\"-1\">\"-2\"": "FALSE", + "=2>=1": "TRUE", + "=2>=3": "FALSE", + "=1>=\"-1\"": "FALSE", + "=\"-1\">=-1": "TRUE", + "=\"-1\">=\"-2\"": "FALSE", + "=1&2": "12", + "=15%": "0.15", + "=1+20%": "1.2", + `="A"="A"`: "TRUE", + `="A"<>"A"`: "FALSE", // Engineering Functions // BESSELI "=BESSELI(4.5,1)": "15.389222753735925", @@ -922,6 +934,13 @@ func TestCalcCellValue(t *testing.T) { "=IFERROR(1/2,0)": "0.5", "=IFERROR(ISERROR(),0)": "0", "=IFERROR(1/0,0)": "0", + // IFNA + "=IFNA(1,\"not found\")": "1", + "=IFNA(NA(),\"not found\")": "not found", + // IFS + "=IFS(4>1,5/4,4<-1,-5/4,TRUE,0)": "1.25", + "=IFS(-2>1,5/-2,-2<-1,-5/-2,TRUE,0)": "2.5", + "=IFS(0>1,5/0,0<-1,-5/0,TRUE,0)": "0", // NOT "=NOT(FALSE())": "TRUE", "=NOT(\"false\")": "TRUE", @@ -1313,7 +1332,17 @@ func TestCalcCellValue(t *testing.T) { assert.Equal(t, expected, result, formula) } mathCalcError := map[string]string{ - "=1/0": "#DIV/0!", + "=1/0": "#DIV/0!", + "1^\"text\"": "strconv.ParseFloat: parsing \"text\": invalid syntax", + "\"text\"^1": "strconv.ParseFloat: parsing \"text\": invalid syntax", + "1+\"text\"": "strconv.ParseFloat: parsing \"text\": invalid syntax", + "\"text\"+1": "strconv.ParseFloat: parsing \"text\": invalid syntax", + "1-\"text\"": "strconv.ParseFloat: parsing \"text\": invalid syntax", + "\"text\"-1": "strconv.ParseFloat: parsing \"text\": invalid syntax", + "1*\"text\"": "strconv.ParseFloat: parsing \"text\": invalid syntax", + "\"text\"*1": "strconv.ParseFloat: parsing \"text\": invalid syntax", + "1/\"text\"": "strconv.ParseFloat: parsing \"text\": invalid syntax", + "\"text\"/1": "strconv.ParseFloat: parsing \"text\": invalid syntax", // Engineering Functions // BESSELI "=BESSELI()": "BESSELI requires 2 numeric arguments", @@ -2023,6 +2052,10 @@ func TestCalcCellValue(t *testing.T) { "=FALSE(A1)": "FALSE takes no arguments", // IFERROR "=IFERROR()": "IFERROR requires 2 arguments", + // IFNA + "=IFNA()": "IFNA requires 2 arguments", + // IFS + "=IFS()": "IFS requires at least 2 arguments", // NOT "=NOT()": "NOT requires 1 argument", "=NOT(NOT())": "NOT requires 1 argument", @@ -2598,40 +2631,6 @@ func TestCalcWithDefinedName(t *testing.T) { } -func TestCalcArithmeticOperations(t *testing.T) { - opdStack := NewStack() - for _, test := range [][]string{{"1", "text", "FALSE"}, {"text", "1", "TRUE"}} { - assert.NoError(t, calcL(test[0], test[1], opdStack)) - assert.Equal(t, test[2], opdStack.Peek().(efp.Token).TValue) - opdStack.Empty() - assert.NoError(t, calcLe(test[0], test[1], opdStack)) - assert.Equal(t, test[2], opdStack.Peek().(efp.Token).TValue) - opdStack.Empty() - } - for _, test := range [][]string{{"1", "text", "TRUE"}, {"text", "1", "FALSE"}} { - assert.NoError(t, calcG(test[0], test[1], opdStack)) - assert.Equal(t, test[2], opdStack.Peek().(efp.Token).TValue) - opdStack.Empty() - assert.NoError(t, calcGe(test[0], test[1], opdStack)) - assert.Equal(t, test[2], opdStack.Peek().(efp.Token).TValue) - opdStack.Empty() - } - - err := `strconv.ParseFloat: parsing "text": invalid syntax` - assert.EqualError(t, calcPow("1", "text", nil), err) - assert.EqualError(t, calcPow("text", "1", nil), err) - assert.EqualError(t, calcAdd("1", "text", nil), err) - assert.EqualError(t, calcAdd("text", "1", nil), err) - assert.EqualError(t, calcAdd("1", "text", nil), err) - assert.EqualError(t, calcAdd("text", "1", nil), err) - assert.EqualError(t, calcSubtract("1", "text", nil), err) - assert.EqualError(t, calcSubtract("text", "1", nil), err) - assert.EqualError(t, calcMultiply("1", "text", nil), err) - assert.EqualError(t, calcMultiply("text", "1", nil), err) - assert.EqualError(t, calcDiv("1", "text", nil), err) - assert.EqualError(t, calcDiv("text", "1", nil), err) -} - func TestCalcISBLANK(t *testing.T) { argsList := list.New() argsList.PushBack(formulaArg{