diff options
author | xuri <xuri.me@gmail.com> | 2022-04-24 23:43:19 +0800 |
---|---|---|
committer | xuri <xuri.me@gmail.com> | 2022-04-24 23:43:19 +0800 |
commit | 0f7a0c8f3b5c9abd5858cab80902296d1639625f (patch) | |
tree | 04123277c5e08c2608b0ae4498fe3fad19ba8d23 | |
parent | 81d9362b4f1cf765712b61837d5b5831d1cd0c58 (diff) |
Optimization formula calculation performance and update README card badge
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | README_zh.md | 2 | ||||
-rw-r--r-- | calc.go | 459 | ||||
-rw-r--r-- | calc_test.go | 125 |
4 files changed, 261 insertions, 327 deletions
@@ -3,7 +3,7 @@ <p align="center"> <a href="https://github.com/xuri/excelize/actions/workflows/go.yml"><img src="https://github.com/xuri/excelize/actions/workflows/go.yml/badge.svg" alt="Build Status"></a> <a href="https://codecov.io/gh/qax-os/excelize"><img src="https://codecov.io/gh/qax-os/excelize/branch/master/graph/badge.svg" alt="Code Coverage"></a> - <a href="https://goreportcard.com/report/github.com/xuri/excelize"><img src="https://goreportcard.com/badge/github.com/xuri/excelize" alt="Go Report Card"></a> + <a href="https://goreportcard.com/report/github.com/xuri/excelize/v2"><img src="https://goreportcard.com/badge/github.com/xuri/excelize/v2" alt="Go Report Card"></a> <a href="https://pkg.go.dev/github.com/xuri/excelize/v2"><img src="https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white" alt="go.dev"></a> <a href="https://opensource.org/licenses/BSD-3-Clause"><img src="https://img.shields.io/badge/license-bsd-orange.svg" alt="Licenses"></a> <a href="https://www.paypal.com/paypalme/xuri"><img src="https://img.shields.io/badge/Donate-PayPal-green.svg" alt="Donate"></a> diff --git a/README_zh.md b/README_zh.md index dafdd93..d67b63c 100644 --- a/README_zh.md +++ b/README_zh.md @@ -3,7 +3,7 @@ <p align="center"> <a href="https://github.com/xuri/excelize/actions/workflows/go.yml"><img src="https://github.com/xuri/excelize/actions/workflows/go.yml/badge.svg" alt="Build Status"></a> <a href="https://codecov.io/gh/qax-os/excelize"><img src="https://codecov.io/gh/qax-os/excelize/branch/master/graph/badge.svg" alt="Code Coverage"></a> - <a href="https://goreportcard.com/report/github.com/xuri/excelize"><img src="https://goreportcard.com/badge/github.com/xuri/excelize" alt="Go Report Card"></a> + <a href="https://goreportcard.com/report/github.com/xuri/excelize/v2"><img src="https://goreportcard.com/badge/github.com/xuri/excelize/v2" alt="Go Report Card"></a> <a href="https://pkg.go.dev/github.com/xuri/excelize/v2"><img src="https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white" alt="go.dev"></a> <a href="https://opensource.org/licenses/BSD-3-Clause"><img src="https://img.shields.io/badge/license-bsd-orange.svg" alt="Licenses"></a> <a href="https://www.paypal.com/paypalme/xuri"><img src="https://img.shields.io/badge/Donate-PayPal-green.svg" alt="Donate"></a> @@ -736,7 +736,7 @@ type formulaFuncs struct { func (f *File) CalcCellValue(sheet, cell string) (result string, err error) { var ( formula string - token efp.Token + token formulaArg ) if formula, err = f.GetCellFormula(sheet, cell); err != nil { return @@ -749,7 +749,7 @@ func (f *File) CalcCellValue(sheet, cell string) (result string, err error) { if token, err = f.evalInfixExp(sheet, cell, tokens); err != nil { return } - result = token.TValue + result = token.Value() isNum, precision := isNumeric(result) if isNum && (precision > 15 || precision == 0) { num := roundPrecision(result, -1) @@ -826,7 +826,7 @@ func newEmptyFormulaArg() formulaArg { // // TODO: handle subtypes: Nothing, Text, Logical, Error, Concatenation, Intersection, Union // -func (f *File) evalInfixExp(sheet, cell string, tokens []efp.Token) (efp.Token, error) { +func (f *File) evalInfixExp(sheet, cell string, tokens []efp.Token) (formulaArg, error) { var err error opdStack, optStack, opfStack, opfdStack, opftStack, argsStack := NewStack(), NewStack(), NewStack(), NewStack(), NewStack(), NewStack() for i := 0; i < len(tokens); i++ { @@ -835,7 +835,7 @@ func (f *File) evalInfixExp(sheet, cell string, tokens []efp.Token) (efp.Token, // out of function stack if opfStack.Len() == 0 { if err = f.parseToken(sheet, token, opdStack, optStack); err != nil { - return efp.Token{}, err + return newEmptyFormulaArg(), err } } @@ -864,16 +864,12 @@ func (f *File) evalInfixExp(sheet, cell string, tokens []efp.Token) (efp.Token, // parse reference: must reference at here result, err := f.parseReference(sheet, token.TValue) if err != nil { - return efp.Token{TValue: formulaErrorNAME}, err + return result, err } if result.Type != ArgString { - return efp.Token{}, errors.New(formulaErrorVALUE) + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE), errors.New(formulaErrorVALUE) } - opfdStack.Push(efp.Token{ - TType: efp.TokenTypeOperand, - TSubType: efp.TokenSubTypeNumber, - TValue: result.String, - }) + opfdStack.Push(result) continue } if nextToken.TType == efp.TokenTypeArgument || nextToken.TType == efp.TokenTypeFunction { @@ -884,10 +880,10 @@ func (f *File) evalInfixExp(sheet, cell string, tokens []efp.Token) (efp.Token, } result, err := f.parseReference(sheet, token.TValue) if err != nil { - return efp.Token{TValue: formulaErrorNAME}, err + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE), err } if result.Type == ArgUnknown { - return efp.Token{}, errors.New(formulaErrorVALUE) + return newEmptyFormulaArg(), errors.New(formulaErrorVALUE) } argsStack.Peek().(*list.List).PushBack(result) continue @@ -896,18 +892,14 @@ func (f *File) evalInfixExp(sheet, cell string, tokens []efp.Token) (efp.Token, if isEndParenthesesToken(token) && isBeginParenthesesToken(opftStack.Peek().(efp.Token)) { if arg := argsStack.Peek().(*list.List).Back(); arg != nil { - opfdStack.Push(efp.Token{ - TType: efp.TokenTypeOperand, - TSubType: efp.TokenSubTypeNumber, - TValue: arg.Value.(formulaArg).Value(), - }) + opfdStack.Push(arg.Value.(formulaArg)) argsStack.Peek().(*list.List).Remove(arg) } } // check current token is opft if err = f.parseToken(sheet, token, opfdStack, opftStack); err != nil { - return efp.Token{}, err + return newEmptyFormulaArg(), err } // current token is arg @@ -921,7 +913,7 @@ func (f *File) evalInfixExp(sheet, cell string, tokens []efp.Token) (efp.Token, opftStack.Pop() } if !opfdStack.Empty() { - argsStack.Peek().(*list.List).PushBack(newStringFormulaArg(opfdStack.Pop().(efp.Token).TValue)) + argsStack.Peek().(*list.List).PushBack(opfdStack.Pop().(formulaArg)) } continue } @@ -932,21 +924,21 @@ func (f *File) evalInfixExp(sheet, cell string, tokens []efp.Token) (efp.Token, } if err = f.evalInfixExpFunc(sheet, cell, token, nextToken, opfStack, opdStack, opftStack, opfdStack, argsStack); err != nil { - return efp.Token{}, err + return newEmptyFormulaArg(), err } } } for optStack.Len() != 0 { topOpt := optStack.Peek().(efp.Token) if err = calculate(opdStack, topOpt); err != nil { - return efp.Token{}, err + return newEmptyFormulaArg(), err } optStack.Pop() } if opdStack.Len() == 0 { - return efp.Token{}, ErrInvalidFormula + return newEmptyFormulaArg(), ErrInvalidFormula } - return opdStack.Peek().(efp.Token), err + return opdStack.Peek().(formulaArg), err } // evalInfixExpFunc evaluate formula function in the infix expression. @@ -968,11 +960,7 @@ func (f *File) evalInfixExpFunc(sheet, cell string, token, nextToken efp.Token, if opfStack.Len() > 0 { // still in function stack if nextToken.TType == efp.TokenTypeOperatorInfix || (opftStack.Len() > 1 && opfdStack.Len() > 0) { // mathematics calculate in formula function - if arg.Type == ArgError { - opfdStack.Push(efp.Token{TValue: arg.Value(), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeError}) - } else { - opfdStack.Push(efp.Token{TValue: arg.Value(), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) - } + opfdStack.Push(arg) } else { argsStack.Peek().(*list.List).PushBack(arg) } @@ -981,7 +969,7 @@ func (f *File) evalInfixExpFunc(sheet, cell string, token, nextToken efp.Token, if arg.Type == ArgMatrix && len(arg.Matrix) > 0 && len(arg.Matrix[0]) > 0 { val = arg.Matrix[0][0].Value() } - opdStack.Push(efp.Token{TValue: val, TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + opdStack.Push(newStringFormulaArg(val)) } return nil } @@ -1010,179 +998,166 @@ func prepareEvalInfixExp(opfStack, opftStack, opfdStack, argsStack *Stack) { } // push opfd to args if argument && opfdStack.Len() > 0 { - argsStack.Peek().(*list.List).PushBack(newStringFormulaArg(opfdStack.Pop().(efp.Token).TValue)) + argsStack.Peek().(*list.List).PushBack(opfdStack.Pop().(formulaArg)) } } // calcPow evaluate exponentiation arithmetic operations. -func calcPow(rOpd, lOpd efp.Token, opdStack *Stack) error { - lOpdVal, err := strconv.ParseFloat(lOpd.TValue, 64) - if err != nil { - return err +func calcPow(rOpd, lOpd formulaArg, opdStack *Stack) error { + lOpdVal := lOpd.ToNumber() + if lOpdVal.Type != ArgNumber { + return errors.New(lOpdVal.Value()) } - rOpdVal, err := strconv.ParseFloat(rOpd.TValue, 64) - if err != nil { - return err + rOpdVal := rOpd.ToNumber() + if rOpdVal.Type != ArgNumber { + return errors.New(rOpdVal.Value()) } - result := math.Pow(lOpdVal, rOpdVal) - opdStack.Push(efp.Token{TValue: fmt.Sprintf("%g", result), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + opdStack.Push(newNumberFormulaArg(math.Pow(lOpdVal.Number, rOpdVal.Number))) return nil } // calcEq evaluate equal arithmetic operations. -func calcEq(rOpd, lOpd efp.Token, opdStack *Stack) error { - opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(rOpd.TValue == lOpd.TValue)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) +func calcEq(rOpd, lOpd formulaArg, opdStack *Stack) error { + opdStack.Push(newBoolFormulaArg(rOpd.Value() == lOpd.Value())) return nil } // calcNEq evaluate not equal arithmetic operations. -func calcNEq(rOpd, lOpd efp.Token, opdStack *Stack) error { - opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(rOpd.TValue != lOpd.TValue)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) +func calcNEq(rOpd, lOpd formulaArg, opdStack *Stack) error { + opdStack.Push(newBoolFormulaArg(rOpd.Value() != lOpd.Value())) return nil } // calcL evaluate less than arithmetic operations. -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}) +func calcL(rOpd, lOpd formulaArg, opdStack *Stack) error { + if rOpd.Type == ArgNumber && lOpd.Type == ArgNumber { + opdStack.Push(newBoolFormulaArg(lOpd.Number < rOpd.Number)) } - 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.Type == ArgString && lOpd.Type == ArgString { + opdStack.Push(newBoolFormulaArg(strings.Compare(lOpd.Value(), rOpd.Value()) == -1)) } - 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.Type == ArgNumber && lOpd.Type == ArgString { + opdStack.Push(newBoolFormulaArg(false)) } - 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}) + if rOpd.Type == ArgString && lOpd.Type == ArgNumber { + opdStack.Push(newBoolFormulaArg(true)) } return nil } // calcLe evaluate less than or equal arithmetic operations. -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}) +func calcLe(rOpd, lOpd formulaArg, opdStack *Stack) error { + if rOpd.Type == ArgNumber && lOpd.Type == ArgNumber { + opdStack.Push(newBoolFormulaArg(lOpd.Number <= rOpd.Number)) } - 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.Type == ArgString && lOpd.Type == ArgString { + opdStack.Push(newBoolFormulaArg(strings.Compare(lOpd.Value(), rOpd.Value()) != 1)) } - 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.Type == ArgNumber && lOpd.Type == ArgString { + opdStack.Push(newBoolFormulaArg(false)) } - 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}) + if rOpd.Type == ArgString && lOpd.Type == ArgNumber { + opdStack.Push(newBoolFormulaArg(true)) } return nil } // calcG evaluate greater than or equal arithmetic operations. -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}) +func calcG(rOpd, lOpd formulaArg, opdStack *Stack) error { + if rOpd.Type == ArgNumber && lOpd.Type == ArgNumber { + opdStack.Push(newBoolFormulaArg(lOpd.Number > rOpd.Number)) } - 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.Type == ArgString && lOpd.Type == ArgString { + opdStack.Push(newBoolFormulaArg(strings.Compare(lOpd.Value(), rOpd.Value()) == 1)) } - 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.Type == ArgNumber && lOpd.Type == ArgString { + opdStack.Push(newBoolFormulaArg(true)) } - 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}) + if rOpd.Type == ArgString && lOpd.Type == ArgNumber { + opdStack.Push(newBoolFormulaArg(false)) } return nil } // calcGe evaluate greater than or equal arithmetic operations. -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}) +func calcGe(rOpd, lOpd formulaArg, opdStack *Stack) error { + if rOpd.Type == ArgNumber && lOpd.Type == ArgNumber { + opdStack.Push(newBoolFormulaArg(lOpd.Number >= rOpd.Number)) } - 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.Type == ArgString && lOpd.Type == ArgString { + opdStack.Push(newBoolFormulaArg(strings.Compare(lOpd.Value(), rOpd.Value()) != -1)) } - 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.Type == ArgNumber && lOpd.Type == ArgString { + opdStack.Push(newBoolFormulaArg(true)) } - 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}) + if rOpd.Type == ArgString && lOpd.Type == ArgNumber { + opdStack.Push(newBoolFormulaArg(false)) } return nil } // calcSplice evaluate splice '&' operations. -func calcSplice(rOpd, lOpd efp.Token, opdStack *Stack) error { - opdStack.Push(efp.Token{TValue: lOpd.TValue + rOpd.TValue, TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) +func calcSplice(rOpd, lOpd formulaArg, opdStack *Stack) error { + opdStack.Push(newStringFormulaArg(lOpd.Value() + rOpd.Value())) return nil } // calcAdd evaluate addition arithmetic operations. -func calcAdd(rOpd, lOpd efp.Token, opdStack *Stack) error { - lOpdVal, err := strconv.ParseFloat(lOpd.TValue, 64) - if err != nil { - return err +func calcAdd(rOpd, lOpd formulaArg, opdStack *Stack) error { + lOpdVal := lOpd.ToNumber() + if lOpdVal.Type != ArgNumber { + return errors.New(lOpdVal.Value()) } - rOpdVal, err := strconv.ParseFloat(rOpd.TValue, 64) - if err != nil { - return err + rOpdVal := rOpd.ToNumber() + if rOpdVal.Type != ArgNumber { + return errors.New(rOpdVal.Value()) } - result := lOpdVal + rOpdVal - opdStack.Push(efp.Token{TValue: fmt.Sprintf("%g", result), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + opdStack.Push(newNumberFormulaArg(lOpdVal.Number + rOpdVal.Number)) return nil } // calcSubtract evaluate subtraction arithmetic operations. -func calcSubtract(rOpd, lOpd efp.Token, opdStack *Stack) error { - lOpdVal, err := strconv.ParseFloat(lOpd.TValue, 64) - if err != nil { - return err +func calcSubtract(rOpd, lOpd formulaArg, opdStack *Stack) error { + lOpdVal := lOpd.ToNumber() + if lOpdVal.Type != ArgNumber { + return errors.New(lOpdVal.Value()) } - rOpdVal, err := strconv.ParseFloat(rOpd.TValue, 64) - if err != nil { - return err + rOpdVal := rOpd.ToNumber() + if rOpdVal.Type != ArgNumber { + return errors.New(rOpdVal.Value()) } - result := lOpdVal - rOpdVal - opdStack.Push(efp.Token{TValue: fmt.Sprintf("%g", result), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + opdStack.Push(newNumberFormulaArg(lOpdVal.Number - rOpdVal.Number)) return nil } // calcMultiply evaluate multiplication arithmetic operations. -func calcMultiply(rOpd, lOpd efp.Token, opdStack *Stack) error { - lOpdVal, err := strconv.ParseFloat(lOpd.TValue, 64) - if err != nil { - return err +func calcMultiply(rOpd, lOpd formulaArg, opdStack *Stack) error { + lOpdVal := lOpd.ToNumber() + if lOpdVal.Type != ArgNumber { + return errors.New(lOpdVal.Value()) } - rOpdVal, err := strconv.ParseFloat(rOpd.TValue, 64) - if err != nil { - return err + rOpdVal := rOpd.ToNumber() + if rOpdVal.Type != ArgNumber { + return errors.New(rOpdVal.Value()) } - result := lOpdVal * rOpdVal - opdStack.Push(efp.Token{TValue: fmt.Sprintf("%g", result), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + opdStack.Push(newNumberFormulaArg(lOpdVal.Number * rOpdVal.Number)) return nil } // calcDiv evaluate division arithmetic operations. -func calcDiv(rOpd, lOpd efp.Token, opdStack *Stack) error { - lOpdVal, err := strconv.ParseFloat(lOpd.TValue, 64) - if err != nil { - return err +func calcDiv(rOpd, lOpd formulaArg, opdStack *Stack) error { + lOpdVal := lOpd.ToNumber() + if lOpdVal.Type != ArgNumber { + return errors.New(lOpdVal.Value()) } - rOpdVal, err := strconv.ParseFloat(rOpd.TValue, 64) - if err != nil { - return err + rOpdVal := rOpd.ToNumber() + if rOpdVal.Type != ArgNumber { + return errors.New(rOpdVal.Value()) } - result := lOpdVal / rOpdVal - if rOpdVal == 0 { + if rOpdVal.Number == 0 { return errors.New(formulaErrorDIV) } - opdStack.Push(efp.Token{TValue: fmt.Sprintf("%g", result), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + opdStack.Push(newNumberFormulaArg(lOpdVal.Number / rOpdVal.Number)) return nil } @@ -1192,25 +1167,20 @@ func calculate(opdStack *Stack, opt efp.Token) error { if opdStack.Len() < 1 { return ErrInvalidFormula } - opd := opdStack.Pop().(efp.Token) - opdVal, err := strconv.ParseFloat(opd.TValue, 64) - if err != nil { - return err - } - result := 0 - opdVal - opdStack.Push(efp.Token{TValue: fmt.Sprintf("%g", result), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}) + opd := opdStack.Pop().(formulaArg) + opdStack.Push(newNumberFormulaArg(0 - opd.Number)) } if opt.TValue == "-" && opt.TType == efp.TokenTypeOperatorInfix { if opdStack.Len() < 2 { return ErrInvalidFormula } - rOpd := opdStack.Pop().(efp.Token) - lOpd := opdStack.Pop().(efp.Token) + rOpd := opdStack.Pop().(formulaArg) + lOpd := opdStack.Pop().(formulaArg) if err := calcSubtract(rOpd, lOpd, opdStack); err != nil { return err } } - tokenCalcFunc := map[string]func(rOpd, lOpd efp.Token, opdStack *Stack) error{ + tokenCalcFunc := map[string]func(rOpd, lOpd formulaArg, opdStack *Stack) error{ "^": calcPow, "*": calcMultiply, "/": calcDiv, @@ -1228,13 +1198,13 @@ func calculate(opdStack *Stack, opt efp.Token) error { if opdStack.Len() < 2 { return ErrInvalidFormula } - rOpd := opdStack.Pop().(efp.Token) - lOpd := opdStack.Pop().(efp.Token) - if rOpd.TSubType == efp.TokenSubTypeError { - return errors.New(rOpd.TValue) + rOpd := opdStack.Pop().(formulaArg) + lOpd := opdStack.Pop().(formulaArg) + if rOpd.Type == ArgError { + return errors.New(rOpd.Value()) } - if lOpd.TSubType == efp.TokenSubTypeError { - return errors.New(lOpd.TValue) + if lOpd.Type == ArgError { + return errors.New(lOpd.Value()) } if err := fn(rOpd, lOpd, opdStack); err != nil { return err @@ -1322,7 +1292,7 @@ func (f *File) parseToken(sheet string, token efp.Token, opdStack, optStack *Sta } token.TValue = result.String token.TType = efp.TokenTypeOperand - token.TSubType = efp.TokenSubTypeNumber + token.TSubType = efp.TokenSubTypeText } if isOperatorPrefixToken(token) { if err := f.parseOperatorPrefixToken(optStack, opdStack, token); err != nil { @@ -1343,15 +1313,17 @@ func (f *File) parseToken(sheet string, token efp.Token, opdStack, optStack *Sta optStack.Pop() } if token.TType == efp.TokenTypeOperatorPostfix && !opdStack.Empty() { - topOpd := opdStack.Pop().(efp.Token) - opd, err := strconv.ParseFloat(topOpd.TValue, 64) - topOpd.TValue = strconv.FormatFloat(opd/100, 'f', -1, 64) - opdStack.Push(topOpd) - return err + topOpd := opdStack.Pop().(formulaArg) + opdStack.Push(newNumberFormulaArg(topOpd.Number / 100)) } // opd if isOperand(token) { - opdStack.Push(token) + if token.TSubType == efp.TokenSubTypeNumber { + num, _ := strconv.ParseFloat(token.TValue, 64) + opdStack.Push(newNumberFormulaArg(num)) + } else { + opdStack.Push(newStringFormulaArg(token.TValue)) + } } return nil } @@ -3723,7 +3695,7 @@ func (fn *formulaFuncs) BASE(argsList *list.List) formulaArg { return newErrorFormulaArg(formulaErrorVALUE, "radix must be an integer >= 2 and <= 36") } if argsList.Len() > 2 { - if minLength, err = strconv.Atoi(argsList.Back().Value.(formulaArg).String); err != nil { + if minLength, err = strconv.Atoi(argsList.Back().Value.(formulaArg).Value()); err != nil { return newErrorFormulaArg(formulaErrorVALUE, err.Error()) } } @@ -4058,17 +4030,16 @@ func (fn *formulaFuncs) DECIMAL(argsList *list.List) formulaArg { if argsList.Len() != 2 { return newErrorFormulaArg(formulaErrorVALUE, "DECIMAL requires 2 numeric arguments") } - text := argsList.Front().Value.(formulaArg).String - var radix int + text := argsList.Front().Value.(formulaArg).Value() var err error - radix, err = strconv.Atoi(argsList.Back().Value.(formulaArg).String) - if err != nil { - return newErrorFormulaArg(formulaErrorVALUE, err.Error()) + radix := argsList.Back().Value.(formulaArg).ToNumber() + if radix.Type != ArgNumber { + return radix } if len(text) > 2 && (strings.HasPrefix(text, "0x") || strings.HasPrefix(text, "0X")) { text = text[2:] } - val, err := strconv.ParseInt(text, radix, 64) + val, err := strconv.ParseInt(text, int(radix.Number), 64) if err != nil { return newErrorFormulaArg(formulaErrorVALUE, err.Error()) } @@ -4948,8 +4919,6 @@ func (fn *formulaFuncs) PRODUCT(argsList *list.List) formulaArg { for arg := argsList.Front(); arg != nil; arg = arg.Next() { token := arg.Value.(formulaArg) switch token.Type { - case ArgUnknown: - continue case ArgString: if token.String == "" { continue @@ -4963,13 +4932,13 @@ func (fn *formulaFuncs) PRODUCT(argsList *list.List) formulaArg { case ArgMatrix: for _, row := range token.Matrix { for _, value := range row { - if value.String == "" { + if value.Value() == "" { continue } if val, err = strconv.ParseFloat(value.String, 64); err != nil { return newErrorFormulaArg(formulaErrorVALUE, err.Error()) } - product = product * val + product *= val } } } @@ -5684,10 +5653,9 @@ func (fn *formulaFuncs) SUMIF(argsList *list.List) formulaArg { ok, _ = formulaCriteriaEval(fromVal, criteria) if ok { if argsList.Len() == 3 { - if len(sumRange) <= rowIdx || len(sumRange[rowIdx]) <= colIdx { - continue + if len(sumRange) > rowIdx && len(sumRange[rowIdx]) > colIdx { + fromVal = sumRange[rowIdx][colIdx].String } - fromVal = sumRange[rowIdx][colIdx].String } if val, err = strconv.ParseFloat(fromVal, 64); err != nil { continue @@ -5718,6 +5686,9 @@ func (fn *formulaFuncs) SUMIFS(argsList *list.List) formulaArg { args = append(args, arg.Value.(formulaArg)) } for _, ref := range formulaIfsMatch(args) { + if ref.Row >= len(sumRange) || ref.Col >= len(sumRange[ref.Row]) { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } if num := sumRange[ref.Row][ref.Col].ToNumber(); num.Type == ArgNumber { sum += num.Number } @@ -5812,14 +5783,14 @@ func (fn *formulaFuncs) SUMSQ(argsList *list.List) formulaArg { } sq += val * val case ArgNumber: - sq += token.Number + sq += token.Number * token.Number case ArgMatrix: for _, row := range token.Matrix { for _, value := range row { - if value.String == "" { + if value.Value() == "" { continue } - if val, err = strconv.ParseFloat(value.String, 64); err != nil { + if val, err = strconv.ParseFloat(value.Value(), 64); err != nil { return newErrorFormulaArg(formulaErrorVALUE, err.Error()) } sq += val * val @@ -6028,7 +5999,7 @@ func (fn *formulaFuncs) AVERAGEIF(argsList *list.List) formulaArg { return newErrorFormulaArg(formulaErrorVALUE, "AVERAGEIF requires at least 2 arguments") } var ( - criteria = formulaCriteriaParser(argsList.Front().Next().Value.(formulaArg).String) + criteria = formulaCriteriaParser(argsList.Front().Next().Value.(formulaArg).Value()) rangeMtx = argsList.Front().Value.(formulaArg).Matrix cellRange [][]formulaArg args []formulaArg @@ -6041,17 +6012,16 @@ func (fn *formulaFuncs) AVERAGEIF(argsList *list.List) formulaArg { } for rowIdx, row := range rangeMtx { for colIdx, col := range row { - fromVal := col.String - if col.String == "" { + fromVal := col.Value() + if col.Value() == "" { continue } ok, _ = formulaCriteriaEval(fromVal, criteria) if ok { if argsList.Len() == 3 { - if len(cellRange) <= rowIdx || len(cellRange[rowIdx]) <= colIdx { - continue + if len(cellRange) > rowIdx && len(cellRange[rowIdx]) > colIdx { + fromVal = cellRange[rowIdx][colIdx].Value() } - fromVal = cellRange[rowIdx][colIdx].String } if val, err = strconv.ParseFloat(fromVal, 64); err != nil { continue @@ -7686,12 +7656,10 @@ func (fn *formulaFuncs) COUNT(argsList *list.List) formulaArg { for token := argsList.Front(); token != nil; token = token.Next() { arg := token.Value.(formulaArg) switch arg.Type { - case ArgString: + case ArgString, ArgNumber: if arg.ToNumber().Type != ArgError { count++ } - case ArgNumber: - count++ case ArgMatrix: for _, row := range arg.Matrix { for _, value := range row { @@ -7928,23 +7896,14 @@ func (fn *formulaFuncs) GAMMA(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "GAMMA requires 1 numeric argument") } - token := argsList.Front().Value.(formulaArg) - switch token.Type { - case ArgString: - arg := token.ToNumber() - if arg.Type == ArgNumber { - if arg.Number <= 0 { - return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) - } - return newNumberFormulaArg(math.Gamma(arg.Number)) - } - case ArgNumber: - if token.Number <= 0 { - return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) - } - return newNumberFormulaArg(math.Gamma(token.Number)) + number := argsList.Front().Value.(formulaArg).ToNumber() + if number.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, "GAMMA requires 1 numeric argument") + } + if number.Number <= 0 { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } - return newErrorFormulaArg(formulaErrorVALUE, "GAMMA requires 1 numeric argument") + return newNumberFormulaArg(math.Gamma(number.Number)) } // GAMMAdotDIST function returns the Gamma Distribution, which is frequently @@ -8073,23 +8032,14 @@ func (fn *formulaFuncs) GAMMALN(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "GAMMALN requires 1 numeric argument") } - token := argsList.Front().Value.(formulaArg) - switch token.Type { - case ArgString: - arg := token.ToNumber() - if arg.Type == ArgNumber { - if arg.Number <= 0 { - return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) - } - return newNumberFormulaArg(math.Log(math.Gamma(arg.Number))) - } - case ArgNumber: - if token.Number <= 0 { - return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) - } - return newNumberFormulaArg(math.Log(math.Gamma(token.Number))) + x := argsList.Front().Value.(formulaArg).ToNumber() + if x.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, "GAMMALN requires 1 numeric argument") + } + if x.Number <= 0 { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } - return newErrorFormulaArg(formulaErrorVALUE, "GAMMALN requires 1 numeric argument") + return newNumberFormulaArg(math.Log(math.Gamma(x.Number))) } // GAMMALNdotPRECISE function returns the natural logarithm of the Gamma @@ -11709,11 +11659,7 @@ func (fn *formulaFuncs) AND(argsList *list.List) formulaArg { if argsList.Len() > 30 { return newErrorFormulaArg(formulaErrorVALUE, "AND accepts at most 30 arguments") } - var ( - and = true - val float64 - err error - ) + and := true for arg := argsList.Front(); arg != nil; arg = arg.Next() { token := arg.Value.(formulaArg) switch token.Type { @@ -11726,10 +11672,9 @@ func (fn *formulaFuncs) AND(argsList *list.List) formulaArg { if token.String == "FALSE" { return newStringFormulaArg(token.String) } - if val, err = strconv.ParseFloat(token.String, 64); err != nil { - return newErrorFormulaArg(formulaErrorVALUE, err.Error()) - } - and = and && (val != 0) + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + case ArgNumber: + and = and && token.Number != 0 case ArgMatrix: // TODO return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) @@ -11845,11 +11790,7 @@ func (fn *formulaFuncs) OR(argsList *list.List) formulaArg { if argsList.Len() > 30 { return newErrorFormulaArg(formulaErrorVALUE, "OR accepts at most 30 arguments") } - var ( - or bool - val float64 - err error - ) + var or bool for arg := argsList.Front(); arg != nil; arg = arg.Next() { token := arg.Value.(formulaArg) switch token.Type { @@ -11863,10 +11804,9 @@ func (fn *formulaFuncs) OR(argsList *list.List) formulaArg { or = true continue } - if val, err = strconv.ParseFloat(token.String, 64); err != nil { - return newErrorFormulaArg(formulaErrorVALUE, err.Error()) - } - or = val != 0 + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + case ArgNumber: + or = token.Number != 0 case ArgMatrix: // TODO return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) @@ -11931,20 +11871,6 @@ func calcXor(argsList *list.List) formulaArg { switch token.Type { case ArgError: return token - case ArgString: - if b := token.ToBool(); b.Type == ArgNumber { - ok = true - if b.Number == 1 { - count++ - } - continue - } - if num := token.ToNumber(); num.Type == ArgNumber { - ok = true - if num.Number != 0 { - count++ - } - } case ArgNumber: ok = true if token.Number != 0 { @@ -12907,7 +12833,7 @@ func (fn *formulaFuncs) CLEAN(argsList *list.List) formulaArg { return newErrorFormulaArg(formulaErrorVALUE, "CLEAN requires 1 argument") } b := bytes.Buffer{} - for _, c := range argsList.Front().Value.(formulaArg).String { + for _, c := range argsList.Front().Value.(formulaArg).Value() { if c > 31 { b.WriteRune(c) } @@ -13477,7 +13403,7 @@ func (fn *formulaFuncs) TRIM(argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, "TRIM requires 1 argument") } - return newStringFormulaArg(strings.TrimSpace(argsList.Front().Value.(formulaArg).String)) + return newStringFormulaArg(strings.TrimSpace(argsList.Front().Value.(formulaArg).Value())) } // UNICHAR returns the Unicode character that is referenced by the given @@ -13584,27 +13510,30 @@ func (fn *formulaFuncs) IF(argsList *list.List) formulaArg { if cond, err = strconv.ParseBool(token.String); err != nil { return newErrorFormulaArg(formulaErrorVALUE, err.Error()) } - if argsList.Len() == 1 { - return newBoolFormulaArg(cond) - } - if cond { - value := argsList.Front().Next().Value.(formulaArg) - switch value.Type { - case ArgNumber: - result = value.ToNumber() - default: - result = newStringFormulaArg(value.String) - } - return result + case ArgNumber: + cond = token.Number == 1 + } + + if argsList.Len() == 1 { + return newBoolFormulaArg(cond) + } + if cond { + value := argsList.Front().Next().Value.(formulaArg) + switch value.Type { + case ArgNumber: + result = value.ToNumber() + default: + result = newStringFormulaArg(value.String) } - if argsList.Len() == 3 { - value := argsList.Back().Value.(formulaArg) - switch value.Type { - case ArgNumber: - result = value.ToNumber() - default: - result = newStringFormulaArg(value.String) - } + return result + } + if argsList.Len() == 3 { + value := argsList.Back().Value.(formulaArg) + switch value.Type { + case ArgNumber: + result = value.ToNumber() + default: + result = newStringFormulaArg(value.String) } } return result @@ -13676,7 +13605,7 @@ func (fn *formulaFuncs) CHOOSE(argsList *list.List) formulaArg { if argsList.Len() < 2 { return newErrorFormulaArg(formulaErrorVALUE, "CHOOSE requires 2 arguments") } - idx, err := strconv.Atoi(argsList.Front().Value.(formulaArg).String) + idx, err := strconv.Atoi(argsList.Front().Value.(formulaArg).Value()) if err != nil { return newErrorFormulaArg(formulaErrorVALUE, "CHOOSE requires first argument of type number") } @@ -14075,7 +14004,7 @@ func (fn *formulaFuncs) MATCH(argsList *list.List) formulaArg { default: return newErrorFormulaArg(formulaErrorNA, lookupArrayErr) } - return calcMatch(matchType, formulaCriteriaParser(argsList.Front().Value.(formulaArg).String), lookupArray) + return calcMatch(matchType, formulaCriteriaParser(argsList.Front().Value.(formulaArg).Value()), lookupArray) } // TRANSPOSE function 'transposes' an array of cells (i.e. the function copies @@ -14237,7 +14166,7 @@ func checkLookupArgs(argsList *list.List) (arrayForm bool, lookupValue, lookupVe errArg = newErrorFormulaArg(formulaErrorVALUE, "LOOKUP requires at most 3 arguments") return } - lookupValue = argsList.Front().Value.(formulaArg) + lookupValue = newStringFormulaArg(argsList.Front().Value.(formulaArg).Value()) lookupVector = argsList.Front().Next().Value.(formulaArg) if lookupVector.Type != ArgMatrix && lookupVector.Type != ArgList { errArg = newErrorFormulaArg(formulaErrorVALUE, "LOOKUP requires second argument of table array") diff --git a/calc_test.go b/calc_test.go index 6d83362..205f329 100644 --- a/calc_test.go +++ b/calc_test.go @@ -8,7 +8,6 @@ import ( "testing" "github.com/stretchr/testify/assert" - "github.com/xuri/efp" ) func prepareCalcData(cellData [][]interface{}) *File { @@ -545,6 +544,7 @@ func TestCalcCellValue(t *testing.T) { // GCD "=GCD(0)": "0", "=GCD(1,0)": "1", + "=GCD(\"0\",1)": "1", "=GCD(1,5)": "1", "=GCD(15,10,25)": "5", "=GCD(0,8,12)": "4", @@ -655,6 +655,7 @@ func TestCalcCellValue(t *testing.T) { "=PRODUCT(3,6)": "18", `=PRODUCT("",3,6)`: "18", `=PRODUCT(PRODUCT(1),3,6)`: "18", + "=PRODUCT(C1:C2)": "1", // QUOTIENT "=QUOTIENT(5,2)": "2", "=QUOTIENT(4.5,3.1)": "1", @@ -798,7 +799,7 @@ func TestCalcCellValue(t *testing.T) { "=SUMSQ(A1,B1,A2,B2,6)": "82", `=SUMSQ("",A1,B1,A2,B2,6)`: "82", `=SUMSQ(1,SUMSQ(1))`: "2", - "=SUMSQ(MUNIT(3))": "0", + "=SUMSQ(MUNIT(3))": "3", // SUMX2MY2 "=SUMX2MY2(A1:A4,B1:B4)": "-36", // SUMX2PY2 @@ -927,8 +928,8 @@ func TestCalcCellValue(t *testing.T) { // CORREL "=CORREL(A1:A5,B1:B5)": "1", // COUNT - "=COUNT()": "0", - "=COUNT(E1:F2,\"text\",1,INT(2))": "3", + "=COUNT()": "0", + "=COUNT(E1:F2,\"text\",1,INT(2),\"0\")": "4", // COUNTA "=COUNTA()": "0", "=COUNTA(A1:A5,B2:B5,\"text\",1,INT(2))": "8", @@ -959,19 +960,22 @@ func TestCalcCellValue(t *testing.T) { "=DEVSQ(1,3,5,2,9,7)": "47.5", "=DEVSQ(A1:D2)": "10", // FISHER - "=FISHER(-0.9)": "-1.47221948958322", - "=FISHER(-0.25)": "-0.255412811882995", - "=FISHER(0.8)": "1.09861228866811", - "=FISHER(INT(0))": "0", + "=FISHER(-0.9)": "-1.47221948958322", + "=FISHER(-0.25)": "-0.255412811882995", + "=FISHER(0.8)": "1.09861228866811", + "=FISHER(\"0.8\")": "1.09861228866811", + "=FISHER(INT(0))": "0", // FISHERINV "=FISHERINV(-0.2)": "-0.197375320224904", "=FISHERINV(INT(0))": "0", + "=FISHERINV(\"0\")": "0", "=FISHERINV(2.8)": "0.992631520201128", // GAMMA - "=GAMMA(0.1)": "9.51350769866873", - "=GAMMA(INT(1))": "1", - "=GAMMA(1.5)": "0.886226925452758", - "=GAMMA(5.5)": "52.3427777845535", + "=GAMMA(0.1)": "9.51350769866873", + "=GAMMA(INT(1))": "1", + "=GAMMA(1.5)": "0.886226925452758", + "=GAMMA(5.5)": "52.3427777845535", + "=GAMMA(\"5.5\")": "52.3427777845535", // GAMMA.DIST "=GAMMA.DIST(6,3,2,FALSE)": "0.112020903827694", "=GAMMA.DIST(6,3,2,TRUE)": "0.576809918873156", @@ -1097,12 +1101,13 @@ func TestCalcCellValue(t *testing.T) { "=LARGE(A1,1)": "1", "=LARGE(A1:F2,1)": "36693", // MAX - "=MAX(1)": "1", - "=MAX(TRUE())": "1", - "=MAX(0.5,TRUE())": "1", - "=MAX(FALSE())": "0", - "=MAX(MUNIT(2))": "1", - "=MAX(INT(1))": "1", + "=MAX(1)": "1", + "=MAX(TRUE())": "1", + "=MAX(0.5,TRUE())": "1", + "=MAX(FALSE())": "0", + "=MAX(MUNIT(2))": "1", + "=MAX(INT(1))": "1", + "=MAX(\"0\",\"2\")": "2", // MAXA "=MAXA(1)": "1", "=MAXA(TRUE())": "1", @@ -1117,6 +1122,7 @@ func TestCalcCellValue(t *testing.T) { "=MEDIAN(A1:A5,12)": "2", "=MEDIAN(A1:A5)": "1.5", "=MEDIAN(A1:A5,MEDIAN(A1:A5,12))": "2", + "=MEDIAN(\"0\",\"2\")": "1", // MIN "=MIN(1)": "1", "=MIN(TRUE())": "1", @@ -1124,6 +1130,7 @@ func TestCalcCellValue(t *testing.T) { "=MIN(FALSE())": "0", "=MIN(MUNIT(2))": "0", "=MIN(INT(1))": "1", + "=MIN(2,\"1\")": "1", // MINA "=MINA(1)": "1", "=MINA(TRUE())": "1", @@ -1345,14 +1352,15 @@ func TestCalcCellValue(t *testing.T) { "=T(N(10))": "", // Logical Functions // AND - "=AND(0)": "FALSE", - "=AND(1)": "TRUE", - "=AND(1,0)": "FALSE", - "=AND(0,1)": "FALSE", - "=AND(1=1)": "TRUE", - "=AND(1<2)": "TRUE", - "=AND(1>2,2<3,2>0,3>1)": "FALSE", - "=AND(1=1),1=1": "TRUE", + "=AND(0)": "FALSE", + "=AND(1)": "TRUE", + "=AND(1,0)": "FALSE", + "=AND(0,1)": "FALSE", + "=AND(1=1)": "TRUE", + "=AND(1<2)": "TRUE", + "=AND(1>2,2<3,2>0,3>1)": "FALSE", + "=AND(1=1),1=1": "TRUE", + "=AND(\"TRUE\",\"FALSE\")": "FALSE", // FALSE "=FALSE()": "FALSE", // IFERROR @@ -1372,10 +1380,11 @@ func TestCalcCellValue(t *testing.T) { "=NOT(\"true\")": "FALSE", "=NOT(ISBLANK(B1))": "TRUE", // OR - "=OR(1)": "TRUE", - "=OR(0)": "FALSE", - "=OR(1=2,2=2)": "TRUE", - "=OR(1=2,2=3)": "FALSE", + "=OR(1)": "TRUE", + "=OR(0)": "FALSE", + "=OR(1=2,2=2)": "TRUE", + "=OR(1=2,2=3)": "FALSE", + "=OR(\"TRUE\",\"FALSE\")": "TRUE", // SWITCH "=SWITCH(1,1,\"A\",2,\"B\",3,\"C\",\"N\")": "A", "=SWITCH(3,1,\"A\",2,\"B\",3,\"C\",\"N\")": "C", @@ -1897,6 +1906,7 @@ func TestCalcCellValue(t *testing.T) { // PRICEDISC "=PRICEDISC(\"04/01/2017\",\"03/31/2021\",2.5%,100)": "90", "=PRICEDISC(\"04/01/2017\",\"03/31/2021\",2.5%,100,3)": "90", + "=PRICEDISC(\"42826\",\"03/31/2021\",2.5%,100,3)": "90", // PRICEMAT "=PRICEMAT(\"04/01/2017\",\"03/31/2021\",\"01/01/2017\",4.5%,2.5%)": "107.170454545455", "=PRICEMAT(\"04/01/2017\",\"03/31/2021\",\"01/01/2017\",4.5%,2.5%,0)": "107.170454545455", @@ -2335,7 +2345,7 @@ func TestCalcCellValue(t *testing.T) { // _xlfn.DECIMAL "=_xlfn.DECIMAL()": "DECIMAL requires 2 numeric arguments", `=_xlfn.DECIMAL("X", 2)`: "strconv.ParseInt: parsing \"X\": invalid syntax", - `=_xlfn.DECIMAL(2000, "X")`: "strconv.Atoi: parsing \"X\": invalid syntax", + `=_xlfn.DECIMAL(2000, "X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax", // DEGREES "=DEGREES()": "DEGREES requires 1 numeric argument", `=DEGREES("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax", @@ -2461,10 +2471,11 @@ func TestCalcCellValue(t *testing.T) { "=RANDBETWEEN()": "RANDBETWEEN requires 2 numeric arguments", "=RANDBETWEEN(2,1)": "#NUM!", // ROMAN - "=ROMAN()": "ROMAN requires at least 1 argument", - "=ROMAN(1,2,3)": "ROMAN allows at most 2 arguments", - `=ROMAN("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax", - `=ROMAN("X",1)`: "strconv.ParseFloat: parsing \"X\": invalid syntax", + "=ROMAN()": "ROMAN requires at least 1 argument", + "=ROMAN(1,2,3)": "ROMAN allows at most 2 arguments", + "=ROMAN(1,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax", + "=ROMAN(\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax", + "=ROMAN(\"\",1)": "strconv.ParseFloat: parsing \"\": invalid syntax", // ROUND "=ROUND()": "ROUND requires 2 numeric arguments", `=ROUND("X",1)`: "strconv.ParseFloat: parsing \"X\": invalid syntax", @@ -2776,6 +2787,7 @@ func TestCalcCellValue(t *testing.T) { "=GAMMA()": "GAMMA requires 1 numeric argument", "=GAMMA(F1)": "GAMMA requires 1 numeric argument", "=GAMMA(0)": "#N/A", + "=GAMMA(\"0\")": "#N/A", "=GAMMA(INT(0))": "#N/A", // GAMMA.DIST "=GAMMA.DIST()": "GAMMA.DIST requires 4 arguments", @@ -3289,9 +3301,10 @@ func TestCalcCellValue(t *testing.T) { "=T(NA())": "#N/A", // Logical Functions // AND - `=AND("text")`: "strconv.ParseFloat: parsing \"text\": invalid syntax", - `=AND(A1:B1)`: "#VALUE!", - "=AND()": "AND requires at least 1 argument", + "=AND(\"text\")": "#VALUE!", + "=AND(A1:B1)": "#VALUE!", + "=AND(\"1\",\"TRUE\",\"FALSE\")": "#VALUE!", + "=AND()": "AND requires at least 1 argument", "=AND(1" + strings.Repeat(",1", 30) + ")": "AND accepts at most 30 arguments", // FALSE "=FALSE(A1)": "FALSE takes no arguments", @@ -3307,8 +3320,9 @@ func TestCalcCellValue(t *testing.T) { "=NOT(NOT())": "NOT requires 1 argument", "=NOT(\"\")": "NOT expects 1 boolean or numeric argument", // OR - `=OR("text")`: "strconv.ParseFloat: parsing \"text\": invalid syntax", - `=OR(A1:B1)`: "#VALUE!", + "=OR(\"text\")": "#VALUE!", + "=OR(A1:B1)": "#VALUE!", + "=OR(\"1\",\"TRUE\",\"FALSE\")": "#VALUE!", "=OR()": "OR requires at least 1 argument", "=OR(1" + strings.Repeat(",1", 30) + ")": "OR accepts at most 30 arguments", // SWITCH @@ -3318,6 +3332,7 @@ func TestCalcCellValue(t *testing.T) { "=TRUE(A1)": "TRUE takes no arguments", // XOR "=XOR()": "XOR requires at least 1 argument", + "=XOR(\"1\")": "#VALUE!", "=XOR(\"text\")": "#VALUE!", "=XOR(XOR(\"text\"))": "#VALUE!", // Date and Time Functions @@ -3595,7 +3610,7 @@ func TestCalcCellValue(t *testing.T) { "=HLOOKUP(D2,D1,1,FALSE)": "HLOOKUP requires second argument of table array", "=HLOOKUP(D2,D:D,FALSE,FALSE)": "HLOOKUP requires numeric row argument", "=HLOOKUP(D2,D:D,1,FALSE,FALSE)": "HLOOKUP requires at most 4 arguments", - "=HLOOKUP(D2,D:D,1,2)": "strconv.ParseBool: parsing \"2\": invalid syntax", + "=HLOOKUP(D2,D:D,1,2)": "HLOOKUP no result found", "=HLOOKUP(D2,D10:D10,1,FALSE)": "HLOOKUP no result found", "=HLOOKUP(D2,D2:D3,4,FALSE)": "HLOOKUP has invalid row index", "=HLOOKUP(D2,C:C,1,FALSE)": "HLOOKUP no result found", @@ -3616,7 +3631,7 @@ func TestCalcCellValue(t *testing.T) { "=VLOOKUP(D2,D1,1,FALSE)": "VLOOKUP requires second argument of table array", "=VLOOKUP(D2,D:D,FALSE,FALSE)": "VLOOKUP requires numeric col argument", "=VLOOKUP(D2,D:D,1,FALSE,FALSE)": "VLOOKUP requires at most 4 arguments", - "=VLOOKUP(D2,D:D,1,2)": "strconv.ParseBool: parsing \"2\": invalid syntax", + "=VLOOKUP(A1:A2,A1:A1,1)": "VLOOKUP no result found", "=VLOOKUP(D2,D10:D10,1,FALSE)": "VLOOKUP no result found", "=VLOOKUP(D2,D:D,2,FALSE)": "VLOOKUP has invalid column index", "=VLOOKUP(D2,C:C,1,FALSE)": "VLOOKUP no result found", @@ -4210,18 +4225,6 @@ func TestCalcCellValue(t *testing.T) { assert.NoError(t, f.SaveAs(filepath.Join("test", "TestCalcCellValue.xlsx"))) } -func TestCalculate(t *testing.T) { - err := `strconv.ParseFloat: parsing "string": invalid syntax` - opd := NewStack() - opd.Push(efp.Token{TValue: "string"}) - opt := efp.Token{TValue: "-", TType: efp.TokenTypeOperatorPrefix} - assert.EqualError(t, calculate(opd, opt), err) - opd.Push(efp.Token{TValue: "string"}) - opd.Push(efp.Token{TValue: "string"}) - opt = efp.Token{TValue: "-", TType: efp.TokenTypeOperatorInfix} - assert.EqualError(t, calculate(opd, opt), err) -} - func TestCalcWithDefinedName(t *testing.T) { cellData := [][]interface{}{ {"A1_as_string", "B1_as_string", 123, nil}, @@ -4812,12 +4815,13 @@ func TestCalcSUMIFSAndAVERAGEIFS(t *testing.T) { assert.Equal(t, expected, result, formula) } calcError := map[string]string{ - "=AVERAGEIFS()": "AVERAGEIFS requires at least 3 arguments", - "=AVERAGEIFS(H1,\"\")": "AVERAGEIFS requires at least 3 arguments", - "=AVERAGEIFS(H1,\"\",TRUE,1)": "#N/A", - "=AVERAGEIFS(H1,\"\",TRUE)": "AVERAGEIF divide by zero", - "=SUMIFS()": "SUMIFS requires at least 3 arguments", - "=SUMIFS(D2:D13,A2:A13,1,B2:B13)": "#N/A", + "=AVERAGEIFS()": "AVERAGEIFS requires at least 3 arguments", + "=AVERAGEIFS(H1,\"\")": "AVERAGEIFS requires at least 3 arguments", + "=AVERAGEIFS(H1,\"\",TRUE,1)": "#N/A", + "=AVERAGEIFS(H1,\"\",TRUE)": "AVERAGEIF divide by zero", + "=SUMIFS()": "SUMIFS requires at least 3 arguments", + "=SUMIFS(D2:D13,A2:A13,1,B2:B13)": "#N/A", + "=SUMIFS(D20:D23,A2:A13,\">2\",C2:C13,\"Jeff\")": "#VALUE!", } for formula, expected := range calcError { assert.NoError(t, f.SetCellFormula("Sheet1", "E1", formula)) @@ -4906,6 +4910,7 @@ func TestCalcXLOOKUP(t *testing.T) { "=XLOOKUP()": "XLOOKUP requires at least 3 arguments", "=XLOOKUP($C3,$C5:$C5,$C6:$C17,NA(),0,2,1)": "XLOOKUP allows at most 6 arguments", "=XLOOKUP($C3,$C5,$C6,NA(),0,2)": "#N/A", + "=XLOOKUP(\"?\",B2:B9,C2:C9,NA(),2)": "#N/A", "=XLOOKUP($C3,$C4:$D5,$C6:$C17,NA(),0,2)": "#VALUE!", "=XLOOKUP($C3,$C5:$C5,$C6:$G17,NA(),0,-2)": "#VALUE!", "=XLOOKUP($C3,$C5:$G5,$C6:$F7,NA(),0,2)": "#VALUE!", |