Skip to content

Commit 36df513

Browse files
committed
- formula engine: reduce cyclomatic complexity
- styles: allow empty and default cell formats, qax-os#628
1 parent 510f730 commit 36df513

8 files changed

+206
-154
lines changed

calc.go

+176-127
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@ type formulaArg struct {
6363
type formulaFuncs struct{}
6464

6565
// CalcCellValue provides a function to get calculated cell value. This
66-
// feature is currently in beta. Array formula, table formula and some other
67-
// formulas are not supported currently.
66+
// feature is currently in working processing. Array formula, table formula
67+
// and some other formulas are not supported currently.
6868
func (f *File) CalcCellValue(sheet, cell string) (result string, err error) {
6969
var (
7070
formula string
@@ -265,6 +265,89 @@ func (f *File) evalInfixExp(sheet string, tokens []efp.Token) (efp.Token, error)
265265
return opdStack.Peek().(efp.Token), err
266266
}
267267

268+
// calcAdd evaluate addition arithmetic operations.
269+
func calcAdd(opdStack *Stack) error {
270+
if opdStack.Len() < 2 {
271+
return errors.New("formula not valid")
272+
}
273+
rOpd := opdStack.Pop().(efp.Token)
274+
lOpd := opdStack.Pop().(efp.Token)
275+
lOpdVal, err := strconv.ParseFloat(lOpd.TValue, 64)
276+
if err != nil {
277+
return err
278+
}
279+
rOpdVal, err := strconv.ParseFloat(rOpd.TValue, 64)
280+
if err != nil {
281+
return err
282+
}
283+
result := lOpdVal + rOpdVal
284+
opdStack.Push(efp.Token{TValue: fmt.Sprintf("%g", result), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
285+
return nil
286+
}
287+
288+
// calcAdd evaluate subtraction arithmetic operations.
289+
func calcSubtract(opdStack *Stack) error {
290+
if opdStack.Len() < 2 {
291+
return errors.New("formula not valid")
292+
}
293+
rOpd := opdStack.Pop().(efp.Token)
294+
lOpd := opdStack.Pop().(efp.Token)
295+
lOpdVal, err := strconv.ParseFloat(lOpd.TValue, 64)
296+
if err != nil {
297+
return err
298+
}
299+
rOpdVal, err := strconv.ParseFloat(rOpd.TValue, 64)
300+
if err != nil {
301+
return err
302+
}
303+
result := lOpdVal - rOpdVal
304+
opdStack.Push(efp.Token{TValue: fmt.Sprintf("%g", result), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
305+
return nil
306+
}
307+
308+
// calcAdd evaluate multiplication arithmetic operations.
309+
func calcMultiply(opdStack *Stack) error {
310+
if opdStack.Len() < 2 {
311+
return errors.New("formula not valid")
312+
}
313+
rOpd := opdStack.Pop().(efp.Token)
314+
lOpd := opdStack.Pop().(efp.Token)
315+
lOpdVal, err := strconv.ParseFloat(lOpd.TValue, 64)
316+
if err != nil {
317+
return err
318+
}
319+
rOpdVal, err := strconv.ParseFloat(rOpd.TValue, 64)
320+
if err != nil {
321+
return err
322+
}
323+
result := lOpdVal * rOpdVal
324+
opdStack.Push(efp.Token{TValue: fmt.Sprintf("%g", result), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
325+
return nil
326+
}
327+
328+
// calcAdd evaluate division arithmetic operations.
329+
func calcDivide(opdStack *Stack) error {
330+
if opdStack.Len() < 2 {
331+
return errors.New("formula not valid")
332+
}
333+
rOpd := opdStack.Pop().(efp.Token)
334+
lOpd := opdStack.Pop().(efp.Token)
335+
lOpdVal, err := strconv.ParseFloat(lOpd.TValue, 64)
336+
if err != nil {
337+
return err
338+
}
339+
rOpdVal, err := strconv.ParseFloat(rOpd.TValue, 64)
340+
if err != nil {
341+
return err
342+
}
343+
result := lOpdVal / rOpdVal
344+
if rOpdVal == 0 {
345+
return errors.New(formulaErrorDIV)
346+
}
347+
opdStack.Push(efp.Token{TValue: fmt.Sprintf("%g", result), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
348+
return nil
349+
}
350+
268351
// calculate evaluate basic arithmetic operations.
269352
func calculate(opdStack *Stack, opt efp.Token) error {
270353
if opt.TValue == "-" && opt.TType == efp.TokenTypeOperatorPrefix {
@@ -279,80 +362,69 @@ func calculate(opdStack *Stack, opt efp.Token) error {
279362
result := 0 - opdVal
280363
opdStack.Push(efp.Token{TValue: fmt.Sprintf("%g", result), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
281364
}
365+
282366
if opt.TValue == "+" {
283-
if opdStack.Len() < 2 {
284-
return errors.New("formula not valid")
285-
}
286-
rOpd := opdStack.Pop().(efp.Token)
287-
lOpd := opdStack.Pop().(efp.Token)
288-
lOpdVal, err := strconv.ParseFloat(lOpd.TValue, 64)
289-
if err != nil {
367+
if err := calcAdd(opdStack); err != nil {
290368
return err
291369
}
292-
rOpdVal, err := strconv.ParseFloat(rOpd.TValue, 64)
293-
if err != nil {
294-
return err
295-
}
296-
result := lOpdVal + rOpdVal
297-
opdStack.Push(efp.Token{TValue: fmt.Sprintf("%g", result), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
298370
}
299371
if opt.TValue == "-" && opt.TType == efp.TokenTypeOperatorInfix {
300-
if opdStack.Len() < 2 {
301-
return errors.New("formula not valid")
302-
}
303-
rOpd := opdStack.Pop().(efp.Token)
304-
lOpd := opdStack.Pop().(efp.Token)
305-
lOpdVal, err := strconv.ParseFloat(lOpd.TValue, 64)
306-
if err != nil {
372+
if err := calcSubtract(opdStack); err != nil {
307373
return err
308374
}
309-
rOpdVal, err := strconv.ParseFloat(rOpd.TValue, 64)
310-
if err != nil {
311-
return err
312-
}
313-
result := lOpdVal - rOpdVal
314-
opdStack.Push(efp.Token{TValue: fmt.Sprintf("%g", result), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
315375
}
316376
if opt.TValue == "*" {
317-
if opdStack.Len() < 2 {
318-
return errors.New("formula not valid")
319-
}
320-
rOpd := opdStack.Pop().(efp.Token)
321-
lOpd := opdStack.Pop().(efp.Token)
322-
lOpdVal, err := strconv.ParseFloat(lOpd.TValue, 64)
323-
if err != nil {
377+
if err := calcMultiply(opdStack); err != nil {
324378
return err
325379
}
326-
rOpdVal, err := strconv.ParseFloat(rOpd.TValue, 64)
327-
if err != nil {
328-
return err
329-
}
330-
result := lOpdVal * rOpdVal
331-
opdStack.Push(efp.Token{TValue: fmt.Sprintf("%g", result), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
332380
}
333381
if opt.TValue == "/" {
334-
if opdStack.Len() < 2 {
335-
return errors.New("formula not valid")
336-
}
337-
rOpd := opdStack.Pop().(efp.Token)
338-
lOpd := opdStack.Pop().(efp.Token)
339-
lOpdVal, err := strconv.ParseFloat(lOpd.TValue, 64)
340-
if err != nil {
382+
if err := calcDivide(opdStack); err != nil {
341383
return err
342384
}
343-
rOpdVal, err := strconv.ParseFloat(rOpd.TValue, 64)
344-
if err != nil {
345-
return err
346-
}
347-
result := lOpdVal / rOpdVal
348-
if rOpdVal == 0 {
349-
return errors.New(formulaErrorDIV)
350-
}
351-
opdStack.Push(efp.Token{TValue: fmt.Sprintf("%g", result), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
352385
}
353386
return nil
354387
}
355388

389+
// parseOperatorPrefixToken parse operator prefix token.
390+
func (f *File) parseOperatorPrefixToken(optStack, opdStack *Stack, token efp.Token) (err error) {
391+
if optStack.Len() == 0 {
392+
optStack.Push(token)
393+
} else {
394+
tokenPriority := getPriority(token)
395+
topOpt := optStack.Peek().(efp.Token)
396+
topOptPriority := getPriority(topOpt)
397+
if tokenPriority > topOptPriority {
398+
optStack.Push(token)
399+
} else {
400+
for tokenPriority <= topOptPriority {
401+
optStack.Pop()
402+
if err = calculate(opdStack, topOpt); err != nil {
403+
return
404+
}
405+
if optStack.Len() > 0 {
406+
topOpt = optStack.Peek().(efp.Token)
407+
topOptPriority = getPriority(topOpt)
408+
continue
409+
}
410+
break
411+
}
412+
optStack.Push(token)
413+
}
414+
}
415+
return
416+
}
417+
418+
// isOperatorPrefixToken determine if the token is parse operator prefix
419+
// token.
420+
func isOperatorPrefixToken(token efp.Token) bool {
421+
if (token.TValue == "-" && token.TType == efp.TokenTypeOperatorPrefix) ||
422+
token.TValue == "+" || token.TValue == "-" || token.TValue == "*" || token.TValue == "/" {
423+
return true
424+
}
425+
return false
426+
}
427+
356428
// parseToken parse basic arithmetic operator priority and evaluate based on
357429
// operators and operands.
358430
func (f *File) parseToken(sheet string, token efp.Token, opdStack, optStack *Stack) error {
@@ -369,30 +441,9 @@ func (f *File) parseToken(sheet string, token efp.Token, opdStack, optStack *Sta
369441
token.TType = efp.TokenTypeOperand
370442
token.TSubType = efp.TokenSubTypeNumber
371443
}
372-
if (token.TValue == "-" && token.TType == efp.TokenTypeOperatorPrefix) || token.TValue == "+" || token.TValue == "-" || token.TValue == "*" || token.TValue == "/" {
373-
if optStack.Len() == 0 {
374-
optStack.Push(token)
375-
} else {
376-
tokenPriority := getPriority(token)
377-
topOpt := optStack.Peek().(efp.Token)
378-
topOptPriority := getPriority(topOpt)
379-
if tokenPriority > topOptPriority {
380-
optStack.Push(token)
381-
} else {
382-
for tokenPriority <= topOptPriority {
383-
optStack.Pop()
384-
if err := calculate(opdStack, topOpt); err != nil {
385-
return err
386-
}
387-
if optStack.Len() > 0 {
388-
topOpt = optStack.Peek().(efp.Token)
389-
topOptPriority = getPriority(topOpt)
390-
continue
391-
}
392-
break
393-
}
394-
optStack.Push(token)
395-
}
444+
if isOperatorPrefixToken(token) {
445+
if err := f.parseOperatorPrefixToken(optStack, opdStack, token); err != nil {
446+
return err
396447
}
397448
}
398449
if token.TType == efp.TokenTypeSubexpression && token.TSubType == efp.TokenSubTypeStart { // (
@@ -461,11 +512,44 @@ func (f *File) parseReference(sheet, reference string) (result []string, matrix
461512
return
462513
}
463514

515+
// prepareValueRange prepare value range.
516+
func prepareValueRange(cr cellRange, valueRange []int) {
517+
if cr.From.Row < valueRange[0] {
518+
valueRange[0] = cr.From.Row
519+
}
520+
if cr.From.Col < valueRange[2] {
521+
valueRange[2] = cr.From.Col
522+
}
523+
if cr.To.Row > valueRange[0] {
524+
valueRange[1] = cr.To.Row
525+
}
526+
if cr.To.Col > valueRange[3] {
527+
valueRange[3] = cr.To.Col
528+
}
529+
}
530+
531+
// prepareValueRef prepare value reference.
532+
func prepareValueRef(cr cellRef, valueRange []int) {
533+
if cr.Row < valueRange[0] {
534+
valueRange[0] = cr.Row
535+
}
536+
if cr.Col < valueRange[2] {
537+
valueRange[2] = cr.Col
538+
}
539+
if cr.Row > valueRange[0] {
540+
valueRange[1] = cr.Row
541+
}
542+
if cr.Col > valueRange[3] {
543+
valueRange[3] = cr.Col
544+
}
545+
}
546+
464547
// rangeResolver extract value as string from given reference and range list.
465-
// This function will not ignore the empty cell. For example,
466-
// A1:A2:A2:B3 will be reference A1:B3.
548+
// This function will not ignore the empty cell. For example, A1:A2:A2:B3 will
549+
// be reference A1:B3.
467550
func (f *File) rangeResolver(cellRefs, cellRanges *list.List) (result []string, matrix [][]string, err error) {
468-
var fromRow, toRow, fromCol, toCol int = 1, 1, 1, 1
551+
// value range order: from row, to row, from column, to column
552+
valueRange := []int{1, 1, 1, 1}
469553
var sheet string
470554
filter := map[string]string{}
471555
// prepare value range
@@ -476,18 +560,7 @@ func (f *File) rangeResolver(cellRefs, cellRanges *list.List) (result []string,
476560
}
477561
rng := []int{cr.From.Col, cr.From.Row, cr.To.Col, cr.To.Row}
478562
sortCoordinates(rng)
479-
if cr.From.Row < fromRow {
480-
fromRow = cr.From.Row
481-
}
482-
if cr.From.Col < fromCol {
483-
fromCol = cr.From.Col
484-
}
485-
if cr.To.Row > fromRow {
486-
toRow = cr.To.Row
487-
}
488-
if cr.To.Col > toCol {
489-
toCol = cr.To.Col
490-
}
563+
prepareValueRange(cr, valueRange)
491564
if cr.From.Sheet != "" {
492565
sheet = cr.From.Sheet
493566
}
@@ -497,24 +570,13 @@ func (f *File) rangeResolver(cellRefs, cellRanges *list.List) (result []string,
497570
if cr.Sheet != "" {
498571
sheet = cr.Sheet
499572
}
500-
if cr.Row < fromRow {
501-
fromRow = cr.Row
502-
}
503-
if cr.Col < fromCol {
504-
fromCol = cr.Col
505-
}
506-
if cr.Row > fromRow {
507-
toRow = cr.Row
508-
}
509-
if cr.Col > toCol {
510-
toCol = cr.Col
511-
}
573+
prepareValueRef(cr, valueRange)
512574
}
513575
// extract value from ranges
514576
if cellRanges.Len() > 0 {
515-
for row := fromRow; row <= toRow; row++ {
577+
for row := valueRange[0]; row <= valueRange[1]; row++ {
516578
var matrixRow = []string{}
517-
for col := fromCol; col <= toCol; col++ {
579+
for col := valueRange[2]; col <= valueRange[3]; col++ {
518580
var cell, value string
519581
if cell, err = CoordinatesToCellName(col, row); err != nil {
520582
return
@@ -672,28 +734,15 @@ func (fn *formulaFuncs) ARABIC(argsList *list.List) (result string, err error) {
672734
err = errors.New("ARABIC requires 1 numeric argument")
673735
return
674736
}
737+
charMap := map[rune]float64{'I': 1, 'V': 5, 'X': 10, 'L': 50, 'C': 100, 'D': 500, 'M': 1000}
675738
val, last, prefix := 0.0, 0.0, 1.0
676739
for _, char := range argsList.Front().Value.(formulaArg).Value {
677740
digit := 0.0
678-
switch char {
679-
case '-':
741+
if char == '-' {
680742
prefix = -1
681743
continue
682-
case 'I':
683-
digit = 1
684-
case 'V':
685-
digit = 5
686-
case 'X':
687-
digit = 10
688-
case 'L':
689-
digit = 50
690-
case 'C':
691-
digit = 100
692-
case 'D':
693-
digit = 500
694-
case 'M':
695-
digit = 1000
696744
}
745+
digit, _ = charMap[char]
697746
val += digit
698747
switch {
699748
case last == digit && (last == 5 || last == 50 || last == 500):
@@ -850,7 +899,7 @@ func (fn *formulaFuncs) BASE(argsList *list.List) (result string, err error) {
850899
return
851900
}
852901
if radix < 2 || radix > 36 {
853-
err = errors.New("radix must be an integer 2 and 36")
902+
err = errors.New("radix must be an integer >= 2 and <= 36")
854903
return
855904
}
856905
if argsList.Len() > 2 {

0 commit comments

Comments
 (0)