Skip to content

Commit

Permalink
Fix #701, init new formula function AND and OR, prevent formula lexer…
Browse files Browse the repository at this point in the history
… panic on retrieving the top token type
  • Loading branch information
xuri committed Oct 16, 2020
1 parent ac3dce0 commit 02530e8
Show file tree
Hide file tree
Showing 4 changed files with 337 additions and 89 deletions.
296 changes: 232 additions & 64 deletions calc.go
Expand Up @@ -93,21 +93,36 @@ type formulaArg struct {
// formulaFuncs is the type of the formula functions.
type formulaFuncs struct{}

// tokenPriority defined basic arithmetic operator priority.
var tokenPriority = map[string]int{
"^": 5,
"*": 4,
"/": 4,
"+": 3,
"-": 3,
"=": 2,
"<": 2,
"<=": 2,
">": 2,
">=": 2,
"&": 1,
}

// CalcCellValue provides a function to get calculated cell value. This
// feature is currently in working processing. Array formula, table formula
// and some other formulas are not supported currently.
//
// Supported formulas:
//
// ABS, ACOS, ACOSH, ACOT, ACOTH, ARABIC, ASIN, ASINH, ATAN2, ATANH, BASE,
// CEILING, CEILING.MATH, CEILING.PRECISE, COMBIN, COMBINA, COS, COSH, COT,
// COTH, COUNTA, CSC, CSCH, DECIMAL, DEGREES, EVEN, EXP, FACT, FACTDOUBLE,
// FLOOR, FLOOR.MATH, FLOOR.PRECISE, GCD, INT, ISBLANK, ISERR, ISERROR,
// ISEVEN, ISNA, ISNONTEXT, ISNUMBER, ISO.CEILING, ISODD, LCM, LN, LOG,
// LOG10, MDETERM, MEDIAN, MOD, MROUND, MULTINOMIAL, MUNIT, NA, ODD, PI,
// POWER, PRODUCT, QUOTIENT, RADIANS, RAND, RANDBETWEEN, ROUND, ROUNDDOWN,
// ROUNDUP, SEC, SECH, SIGN, SIN, SINH, SQRT, SQRTPI, SUM, SUMIF, SUMSQ,
// TAN, TANH, TRUNC
// ABS, ACOS, ACOSH, ACOT, ACOTH, AND, ARABIC, ASIN, ASINH, ATAN2, ATANH,
// BASE, CEILING, CEILING.MATH, CEILING.PRECISE, COMBIN, COMBINA, COS,
// COSH, COT, COTH, COUNTA, CSC, CSCH, DECIMAL, DEGREES, EVEN, EXP, FACT,
// FACTDOUBLE, FLOOR, FLOOR.MATH, FLOOR.PRECISE, GCD, INT, ISBLANK, ISERR,
// ISERROR, ISEVEN, ISNA, ISNONTEXT, ISNUMBER, ISO.CEILING, ISODD, LCM,
// LN, LOG, LOG10, MDETERM, MEDIAN, MOD, MROUND, MULTINOMIAL, MUNIT, NA,
// ODD, OR, PI, POWER, PRODUCT, QUOTIENT, RADIANS, RAND, RANDBETWEEN,
// ROUND, ROUNDDOWN, ROUNDUP, SEC, SECH, SIGN, SIN, SINH, SQRT, SQRTPI,
// SUM, SUMIF, SUMSQ, TAN, TANH, TRUNC
//
func (f *File) CalcCellValue(sheet, cell string) (result string, err error) {
var (
Expand All @@ -131,15 +146,9 @@ func (f *File) CalcCellValue(sheet, cell string) (result string, err error) {

// getPriority calculate arithmetic operator priority.
func getPriority(token efp.Token) (pri int) {
var priority = map[string]int{
"*": 2,
"/": 2,
"+": 1,
"-": 1,
}
pri, _ = priority[token.TValue]
pri, _ = tokenPriority[token.TValue]
if token.TValue == "-" && token.TType == efp.TokenTypeOperatorPrefix {
pri = 3
pri = 6
}
if token.TSubType == efp.TokenSubTypeStart && token.TType == efp.TokenTypeSubexpression { // (
pri = 0
Expand Down Expand Up @@ -306,18 +315,96 @@ func (f *File) evalInfixExp(sheet string, tokens []efp.Token) (efp.Token, error)
return opdStack.Peek().(efp.Token), err
}

// calcAdd evaluate addition arithmetic operations.
func calcAdd(opdStack *Stack) error {
if opdStack.Len() < 2 {
return errors.New("formula not valid")
// calcPow evaluate exponentiation arithmetic operations.
func calcPow(rOpd, lOpd string, opdStack *Stack) error {
lOpdVal, err := strconv.ParseFloat(lOpd, 64)
if err != nil {
return err
}
rOpdVal, err := strconv.ParseFloat(rOpd, 64)
if err != nil {
return err
}
result := math.Pow(lOpdVal, rOpdVal)
opdStack.Push(efp.Token{TValue: fmt.Sprintf("%g", result), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
return nil
}

// calcEq evaluate equal arithmetic operations.
func calcEq(rOpd, lOpd string, 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 {
lOpdVal, err := strconv.ParseFloat(lOpd, 64)
if err != nil {
return err
}
rOpdVal, err := strconv.ParseFloat(rOpd, 64)
if err != nil {
return err
}
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(rOpdVal > lOpdVal)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
return nil
}

// calcLe evaluate less than or equal arithmetic operations.
func calcLe(rOpd, lOpd string, opdStack *Stack) error {
lOpdVal, err := strconv.ParseFloat(lOpd, 64)
if err != nil {
return err
}
rOpdVal, err := strconv.ParseFloat(rOpd, 64)
if err != nil {
return err
}
rOpd := opdStack.Pop().(efp.Token)
lOpd := opdStack.Pop().(efp.Token)
lOpdVal, err := strconv.ParseFloat(lOpd.TValue, 64)
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(rOpdVal >= lOpdVal)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
return nil
}

// calcG evaluate greater than or equal arithmetic operations.
func calcG(rOpd, lOpd string, opdStack *Stack) error {
lOpdVal, err := strconv.ParseFloat(lOpd, 64)
if err != nil {
return err
}
rOpdVal, err := strconv.ParseFloat(rOpd, 64)
if err != nil {
return err
}
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(rOpdVal < lOpdVal)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
return nil
}

// calcGe evaluate greater than or equal arithmetic operations.
func calcGe(rOpd, lOpd string, opdStack *Stack) error {
lOpdVal, err := strconv.ParseFloat(lOpd, 64)
if err != nil {
return err
}
rOpdVal, err := strconv.ParseFloat(rOpd.TValue, 64)
rOpdVal, err := strconv.ParseFloat(rOpd, 64)
if err != nil {
return err
}
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(rOpdVal <= lOpdVal)), 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})
return nil
}

// calcAdd evaluate addition arithmetic operations.
func calcAdd(rOpd, lOpd string, opdStack *Stack) error {
lOpdVal, err := strconv.ParseFloat(lOpd, 64)
if err != nil {
return err
}
rOpdVal, err := strconv.ParseFloat(rOpd, 64)
if err != nil {
return err
}
Expand All @@ -327,17 +414,12 @@ func calcAdd(opdStack *Stack) error {
}

// calcSubtract evaluate subtraction arithmetic operations.
func calcSubtract(opdStack *Stack) error {
if opdStack.Len() < 2 {
return errors.New("formula not valid")
}
rOpd := opdStack.Pop().(efp.Token)
lOpd := opdStack.Pop().(efp.Token)
lOpdVal, err := strconv.ParseFloat(lOpd.TValue, 64)
func calcSubtract(rOpd, lOpd string, opdStack *Stack) error {
lOpdVal, err := strconv.ParseFloat(lOpd, 64)
if err != nil {
return err
}
rOpdVal, err := strconv.ParseFloat(rOpd.TValue, 64)
rOpdVal, err := strconv.ParseFloat(rOpd, 64)
if err != nil {
return err
}
Expand All @@ -347,17 +429,12 @@ func calcSubtract(opdStack *Stack) error {
}

// calcMultiply evaluate multiplication arithmetic operations.
func calcMultiply(opdStack *Stack) error {
if opdStack.Len() < 2 {
return errors.New("formula not valid")
}
rOpd := opdStack.Pop().(efp.Token)
lOpd := opdStack.Pop().(efp.Token)
lOpdVal, err := strconv.ParseFloat(lOpd.TValue, 64)
func calcMultiply(rOpd, lOpd string, opdStack *Stack) error {
lOpdVal, err := strconv.ParseFloat(lOpd, 64)
if err != nil {
return err
}
rOpdVal, err := strconv.ParseFloat(rOpd.TValue, 64)
rOpdVal, err := strconv.ParseFloat(rOpd, 64)
if err != nil {
return err
}
Expand All @@ -366,18 +443,13 @@ func calcMultiply(opdStack *Stack) error {
return nil
}

// calcDivide evaluate division arithmetic operations.
func calcDivide(opdStack *Stack) error {
if opdStack.Len() < 2 {
return errors.New("formula not valid")
}
rOpd := opdStack.Pop().(efp.Token)
lOpd := opdStack.Pop().(efp.Token)
lOpdVal, err := strconv.ParseFloat(lOpd.TValue, 64)
// calcDiv evaluate division arithmetic operations.
func calcDiv(rOpd, lOpd string, opdStack *Stack) error {
lOpdVal, err := strconv.ParseFloat(lOpd, 64)
if err != nil {
return err
}
rOpdVal, err := strconv.ParseFloat(rOpd.TValue, 64)
rOpdVal, err := strconv.ParseFloat(rOpd, 64)
if err != nil {
return err
}
Expand All @@ -403,24 +475,36 @@ 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})
}

if opt.TValue == "+" {
if err := calcAdd(opdStack); err != nil {
return err
}
tokenCalcFunc := map[string]func(rOpd, lOpd string, opdStack *Stack) error{
"^": calcPow,
"*": calcMultiply,
"/": calcDiv,
"+": calcAdd,
"=": calcEq,
"<": calcL,
"<=": calcLe,
">": calcG,
">=": calcGe,
"&": calcSplice,
}
if opt.TValue == "-" && opt.TType == efp.TokenTypeOperatorInfix {
if err := calcSubtract(opdStack); err != nil {
return err
if opdStack.Len() < 2 {
return errors.New("formula not valid")
}
}
if opt.TValue == "*" {
if err := calcMultiply(opdStack); err != nil {
rOpd := opdStack.Pop().(efp.Token)
lOpd := opdStack.Pop().(efp.Token)
if err := calcSubtract(rOpd.TValue, lOpd.TValue, opdStack); err != nil {
return err
}
}
if opt.TValue == "/" {
if err := calcDivide(opdStack); err != nil {
fn, ok := tokenCalcFunc[opt.TValue]
if ok {
if opdStack.Len() < 2 {
return errors.New("formula not valid")
}
rOpd := opdStack.Pop().(efp.Token)
lOpd := opdStack.Pop().(efp.Token)
if err := fn(rOpd.TValue, lOpd.TValue, opdStack); err != nil {
return err
}
}
Expand Down Expand Up @@ -459,8 +543,8 @@ func (f *File) parseOperatorPrefixToken(optStack, opdStack *Stack, token efp.Tok
// isOperatorPrefixToken determine if the token is parse operator prefix
// token.
func isOperatorPrefixToken(token efp.Token) bool {
if (token.TValue == "-" && token.TType == efp.TokenTypeOperatorPrefix) ||
token.TValue == "+" || token.TValue == "-" || token.TValue == "*" || token.TValue == "/" {
_, ok := tokenPriority[token.TValue]
if (token.TValue == "-" && token.TType == efp.TokenTypeOperatorPrefix) || ok {
return true
}
return false
Expand Down Expand Up @@ -3140,3 +3224,87 @@ func (fn *formulaFuncs) NA(argsList *list.List) (result string, err error) {
result = formulaErrorNA
return
}

// Logical Functions

// AND function tests a number of supplied conditions and returns TRUE or
// FALSE.
func (fn *formulaFuncs) AND(argsList *list.List) (result string, err error) {
if argsList.Len() == 0 {
err = errors.New("AND requires at least 1 argument")
return
}
if argsList.Len() > 30 {
err = errors.New("AND accepts at most 30 arguments")
return
}
var and = true
var val float64
for arg := argsList.Front(); arg != nil; arg = arg.Next() {
token := arg.Value.(formulaArg)
switch token.Type {
case ArgUnknown:
continue
case ArgString:
if token.String == "TRUE" {
continue
}
if token.String == "FALSE" {
result = token.String
return
}
if val, err = strconv.ParseFloat(token.String, 64); err != nil {
err = errors.New(formulaErrorVALUE)
return
}
and = and && (val != 0)
case ArgMatrix:
// TODO
err = errors.New(formulaErrorVALUE)
return
}
}
result = strings.ToUpper(strconv.FormatBool(and))
return
}

// OR function tests a number of supplied conditions and returns either TRUE
// or FALSE.
func (fn *formulaFuncs) OR(argsList *list.List) (result string, err error) {
if argsList.Len() == 0 {
err = errors.New("OR requires at least 1 argument")
return
}
if argsList.Len() > 30 {
err = errors.New("OR accepts at most 30 arguments")
return
}
var or bool
var val float64
for arg := argsList.Front(); arg != nil; arg = arg.Next() {
token := arg.Value.(formulaArg)
switch token.Type {
case ArgUnknown:
continue
case ArgString:
if token.String == "FALSE" {
continue
}
if token.String == "TRUE" {
or = true
continue
}
if val, err = strconv.ParseFloat(token.String, 64); err != nil {
err = errors.New(formulaErrorVALUE)
return
}
or = val != 0
case ArgMatrix:
// TODO
err = errors.New(formulaErrorVALUE)
return
}
}
result = strings.ToUpper(strconv.FormatBool(or))
return
}

0 comments on commit 02530e8

Please sign in to comment.