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
|
// IMSUM
|
||||||
// IMTAN
|
// IMTAN
|
||||||
// INDEX
|
// INDEX
|
||||||
|
// INDIRECT
|
||||||
// INT
|
// INT
|
||||||
// INTRATE
|
// INTRATE
|
||||||
// IPMT
|
// IPMT
|
||||||
|
@ -851,22 +852,7 @@ func (f *File) evalInfixExpFunc(sheet, cell string, token, nextToken efp.Token,
|
||||||
if !isFunctionStopToken(token) {
|
if !isFunctionStopToken(token) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// current token is function stop
|
prepareEvalInfixExp(opfStack, opftStack, opfdStack, argsStack)
|
||||||
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))
|
|
||||||
}
|
|
||||||
// call formula function to evaluate
|
// call formula function to evaluate
|
||||||
arg := callFuncByName(&formulaFuncs{f: f, sheet: sheet, cell: cell}, strings.NewReplacer(
|
arg := callFuncByName(&formulaFuncs{f: f, sheet: sheet, cell: cell}, strings.NewReplacer(
|
||||||
"_xlfn.", "", ".", "dot").Replace(opfStack.Peek().(efp.Token).TValue),
|
"_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
|
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.
|
// calcPow evaluate exponentiation arithmetic operations.
|
||||||
func calcPow(rOpd, lOpd efp.Token, opdStack *Stack) error {
|
func calcPow(rOpd, lOpd efp.Token, opdStack *Stack) error {
|
||||||
lOpdVal, err := strconv.ParseFloat(lOpd.TValue, 64)
|
lOpdVal, err := strconv.ParseFloat(lOpd.TValue, 64)
|
||||||
|
@ -1080,6 +1094,16 @@ func calculate(opdStack *Stack, opt efp.Token) error {
|
||||||
result := 0 - opdVal
|
result := 0 - opdVal
|
||||||
opdStack.Push(efp.Token{TValue: fmt.Sprintf("%g", result), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
|
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{
|
tokenCalcFunc := map[string]func(rOpd, lOpd efp.Token, opdStack *Stack) error{
|
||||||
"^": calcPow,
|
"^": calcPow,
|
||||||
"*": calcMultiply,
|
"*": calcMultiply,
|
||||||
|
@ -1093,29 +1117,16 @@ func calculate(opdStack *Stack, opt efp.Token) error {
|
||||||
">=": calcGe,
|
">=": calcGe,
|
||||||
"&": calcSplice,
|
"&": 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]
|
fn, ok := tokenCalcFunc[opt.TValue]
|
||||||
if ok {
|
if ok {
|
||||||
if opdStack.Len() < 2 {
|
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
|
return ErrInvalidFormula
|
||||||
}
|
}
|
||||||
rOpd := opdStack.Pop().(efp.Token)
|
rOpd := opdStack.Pop().(efp.Token)
|
||||||
lOpd := opdStack.Pop().(efp.Token)
|
lOpd := opdStack.Pop().(efp.Token)
|
||||||
|
if rOpd.TSubType == efp.TokenSubTypeError {
|
||||||
|
return errors.New(rOpd.TValue)
|
||||||
|
}
|
||||||
if lOpd.TSubType == efp.TokenSubTypeError {
|
if lOpd.TSubType == efp.TokenSubTypeError {
|
||||||
return errors.New(lOpd.TValue)
|
return errors.New(lOpd.TValue)
|
||||||
}
|
}
|
||||||
|
@ -9959,6 +9970,68 @@ func (fn *formulaFuncs) INDEX(argsList *list.List) formulaArg {
|
||||||
return cells.List[colIdx]
|
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
|
// LOOKUP function performs an approximate match lookup in a one-column or
|
||||||
// one-row range, and returns the corresponding value from another one-column
|
// one-row range, and returns the corresponding value from another one-column
|
||||||
// or one-row range. The syntax of the function is:
|
// 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",
|
"=((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/2+5+(4+2)*3": "2",
|
||||||
"=1+SUM(SUM(1,2*3),4)*4/3+5+(4+2)*3": "38.666666666666664",
|
"=1+SUM(SUM(1,2*3),4)*4/3+5+(4+2)*3": "38.666666666666664",
|
||||||
|
"=SUM(1+ROW())": "2",
|
||||||
// SUMIF
|
// SUMIF
|
||||||
`=SUMIF(F1:F5, "")`: "0",
|
`=SUMIF(F1:F5, "")`: "0",
|
||||||
`=SUMIF(A1:A5, "3")`: "3",
|
`=SUMIF(A1:A5, "3")`: "3",
|
||||||
|
@ -1447,6 +1448,16 @@ func TestCalcCellValue(t *testing.T) {
|
||||||
"=SUM(INDEX(A1:B2,2,0))": "7",
|
"=SUM(INDEX(A1:B2,2,0))": "7",
|
||||||
"=SUM(INDEX(A1:B4,0,2))": "9",
|
"=SUM(INDEX(A1:B4,0,2))": "9",
|
||||||
"=SUM(INDEX(E1:F5,5,2))": "34440",
|
"=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
|
||||||
"=LOOKUP(F8,F8:F9,F8:F9)": "32080",
|
"=LOOKUP(F8,F8:F9,F8:F9)": "32080",
|
||||||
"=LOOKUP(F8,F8:F9,D8:D9)": "Feb",
|
"=LOOKUP(F8,F8:F9,D8:D9)": "Feb",
|
||||||
|
@ -2872,6 +2883,17 @@ func TestCalcCellValue(t *testing.T) {
|
||||||
"=INDEX(A1:A2,0,0)": "#VALUE!",
|
"=INDEX(A1:A2,0,0)": "#VALUE!",
|
||||||
"=INDEX(0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
|
"=INDEX(0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
|
||||||
"=INDEX(0,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()": "LOOKUP requires at least 2 arguments",
|
"=LOOKUP()": "LOOKUP requires at least 2 arguments",
|
||||||
"=LOOKUP(D2,D1,D2)": "LOOKUP requires second argument of table array",
|
"=LOOKUP(D2,D1,D2)": "LOOKUP requires second argument of table array",
|
||||||
|
|
Loading…
Reference in New Issue