diff options
Diffstat (limited to 'calc.go')
-rw-r--r-- | calc.go | 238 |
1 files changed, 207 insertions, 31 deletions
@@ -18,6 +18,7 @@ import ( "fmt" "math" "math/rand" + "net/url" "reflect" "regexp" "sort" @@ -99,13 +100,15 @@ const ( // formulaArg is the argument of a formula or function. type formulaArg struct { - Number float64 - String string - List []formulaArg - Matrix [][]formulaArg - Boolean bool - Error string - Type ArgType + f *File + SheetName string + Number float64 + String string + List []formulaArg + Matrix [][]formulaArg + Boolean bool + Error string + Type ArgType } // Value returns a string data type of the formula argument. @@ -162,7 +165,10 @@ func (fa formulaArg) ToBool() formulaArg { } // formulaFuncs is the type of the formula functions. -type formulaFuncs struct{} +type formulaFuncs struct { + f *File + sheet string +} // tokenPriority defined basic arithmetic operator priority. var tokenPriority = map[string]int{ @@ -184,7 +190,7 @@ var tokenPriority = map[string]int{ // feature is currently in working processing. Array formula, table formula // and some other formulas are not supported currently. // -// Supported formulas: +// Supported formula functions: // // ABS // ACOS @@ -215,16 +221,20 @@ var tokenPriority = map[string]int{ // DATE // DECIMAL // DEGREES +// ENCODEURL // EVEN +// EXACT // EXP // FACT // FACTDOUBLE +// FALSE // FLOOR // FLOOR.MATH // FLOOR.PRECISE // GCD // HLOOKUP // IF +// IFERROR // INT // ISBLANK // ISERR @@ -234,9 +244,11 @@ var tokenPriority = map[string]int{ // ISNONTEXT // ISNUMBER // ISODD +// ISTEXT // ISO.CEILING // LCM // LEN +// LENB // LN // LOG // LOG10 @@ -249,6 +261,7 @@ var tokenPriority = map[string]int{ // MULTINOMIAL // MUNIT // NA +// NOT // ODD // OR // PI @@ -259,11 +272,13 @@ var tokenPriority = map[string]int{ // RADIANS // RAND // RANDBETWEEN +// REPT // ROUND // ROUNDDOWN // ROUNDUP // SEC // SECH +// SHEET // SIGN // SIN // SINH @@ -275,6 +290,7 @@ var tokenPriority = map[string]int{ // TAN // TANH // TRIM +// TRUE // TRUNC // UPPER // VLOOKUP @@ -445,15 +461,12 @@ func (f *File) evalInfixExp(sheet string, tokens []efp.Token) (efp.Token, error) // calculate trigger topOpt := opftStack.Peek().(efp.Token) if err := calculate(opfdStack, topOpt); err != nil { - return efp.Token{}, err + argsStack.Peek().(*list.List).PushFront(newErrorFormulaArg(formulaErrorVALUE, err.Error())) } opftStack.Pop() } if !opfdStack.Empty() { - argsStack.Peek().(*list.List).PushBack(formulaArg{ - String: opfdStack.Pop().(efp.Token).TValue, - Type: ArgString, - }) + argsStack.Peek().(*list.List).PushBack(newStringFormulaArg(opfdStack.Pop().(efp.Token).TValue)) } continue } @@ -462,20 +475,14 @@ func (f *File) evalInfixExp(sheet string, tokens []efp.Token) (efp.Token, error) if token.TType == efp.OperatorsInfix && token.TSubType == efp.TokenSubTypeLogical { } if token.TType == efp.TokenTypeOperand && token.TSubType == efp.TokenSubTypeLogical { - argsStack.Peek().(*list.List).PushBack(formulaArg{ - String: token.TValue, - Type: ArgString, - }) + argsStack.Peek().(*list.List).PushBack(newStringFormulaArg(token.TValue)) } // current token is text if token.TType == efp.TokenTypeOperand && token.TSubType == efp.TokenSubTypeText { - argsStack.Peek().(*list.List).PushBack(formulaArg{ - String: token.TValue, - Type: ArgString, - }) + argsStack.Peek().(*list.List).PushBack(newStringFormulaArg(token.TValue)) } - if err = evalInfixExpFunc(token, nextToken, opfStack, opdStack, opftStack, opfdStack, argsStack); err != nil { + if err = f.evalInfixExpFunc(sheet, token, nextToken, opfStack, opdStack, opftStack, opfdStack, argsStack); err != nil { return efp.Token{}, err } } @@ -494,7 +501,7 @@ func (f *File) evalInfixExp(sheet string, tokens []efp.Token) (efp.Token, error) } // evalInfixExpFunc evaluate formula function in the infix expression. -func evalInfixExpFunc(token, nextToken efp.Token, opfStack, opdStack, opftStack, opfdStack, argsStack *Stack) error { +func (f *File) evalInfixExpFunc(sheet string, token, nextToken efp.Token, opfStack, opdStack, opftStack, opfdStack, argsStack *Stack) error { if !isFunctionStopToken(token) { return nil } @@ -510,16 +517,13 @@ func evalInfixExpFunc(token, nextToken efp.Token, opfStack, opdStack, opftStack, // push opfd to args if opfdStack.Len() > 0 { - argsStack.Peek().(*list.List).PushBack(formulaArg{ - String: opfdStack.Pop().(efp.Token).TValue, - Type: ArgString, - }) + argsStack.Peek().(*list.List).PushBack(newStringFormulaArg(opfdStack.Pop().(efp.Token).TValue)) } // call formula function to evaluate - arg := callFuncByName(&formulaFuncs{}, strings.NewReplacer( + arg := callFuncByName(&formulaFuncs{f: f, sheet: sheet}, strings.NewReplacer( "_xlfn", "", ".", "").Replace(opfStack.Peek().(efp.Token).TValue), []reflect.Value{reflect.ValueOf(argsStack.Peek().(*list.List))}) - if arg.Type == ArgError { + if arg.Type == ArgError && opfStack.Len() == 1 { return errors.New(arg.Value()) } argsStack.Pop() @@ -793,7 +797,7 @@ func isEndParenthesesToken(token efp.Token) bool { // token. func isOperatorPrefixToken(token efp.Token) bool { _, ok := tokenPriority[token.TValue] - if (token.TValue == "-" && token.TType == efp.TokenTypeOperatorPrefix) || ok { + if (token.TValue == "-" && token.TType == efp.TokenTypeOperatorPrefix) || (ok && token.TType == efp.TokenTypeOperatorInfix) { return true } return false @@ -3274,6 +3278,22 @@ func (fn *formulaFuncs) ISODD(argsList *list.List) formulaArg { return newStringFormulaArg(result) } +// ISTEXT function tests if a supplied value is text, and if so, returns TRUE; +// Otherwise, the function returns FALSE. The syntax of the function is: +// +// ISTEXT(value) +// +func (fn *formulaFuncs) ISTEXT(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "ISTEXT requires 1 argument") + } + token := argsList.Front().Value.(formulaArg) + if token.ToNumber().Type != ArgError { + return newBoolFormulaArg(false) + } + return newBoolFormulaArg(token.Type == ArgString) +} + // NA function returns the Excel #N/A error. This error message has the // meaning 'value not available' and is produced when an Excel Formula is // unable to find a value that it needs. The syntax of the function is: @@ -3287,6 +3307,18 @@ func (fn *formulaFuncs) NA(argsList *list.List) formulaArg { return newStringFormulaArg(formulaErrorNA) } +// SHEET function returns the Sheet number for a specified reference. The +// syntax of the function is: +// +// SHEET() +// +func (fn *formulaFuncs) SHEET(argsList *list.List) formulaArg { + if argsList.Len() != 0 { + return newErrorFormulaArg(formulaErrorVALUE, "SHEET accepts no arguments") + } + return newNumberFormulaArg(float64(fn.f.GetSheetIndex(fn.sheet) + 1)) +} + // Logical Functions // AND function tests a number of supplied conditions and returns TRUE or @@ -3330,6 +3362,64 @@ func (fn *formulaFuncs) AND(argsList *list.List) formulaArg { return newBoolFormulaArg(and) } +// FALSE function function returns the logical value FALSE. The syntax of the +// function is: +// +// FALSE() +// +func (fn *formulaFuncs) FALSE(argsList *list.List) formulaArg { + if argsList.Len() != 0 { + return newErrorFormulaArg(formulaErrorVALUE, "FALSE takes no arguments") + } + return newBoolFormulaArg(false) +} + +// IFERROR function receives two values (or expressions) and tests if the +// first of these evaluates to an error. The syntax of the function is: +// +// IFERROR(value,value_if_error) +// +func (fn *formulaFuncs) IFERROR(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "IFERROR requires 2 arguments") + } + value := argsList.Front().Value.(formulaArg) + if value.Type != ArgError { + if value.Type == ArgEmpty { + return newNumberFormulaArg(0) + } + return value + } + return argsList.Back().Value.(formulaArg) +} + +// NOT function returns the opposite to a supplied logical value. The syntax +// of the function is: +// +// NOT(logical) +// +func (fn *formulaFuncs) NOT(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "NOT requires 1 argument") + } + token := argsList.Front().Value.(formulaArg) + switch token.Type { + case ArgString, ArgList: + if strings.ToUpper(token.String) == "TRUE" { + return newBoolFormulaArg(false) + } + if strings.ToUpper(token.String) == "FALSE" { + return newBoolFormulaArg(true) + } + case ArgNumber: + return newBoolFormulaArg(!(token.Number != 0)) + case ArgError: + + return token + } + return newErrorFormulaArg(formulaErrorVALUE, "NOT expects 1 boolean or numeric argument") +} + // OR function tests a number of supplied conditions and returns either TRUE // or FALSE. The syntax of the function is: // @@ -3372,6 +3462,18 @@ func (fn *formulaFuncs) OR(argsList *list.List) formulaArg { return newStringFormulaArg(strings.ToUpper(strconv.FormatBool(or))) } +// TRUE function returns the logical value TRUE. The syntax of the function +// is: +// +// TRUE() +// +func (fn *formulaFuncs) TRUE(argsList *list.List) formulaArg { + if argsList.Len() != 0 { + return newErrorFormulaArg(formulaErrorVALUE, "TRUE takes no arguments") + } + return newBoolFormulaArg(true) +} + // Date and Time Functions // DATE returns a date, from a user-supplied year, month and day. The syntax @@ -3434,6 +3536,21 @@ func (fn *formulaFuncs) CLEAN(argsList *list.List) formulaArg { return newStringFormulaArg(b.String()) } +// EXACT function tests if two supplied text strings or values are exactly +// equal and if so, returns TRUE; Otherwise, the function returns FALSE. The +// function is case-sensitive. The syntax of the function is: +// +// EXACT(text1,text2) +// +func (fn *formulaFuncs) EXACT(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "EXACT requires 2 arguments") + } + text1 := argsList.Front().Value.(formulaArg).Value() + text2 := argsList.Back().Value.(formulaArg).Value() + return newBoolFormulaArg(text1 == text2) +} + // LEN returns the length of a supplied text string. The syntax of the // function is: // @@ -3446,6 +3563,22 @@ func (fn *formulaFuncs) LEN(argsList *list.List) formulaArg { return newStringFormulaArg(strconv.Itoa(len(argsList.Front().Value.(formulaArg).String))) } +// LENB returns the number of bytes used to represent the characters in a text +// string. LENB counts 2 bytes per character only when a DBCS language is set +// as the default language. Otherwise LENB behaves the same as LEN, counting +// 1 byte per character. The syntax of the function is: +// +// LENB(text) +// +// TODO: the languages that support DBCS include Japanese, Chinese +// (Simplified), Chinese (Traditional), and Korean. +func (fn *formulaFuncs) LENB(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "LENB requires 1 string argument") + } + return newStringFormulaArg(strconv.Itoa(len(argsList.Front().Value.(formulaArg).String))) +} + // TRIM removes extra spaces (i.e. all spaces except for single spaces between // words or characters) from a supplied text string. The syntax of the // function is: @@ -3495,6 +3628,36 @@ func (fn *formulaFuncs) PROPER(argsList *list.List) formulaArg { return newStringFormulaArg(buf.String()) } +// REPT function returns a supplied text string, repeated a specified number +// of times. The syntax of the function is: +// +// REPT(text,number_times) +// +func (fn *formulaFuncs) REPT(argsList *list.List) formulaArg { + if argsList.Len() != 2 { + return newErrorFormulaArg(formulaErrorVALUE, "REPT requires 2 arguments") + } + text := argsList.Front().Value.(formulaArg) + if text.Type != ArgString { + return newErrorFormulaArg(formulaErrorVALUE, "REPT requires first argument to be a string") + } + times := argsList.Back().Value.(formulaArg).ToNumber() + if times.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, "REPT requires second argument to be a number") + } + if times.Number < 0 { + return newErrorFormulaArg(formulaErrorVALUE, "REPT requires second argument to be >= 0") + } + if times.Number == 0 { + return newStringFormulaArg("") + } + buf := bytes.Buffer{} + for i := 0; i < int(times.Number); i++ { + buf.WriteString(text.String) + } + return newStringFormulaArg(buf.String()) +} + // UPPER converts all characters in a supplied text string to upper case. The // syntax of the function is: // @@ -3976,3 +4139,16 @@ func lookupCol(arr formulaArg) []formulaArg { } return col } + +// Web Functions + +// ENCODEURL function returns a URL-encoded string, replacing certain non-alphanumeric characters with the percentage symbol (%) and a hexadecimal number. The syntax of the function is: +// +// ENCODEURL(url) +// +func (fn *formulaFuncs) ENCODEURL(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "ENCODEURL requires 1 argument") + } + return newStringFormulaArg(strings.Replace(url.QueryEscape(argsList.Front().Value.(formulaArg).Value()), "+", "%20", -1)) +} |