forked from p30928647/excelize
ref #65: formula function INDIRECT support and formula engine improvement
Support calculation with the none parameter formula function after infix operator notation
This commit is contained in:
parent
471c8f22d0
commit
42a9665aa9
137
calc.go
137
calc.go
|
@ -459,6 +459,7 @@ type formulaFuncs struct {
|
|||
// IMSUM
|
||||
// IMTAN
|
||||
// INDEX
|
||||
// INDIRECT
|
||||
// INT
|
||||
// INTRATE
|
||||
// IPMT
|
||||
|
@ -851,22 +852,7 @@ func (f *File) evalInfixExpFunc(sheet, cell string, token, nextToken efp.Token,
|
|||
if !isFunctionStopToken(token) {
|
||||
return nil
|
||||
}
|
||||
// current token is function stop
|
||||
for opftStack.Peek().(efp.Token) != opfStack.Peek().(efp.Token) {
|
||||
// calculate trigger
|
||||
topOpt := opftStack.Peek().(efp.Token)
|
||||
if err := calculate(opfdStack, topOpt); err != nil {
|
||||
argsStack.Peek().(*list.List).PushBack(newErrorFormulaArg(err.Error(), err.Error()))
|
||||
opftStack.Pop()
|
||||
continue
|
||||
}
|
||||
opftStack.Pop()
|
||||
}
|
||||
|
||||
// push opfd to args
|
||||
if opfdStack.Len() > 0 {
|
||||
argsStack.Peek().(*list.List).PushBack(newStringFormulaArg(opfdStack.Pop().(efp.Token).TValue))
|
||||
}
|
||||
prepareEvalInfixExp(opfStack, opftStack, opfdStack, argsStack)
|
||||
// call formula function to evaluate
|
||||
arg := callFuncByName(&formulaFuncs{f: f, sheet: sheet, cell: cell}, strings.NewReplacer(
|
||||
"_xlfn.", "", ".", "dot").Replace(opfStack.Peek().(efp.Token).TValue),
|
||||
|
@ -894,6 +880,34 @@ func (f *File) evalInfixExpFunc(sheet, cell string, token, nextToken efp.Token,
|
|||
return nil
|
||||
}
|
||||
|
||||
// prepareEvalInfixExp check the token and stack state for formula function
|
||||
// evaluate.
|
||||
func prepareEvalInfixExp(opfStack, opftStack, opfdStack, argsStack *Stack) {
|
||||
// current token is function stop
|
||||
for opftStack.Peek().(efp.Token) != opfStack.Peek().(efp.Token) {
|
||||
// calculate trigger
|
||||
topOpt := opftStack.Peek().(efp.Token)
|
||||
if err := calculate(opfdStack, topOpt); err != nil {
|
||||
argsStack.Peek().(*list.List).PushBack(newErrorFormulaArg(err.Error(), err.Error()))
|
||||
opftStack.Pop()
|
||||
continue
|
||||
}
|
||||
opftStack.Pop()
|
||||
}
|
||||
argument := true
|
||||
if opftStack.Len() > 2 && opfdStack.Len() == 1 {
|
||||
topOpt := opftStack.Pop()
|
||||
if opftStack.Peek().(efp.Token).TType == efp.TokenTypeOperatorInfix {
|
||||
argument = false
|
||||
}
|
||||
opftStack.Push(topOpt)
|
||||
}
|
||||
// push opfd to args
|
||||
if argument && opfdStack.Len() > 0 {
|
||||
argsStack.Peek().(*list.List).PushBack(newStringFormulaArg(opfdStack.Pop().(efp.Token).TValue))
|
||||
}
|
||||
}
|
||||
|
||||
// calcPow evaluate exponentiation arithmetic operations.
|
||||
func calcPow(rOpd, lOpd efp.Token, opdStack *Stack) error {
|
||||
lOpdVal, err := strconv.ParseFloat(lOpd.TValue, 64)
|
||||
|
@ -1080,6 +1094,16 @@ 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 == "-" && opt.TType == efp.TokenTypeOperatorInfix {
|
||||
if opdStack.Len() < 2 {
|
||||
return ErrInvalidFormula
|
||||
}
|
||||
rOpd := opdStack.Pop().(efp.Token)
|
||||
lOpd := opdStack.Pop().(efp.Token)
|
||||
if err := calcSubtract(rOpd, lOpd, opdStack); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
tokenCalcFunc := map[string]func(rOpd, lOpd efp.Token, opdStack *Stack) error{
|
||||
"^": calcPow,
|
||||
"*": calcMultiply,
|
||||
|
@ -1093,29 +1117,16 @@ func calculate(opdStack *Stack, opt efp.Token) error {
|
|||
">=": calcGe,
|
||||
"&": calcSplice,
|
||||
}
|
||||
if opt.TValue == "-" && opt.TType == efp.TokenTypeOperatorInfix {
|
||||
if opdStack.Len() < 2 {
|
||||
return ErrInvalidFormula
|
||||
}
|
||||
rOpd := opdStack.Pop().(efp.Token)
|
||||
lOpd := opdStack.Pop().(efp.Token)
|
||||
if err := calcSubtract(rOpd, lOpd, opdStack); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
fn, ok := tokenCalcFunc[opt.TValue]
|
||||
if ok {
|
||||
if opdStack.Len() < 2 {
|
||||
if opdStack.Len() == 1 {
|
||||
rOpd := opdStack.Pop().(efp.Token)
|
||||
if rOpd.TSubType == efp.TokenSubTypeError {
|
||||
return errors.New(rOpd.TValue)
|
||||
}
|
||||
}
|
||||
return ErrInvalidFormula
|
||||
}
|
||||
rOpd := opdStack.Pop().(efp.Token)
|
||||
lOpd := opdStack.Pop().(efp.Token)
|
||||
if rOpd.TSubType == efp.TokenSubTypeError {
|
||||
return errors.New(rOpd.TValue)
|
||||
}
|
||||
if lOpd.TSubType == efp.TokenSubTypeError {
|
||||
return errors.New(lOpd.TValue)
|
||||
}
|
||||
|
@ -9959,6 +9970,68 @@ func (fn *formulaFuncs) INDEX(argsList *list.List) formulaArg {
|
|||
return cells.List[colIdx]
|
||||
}
|
||||
|
||||
// INDIRECT function converts a text string into a cell reference. The syntax
|
||||
// of the Indirect function is:
|
||||
//
|
||||
// INDIRECT(ref_text,[a1])
|
||||
//
|
||||
func (fn *formulaFuncs) INDIRECT(argsList *list.List) formulaArg {
|
||||
if argsList.Len() != 1 && argsList.Len() != 2 {
|
||||
return newErrorFormulaArg(formulaErrorVALUE, "INDIRECT requires 1 or 2 arguments")
|
||||
}
|
||||
refText := argsList.Front().Value.(formulaArg).Value()
|
||||
a1 := newBoolFormulaArg(true)
|
||||
if argsList.Len() == 2 {
|
||||
if a1 = argsList.Back().Value.(formulaArg).ToBool(); a1.Type != ArgNumber {
|
||||
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
|
||||
}
|
||||
}
|
||||
R1C1ToA1 := func(ref string) (cell string, err error) {
|
||||
parts := strings.Split(strings.TrimLeft(ref, "R"), "C")
|
||||
if len(parts) != 2 {
|
||||
return
|
||||
}
|
||||
row, err := strconv.Atoi(parts[0])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
col, err := strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
cell, err = CoordinatesToCellName(col, row)
|
||||
return
|
||||
}
|
||||
refs := strings.Split(refText, ":")
|
||||
fromRef, toRef := refs[0], ""
|
||||
if len(refs) == 2 {
|
||||
toRef = refs[1]
|
||||
}
|
||||
if a1.Number == 0 {
|
||||
from, err := R1C1ToA1(refs[0])
|
||||
if err != nil {
|
||||
return newErrorFormulaArg(formulaErrorREF, formulaErrorREF)
|
||||
}
|
||||
fromRef = from
|
||||
if len(refs) == 2 {
|
||||
to, err := R1C1ToA1(refs[1])
|
||||
if err != nil {
|
||||
return newErrorFormulaArg(formulaErrorREF, formulaErrorREF)
|
||||
}
|
||||
toRef = to
|
||||
}
|
||||
}
|
||||
if len(refs) == 1 {
|
||||
value, err := fn.f.GetCellValue(fn.sheet, fromRef)
|
||||
if err != nil {
|
||||
return newErrorFormulaArg(formulaErrorREF, formulaErrorREF)
|
||||
}
|
||||
return newStringFormulaArg(value)
|
||||
}
|
||||
arg, _ := fn.f.parseReference(fn.sheet, fromRef+":"+toRef)
|
||||
return arg
|
||||
}
|
||||
|
||||
// LOOKUP function performs an approximate match lookup in a one-column or
|
||||
// one-row range, and returns the corresponding value from another one-column
|
||||
// or one-row range. The syntax of the function is:
|
||||
|
|
22
calc_test.go
22
calc_test.go
|
@ -724,6 +724,7 @@ func TestCalcCellValue(t *testing.T) {
|
|||
"=((3+5*2)+3)/5+(-6)/4*2+3": "3.2",
|
||||
"=1+SUM(SUM(1,2*3),4)*-4/2+5+(4+2)*3": "2",
|
||||
"=1+SUM(SUM(1,2*3),4)*4/3+5+(4+2)*3": "38.666666666666664",
|
||||
"=SUM(1+ROW())": "2",
|
||||
// SUMIF
|
||||
`=SUMIF(F1:F5, "")`: "0",
|
||||
`=SUMIF(A1:A5, "3")`: "3",
|
||||
|
@ -1447,6 +1448,16 @@ func TestCalcCellValue(t *testing.T) {
|
|||
"=SUM(INDEX(A1:B2,2,0))": "7",
|
||||
"=SUM(INDEX(A1:B4,0,2))": "9",
|
||||
"=SUM(INDEX(E1:F5,5,2))": "34440",
|
||||
// INDIRECT
|
||||
"=INDIRECT(\"E1\")": "Team",
|
||||
"=INDIRECT(\"E\"&1)": "Team",
|
||||
"=INDIRECT(\"E\"&ROW())": "Team",
|
||||
"=INDIRECT(\"E\"&ROW(),TRUE)": "Team",
|
||||
"=INDIRECT(\"R1C5\",FALSE)": "Team",
|
||||
"=INDIRECT(\"R\"&1&\"C\"&5,FALSE)": "Team",
|
||||
"=SUM(INDIRECT(\"A1:B2\"))": "12",
|
||||
"=SUM(INDIRECT(\"A1:B2\",TRUE))": "12",
|
||||
"=SUM(INDIRECT(\"R1C1:R2C2\",FALSE))": "12",
|
||||
// LOOKUP
|
||||
"=LOOKUP(F8,F8:F9,F8:F9)": "32080",
|
||||
"=LOOKUP(F8,F8:F9,D8:D9)": "Feb",
|
||||
|
@ -2872,6 +2883,17 @@ func TestCalcCellValue(t *testing.T) {
|
|||
"=INDEX(A1:A2,0,0)": "#VALUE!",
|
||||
"=INDEX(0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
|
||||
"=INDEX(0,0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
|
||||
// INDIRECT
|
||||
"=INDIRECT()": "INDIRECT requires 1 or 2 arguments",
|
||||
"=INDIRECT(\"E\"&1,TRUE,1)": "INDIRECT requires 1 or 2 arguments",
|
||||
"=INDIRECT(\"R1048577C1\",\"\")": "#VALUE!",
|
||||
"=INDIRECT(\"E1048577\")": "#REF!",
|
||||
"=INDIRECT(\"R1048577C1\",FALSE)": "#REF!",
|
||||
"=INDIRECT(\"R1C16385\",FALSE)": "#REF!",
|
||||
"=INDIRECT(\"\",FALSE)": "#REF!",
|
||||
"=INDIRECT(\"R C1\",FALSE)": "#REF!",
|
||||
"=INDIRECT(\"R1C \",FALSE)": "#REF!",
|
||||
"=INDIRECT(\"R1C1:R2C \",FALSE)": "#REF!",
|
||||
// LOOKUP
|
||||
"=LOOKUP()": "LOOKUP requires at least 2 arguments",
|
||||
"=LOOKUP(D2,D1,D2)": "LOOKUP requires second argument of table array",
|
||||
|
|
Loading…
Reference in New Issue