mirror of
https://github.com/unidoc/unioffice.git
synced 2025-04-25 13:48:53 +08:00
formula: add more math formulas
- ABS - ACOS - ACOSH - ARABIC - ASIN - ASINH - ATAN - ATANH - ATAN2 - BASE - CEILING.MATH - CEILING.PRECISE - COMBIN - COMBINA - COS - COSH - DECIMAL - DEGREES - PI
This commit is contained in:
parent
6778397c6b
commit
382c157c11
@ -4,9 +4,8 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"baliance.com/gooxml/spreadsheet/formula"
|
||||
|
||||
"baliance.com/gooxml/spreadsheet"
|
||||
"baliance.com/gooxml/spreadsheet/formula"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
23
spreadsheet/formula/emptyexpr.go
Normal file
23
spreadsheet/formula/emptyexpr.go
Normal file
@ -0,0 +1,23 @@
|
||||
// Copyright 2017 Baliance. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by the terms of the Affero GNU General
|
||||
// Public License version 3.0 as published by the Free Software Foundation and
|
||||
// appearing in the file LICENSE included in the packaging of this file. A
|
||||
// commercial license can be purchased by contacting sales@baliance.com.
|
||||
|
||||
package formula
|
||||
|
||||
type EmptyExpr struct {
|
||||
}
|
||||
|
||||
func NewEmptyExpr() Expression {
|
||||
return EmptyExpr{}
|
||||
}
|
||||
|
||||
func (e EmptyExpr) Eval(ctx Context, ev Evaluator) Result {
|
||||
return MakeEmptyResult()
|
||||
}
|
||||
|
||||
func (e EmptyExpr) Reference(ctx Context, ev Evaluator) Reference {
|
||||
return ReferenceInvalid
|
||||
}
|
404
spreadsheet/formula/fnmathtrig.go
Normal file
404
spreadsheet/formula/fnmathtrig.go
Normal file
@ -0,0 +1,404 @@
|
||||
// Copyright 2017 Baliance. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by the terms of the Affero GNU General
|
||||
// Public License version 3.0 as published by the Free Software Foundation and
|
||||
// appearing in the file LICENSE included in the packaging of this file. A
|
||||
// commercial license can be purchased by contacting sales@baliance.com.
|
||||
|
||||
package formula
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterFunction("ABS", makeMathWrapper("ASIN", math.Abs))
|
||||
RegisterFunction("ACOS", makeMathWrapper("ASIN", math.Acos))
|
||||
RegisterFunction("ACOSH", makeMathWrapper("ASIN", math.Acosh))
|
||||
// TODO: RegisterFunction("ACOT", Acot) /// Excel 2013+
|
||||
// TODO: RegisterFunction("ACOTH", Acoth) /// Excel 2013+
|
||||
// TODO: RegisterFunction("_xlfn.AGGREGATE", Aggregate) // lots of dependencies
|
||||
RegisterFunction("_xlfn.ARABIC", Arabic)
|
||||
RegisterFunction("ASIN", makeMathWrapper("ASIN", math.Asin))
|
||||
RegisterFunction("ASINH", makeMathWrapper("ASINH", math.Asinh))
|
||||
RegisterFunction("ATAN", makeMathWrapper("ATAN", math.Atan))
|
||||
RegisterFunction("ATANH", makeMathWrapper("ATANH", math.Atanh))
|
||||
RegisterFunction("ATAN2", Atan2)
|
||||
RegisterFunction("_xlfn.BASE", Base)
|
||||
// RegisterFunction("CEILING", ) // TODO: figure out how this acts, Libre doesn't use it
|
||||
RegisterFunction("_xlfn.CEILING.MATH", CeilingMath)
|
||||
RegisterFunction("_xlfn.CEILING.PRECISE", CeilingPrecise)
|
||||
RegisterFunction("COMBIN", Combin)
|
||||
RegisterFunction("_xlfn.COMBINA", Combina)
|
||||
RegisterFunction("COS", makeMathWrapper("COS", math.Cos))
|
||||
RegisterFunction("COSH", makeMathWrapper("COSH", math.Cosh))
|
||||
//RegisterFunction("COT",
|
||||
//RegisterFunction("COTH"
|
||||
//RegisterFunction("CSC"
|
||||
//RegisterFunction("CSCH"
|
||||
RegisterFunction("_xlfn.DECIMAL", Decimal)
|
||||
RegisterFunction("DEGREES", Degrees)
|
||||
RegisterFunction("PI", Pi)
|
||||
}
|
||||
|
||||
// makeMathWrapper is used to wrap single argument math functions from the Go
|
||||
// standard library and present them as a spreadsheet function.
|
||||
func makeMathWrapper(name string, fn func(x float64) float64) Function {
|
||||
return func(args []Result) Result {
|
||||
if len(args) != 1 {
|
||||
return MakeErrorResult(name + " requires one argument")
|
||||
}
|
||||
|
||||
arg := args[0].AsNumber()
|
||||
switch arg.Type {
|
||||
case ResultTypeNumber:
|
||||
v := fn(arg.ValueNumber)
|
||||
if v != v {
|
||||
return MakeErrorResult(name + " returned NaN")
|
||||
}
|
||||
return MakeNumberResult(v)
|
||||
case ResultTypeList, ResultTypeString:
|
||||
return MakeErrorResult(name + " requires a numeric argument")
|
||||
case ResultTypeError:
|
||||
return arg
|
||||
default:
|
||||
return MakeErrorResult(fmt.Sprintf("unhandled %s() argument type %s", name, arg.Type))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Atan2 implements the Excel ATAN2 function. It accepts two numeric arguments,
|
||||
// and the arguments are (x,y), reversed from normal to match Excel's behaviour.
|
||||
func Atan2(args []Result) Result {
|
||||
if len(args) != 2 {
|
||||
return MakeErrorResult("ATAN2 requires two arguments")
|
||||
}
|
||||
|
||||
arg1 := args[0].AsNumber()
|
||||
arg2 := args[1].AsNumber()
|
||||
if arg1.Type == ResultTypeNumber && arg2.Type == ResultTypeNumber {
|
||||
// args are swapped here
|
||||
v := math.Atan2(arg2.ValueNumber, arg1.ValueNumber)
|
||||
if v != v {
|
||||
return MakeErrorResult("ATAN2 returned NaN")
|
||||
}
|
||||
return MakeNumberResult(v)
|
||||
}
|
||||
|
||||
for _, t := range []ResultType{arg1.Type, arg2.Type} {
|
||||
switch t {
|
||||
case ResultTypeList, ResultTypeString:
|
||||
return MakeErrorResult("ATAN2 requires a numeric argument")
|
||||
case ResultTypeError:
|
||||
return arg1
|
||||
default:
|
||||
return MakeErrorResult(fmt.Sprintf("unhandled ATAN2() argument type %s", t))
|
||||
}
|
||||
}
|
||||
return MakeErrorResult("unhandled error for ATAN2()")
|
||||
}
|
||||
|
||||
// Arabic implements the Excel ARABIC function which parses roman numerals. It
|
||||
// accepts one numeric argument.
|
||||
func Arabic(args []Result) Result {
|
||||
if len(args) != 1 {
|
||||
return MakeErrorResult("ARABIC requires one argument")
|
||||
}
|
||||
arg := args[0]
|
||||
switch arg.Type {
|
||||
case ResultTypeNumber, ResultTypeList, ResultTypeEmpty:
|
||||
return MakeErrorResult("ARABIC requires a string argument argument")
|
||||
case ResultTypeString:
|
||||
res := 0.0
|
||||
last := 0.0
|
||||
for _, c := range arg.ValueString {
|
||||
digit := 0.0
|
||||
switch c {
|
||||
case 'I':
|
||||
digit = 1
|
||||
case 'V':
|
||||
digit = 5
|
||||
case 'X':
|
||||
digit = 10
|
||||
case 'L':
|
||||
digit = 50
|
||||
case 'C':
|
||||
digit = 100
|
||||
case 'D':
|
||||
digit = 500
|
||||
case 'M':
|
||||
digit = 1000
|
||||
}
|
||||
res += digit
|
||||
switch {
|
||||
// repeated digits
|
||||
case last == digit &&
|
||||
(last == 5 || last == 50 || last == 500):
|
||||
return MakeErrorResult("invalid ARABIC format")
|
||||
// simpler form
|
||||
case 2*last == digit:
|
||||
return MakeErrorResult("invalid ARABIC format")
|
||||
}
|
||||
if last < digit {
|
||||
res -= 2 * last
|
||||
}
|
||||
last = digit
|
||||
}
|
||||
|
||||
return MakeNumberResult(res)
|
||||
case ResultTypeError:
|
||||
return arg
|
||||
default:
|
||||
return MakeErrorResult(fmt.Sprintf("unhandled ACOSH() argument type %s", arg.Type))
|
||||
}
|
||||
}
|
||||
|
||||
// CeilingMath implements _xlfn.CEILING.MATH which rounds numbers to the nearest
|
||||
// multiple of the second argument, toward or away from zero as specified by the
|
||||
// third argument.
|
||||
func CeilingMath(args []Result) Result {
|
||||
if len(args) == 0 {
|
||||
return MakeErrorResult("CEILING.MATH() requires at least one argument")
|
||||
}
|
||||
if len(args) > 3 {
|
||||
return MakeErrorResult("CEILING.MATH() allows at most three arguments")
|
||||
}
|
||||
// number to round
|
||||
number := args[0].AsNumber()
|
||||
if number.Type != ResultTypeNumber {
|
||||
return MakeErrorResult("first arugment to CEILING.MATH() must be a number")
|
||||
}
|
||||
|
||||
// significance
|
||||
significance := float64(1)
|
||||
if number.ValueNumber < 0 {
|
||||
significance = -1
|
||||
}
|
||||
if len(args) > 1 {
|
||||
sigArg := args[1].AsNumber()
|
||||
if sigArg.Type != ResultTypeNumber {
|
||||
return MakeErrorResult("second arugment to CEILING.MATH() must be a number")
|
||||
}
|
||||
significance = sigArg.ValueNumber
|
||||
}
|
||||
|
||||
// round direction
|
||||
direction := float64(1)
|
||||
if len(args) > 2 {
|
||||
dirArg := args[2].AsNumber()
|
||||
if dirArg.Type != ResultTypeNumber {
|
||||
return MakeErrorResult("third arugment to CEILING.MATH() must be a number")
|
||||
}
|
||||
direction = dirArg.ValueNumber
|
||||
}
|
||||
|
||||
if len(args) == 1 {
|
||||
return MakeNumberResult(math.Ceil(number.ValueNumber))
|
||||
}
|
||||
|
||||
v := number.ValueNumber
|
||||
v, res := math.Modf(v / significance)
|
||||
if res != 0 {
|
||||
if number.ValueNumber > 0 {
|
||||
v++
|
||||
} else if direction < 0 {
|
||||
v--
|
||||
}
|
||||
}
|
||||
return MakeNumberResult(v * significance)
|
||||
}
|
||||
|
||||
func CeilingPrecise(args []Result) Result {
|
||||
if len(args) == 0 {
|
||||
return MakeErrorResult("CEILING.PRECISE() requires at least one argument")
|
||||
}
|
||||
if len(args) > 2 {
|
||||
return MakeErrorResult("CEILING.PRECISE() allows at most two arguments")
|
||||
}
|
||||
// number to round
|
||||
number := args[0].AsNumber()
|
||||
if number.Type != ResultTypeNumber {
|
||||
return MakeErrorResult("first arugment to CEILING.PRECISE() must be a number")
|
||||
}
|
||||
|
||||
// significance
|
||||
significance := float64(1)
|
||||
if number.ValueNumber < 0 {
|
||||
significance = -1
|
||||
}
|
||||
if len(args) > 1 {
|
||||
sigArg := args[1].AsNumber()
|
||||
if sigArg.Type != ResultTypeNumber {
|
||||
return MakeErrorResult("second arugment to CEILING.MATH() must be a number")
|
||||
}
|
||||
// don't care about sign of significance
|
||||
significance = math.Abs(sigArg.ValueNumber)
|
||||
}
|
||||
|
||||
if len(args) == 1 {
|
||||
return MakeNumberResult(math.Ceil(number.ValueNumber))
|
||||
}
|
||||
|
||||
v := number.ValueNumber
|
||||
v, res := math.Modf(v / significance)
|
||||
if res != 0 {
|
||||
if number.ValueNumber > 0 {
|
||||
v++
|
||||
}
|
||||
}
|
||||
return MakeNumberResult(v * significance)
|
||||
}
|
||||
|
||||
// Base is an implementation of the Excel BASE function that returns a string
|
||||
// form of an integer in a specified base and of a minimum length with padded
|
||||
// zeros.
|
||||
func Base(args []Result) Result {
|
||||
if len(args) < 2 {
|
||||
return MakeErrorResult("BASE() requires at least two arguments")
|
||||
}
|
||||
if len(args) > 3 {
|
||||
return MakeErrorResult("BASE() allows at most three arguments")
|
||||
}
|
||||
// number to convert
|
||||
number := args[0].AsNumber()
|
||||
if number.Type != ResultTypeNumber {
|
||||
return MakeErrorResult("first arugment to BASE() must be a number")
|
||||
}
|
||||
|
||||
radixArg := args[1].AsNumber()
|
||||
if radixArg.Type != ResultTypeNumber {
|
||||
return MakeErrorResult("second arugment to BASE() must be a number")
|
||||
}
|
||||
radix := int(radixArg.ValueNumber)
|
||||
if radix < 0 || radix > 36 {
|
||||
return MakeErrorResult("radix must be in the range [0,36]")
|
||||
}
|
||||
|
||||
// min length of result
|
||||
minLength := 0
|
||||
if len(args) > 2 {
|
||||
lenArg := args[2].AsNumber()
|
||||
if lenArg.Type != ResultTypeNumber {
|
||||
return MakeErrorResult("third arugment to BASE() must be a number")
|
||||
}
|
||||
minLength = int(lenArg.ValueNumber)
|
||||
}
|
||||
|
||||
s := strconv.FormatInt(int64(number.ValueNumber), radix)
|
||||
if len(s) < minLength {
|
||||
s = strings.Repeat("0", minLength-len(s)) + s
|
||||
}
|
||||
return MakeStringResult(s)
|
||||
}
|
||||
|
||||
// Combin is an implementation of the Excel COMBINA function whic returns the
|
||||
// number of combinations.
|
||||
func Combin(args []Result) Result {
|
||||
if len(args) != 2 {
|
||||
return MakeErrorResult("COMBIN() requires two argument")
|
||||
}
|
||||
nArg := args[0].AsNumber()
|
||||
kArg := args[1].AsNumber()
|
||||
if nArg.Type != ResultTypeNumber || kArg.Type != ResultTypeNumber {
|
||||
return MakeErrorResult("COMBIN() requires numeric arguments")
|
||||
}
|
||||
n := math.Trunc(nArg.ValueNumber)
|
||||
k := math.Trunc(kArg.ValueNumber)
|
||||
if k > n {
|
||||
return MakeErrorResult("COMBIN() requires k <= n")
|
||||
}
|
||||
if k == n || k == 0 {
|
||||
return MakeNumberResult(1)
|
||||
}
|
||||
|
||||
res := float64(1)
|
||||
for i := float64(1); i <= k; i++ {
|
||||
res *= (n + 1 - i) / i
|
||||
}
|
||||
|
||||
return MakeNumberResult(res)
|
||||
}
|
||||
|
||||
// Combina is an implementation of the Excel COMBINA function whic returns the
|
||||
// number of combinations with repetitions.
|
||||
func Combina(args []Result) Result {
|
||||
if len(args) != 2 {
|
||||
return MakeErrorResult("COMBINA() requires two argument")
|
||||
}
|
||||
nArg := args[0].AsNumber()
|
||||
kArg := args[1].AsNumber()
|
||||
if nArg.Type != ResultTypeNumber || kArg.Type != ResultTypeNumber {
|
||||
return MakeErrorResult("COMBINA() requires numeric arguments")
|
||||
}
|
||||
n := math.Trunc(nArg.ValueNumber)
|
||||
k := math.Trunc(kArg.ValueNumber)
|
||||
if n < k {
|
||||
return MakeErrorResult("COMBINA() requires n > k")
|
||||
}
|
||||
if n == 0 {
|
||||
return MakeNumberResult(0)
|
||||
}
|
||||
args[0] = MakeNumberResult(n + k - 1)
|
||||
args[1] = MakeNumberResult(n - 1)
|
||||
return Combin(args)
|
||||
}
|
||||
|
||||
func fact(f float64) float64 {
|
||||
res := float64(1)
|
||||
for i := float64(2); i <= f; i++ {
|
||||
res *= i
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// Decimal is an implementation of the Excel function DECIMAL() that parses a string
|
||||
// in a given base and returns the numeric result.
|
||||
func Decimal(args []Result) Result {
|
||||
if len(args) != 2 {
|
||||
return MakeErrorResult("DECIMAL() requires two arguments")
|
||||
}
|
||||
sArg := args[0].AsString()
|
||||
if sArg.Type != ResultTypeString {
|
||||
return MakeErrorResult("DECIMAL() requires string first argument")
|
||||
}
|
||||
baseArg := args[1].AsNumber()
|
||||
if baseArg.Type != ResultTypeNumber {
|
||||
return MakeErrorResult("DECIMAL() requires number second argument")
|
||||
}
|
||||
|
||||
sv := sArg.ValueString
|
||||
if len(sv) > 2 && (strings.HasPrefix(sv, "0x") || strings.HasPrefix(sv, "0X")) {
|
||||
sv = sv[2:]
|
||||
}
|
||||
i, err := strconv.ParseInt(sv, int(baseArg.ValueNumber), 64)
|
||||
if err != nil {
|
||||
return MakeErrorResult("DECIMAL() error in conversion")
|
||||
}
|
||||
return MakeNumberResult(float64(i))
|
||||
}
|
||||
|
||||
// Degrees is an implementation of the Excel function DEGREES() that converts
|
||||
// radians to degrees.
|
||||
func Degrees(args []Result) Result {
|
||||
if len(args) != 1 {
|
||||
return MakeErrorResult("DEGREES() requires one argument")
|
||||
}
|
||||
vArg := args[0].AsNumber()
|
||||
if vArg.Type != ResultTypeNumber {
|
||||
return MakeErrorResult("DEGREES() requires number argument")
|
||||
}
|
||||
|
||||
return MakeNumberResult(180.0 / math.Pi * vArg.ValueNumber)
|
||||
}
|
||||
|
||||
// Pi is an implementation of the Excel Pi() function that just returns the Pi
|
||||
// constant.
|
||||
func Pi(args []Result) Result {
|
||||
if len(args) != 0 {
|
||||
return MakeErrorResult("PI() accepts no arguments")
|
||||
}
|
||||
return MakeNumberResult(math.Pi)
|
||||
}
|
@ -34,6 +34,8 @@ func Sum(args []Result) Result {
|
||||
// treated as zero by Excel
|
||||
case ResultTypeError:
|
||||
return a
|
||||
case ResultTypeEmpty:
|
||||
// skip
|
||||
default:
|
||||
return MakeErrorResult(fmt.Sprintf("unhandled SUM() argument type %s", a.Type))
|
||||
}
|
||||
@ -64,6 +66,8 @@ func Min(args []Result) Result {
|
||||
if 0 < res.ValueNumber {
|
||||
res.ValueNumber = 0
|
||||
}
|
||||
case ResultTypeEmpty:
|
||||
// skip
|
||||
case ResultTypeError:
|
||||
return a
|
||||
default:
|
||||
@ -91,6 +95,8 @@ func Max(args []Result) Result {
|
||||
if subMax.ValueNumber > res.ValueNumber {
|
||||
res.ValueNumber = subMax.ValueNumber
|
||||
}
|
||||
case ResultTypeEmpty:
|
||||
// skip
|
||||
case ResultTypeString:
|
||||
// treated as zero by Excel
|
||||
if 0 > res.ValueNumber {
|
||||
|
@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
echo "lexer"
|
||||
ragel -G2 -Z lexer.rl
|
||||
ragel -G0 -Z lexer.rl
|
||||
goimports -w lexer.go
|
||||
|
||||
echo "parser"
|
||||
|
@ -98,13 +98,13 @@ const yyLast = 145
|
||||
|
||||
var yyAct = [...]int{
|
||||
|
||||
42, 43, 3, 39, 59, 35, 34, 26, 36, 37,
|
||||
17, 24, 25, 26, 38, 26, 33, 60, 21, 41,
|
||||
19, 11, 33, 9, 44, 45, 46, 47, 48, 49,
|
||||
50, 51, 52, 53, 54, 55, 61, 56, 1, 22,
|
||||
23, 24, 25, 26, 31, 27, 28, 29, 30, 32,
|
||||
58, 18, 33, 10, 2, 8, 0, 0, 0, 0,
|
||||
0, 62, 57, 22, 23, 24, 25, 26, 31, 27,
|
||||
3, 24, 25, 26, 59, 34, 26, 36, 37, 17,
|
||||
26, 39, 33, 38, 35, 33, 21, 60, 41, 19,
|
||||
42, 43, 62, 44, 45, 46, 47, 48, 49, 50,
|
||||
51, 52, 53, 54, 55, 61, 56, 11, 22, 23,
|
||||
24, 25, 26, 31, 27, 28, 29, 30, 32, 58,
|
||||
9, 33, 1, 18, 10, 2, 8, 0, 0, 0,
|
||||
0, 63, 57, 22, 23, 24, 25, 26, 31, 27,
|
||||
28, 29, 30, 32, 0, 0, 33, 22, 23, 24,
|
||||
25, 26, 31, 27, 28, 29, 30, 32, 0, 0,
|
||||
33, 22, 23, 24, 25, 26, 0, 0, 0, 13,
|
||||
@ -116,42 +116,42 @@ var yyAct = [...]int{
|
||||
}
|
||||
var yyPact = [...]int{
|
||||
|
||||
92, -1000, -1000, 58, 124, -19, 124, 124, -1000, -1000,
|
||||
-1000, -1000, 124, -1000, -1000, -1000, -1000, -27, -1000, -1000,
|
||||
92, -1000, -1000, 58, 124, -10, 124, 124, -1000, -1000,
|
||||
-1000, -1000, 124, -1000, -1000, -1000, -1000, -19, -1000, -1000,
|
||||
110, -1000, 124, 124, 124, 124, 124, 124, 124, 124,
|
||||
124, 124, 124, 124, 58, 124, -10, -10, 44, 5,
|
||||
-1000, -14, -1000, 58, -10, -10, -16, -16, -1000, 72,
|
||||
72, 72, 72, 72, 72, -8, 20, -1000, -1000, -1000,
|
||||
124, -1000, -1000,
|
||||
124, 124, 124, 124, 58, 124, -20, -20, 44, 3,
|
||||
-1000, -14, -1000, 58, -20, -20, -17, -17, -1000, 72,
|
||||
72, 72, 72, 72, 72, -13, 19, -1000, -1000, -1000,
|
||||
124, -1000, -1000, 58,
|
||||
}
|
||||
var yyPgo = [...]int{
|
||||
|
||||
0, 1, 55, 54, 53, 10, 51, 38, 23, 21,
|
||||
0, 20, 19,
|
||||
0, 0, 56, 55, 54, 9, 53, 52, 50, 37,
|
||||
22, 20, 19, 18,
|
||||
}
|
||||
var yyR1 = [...]int{
|
||||
|
||||
0, 7, 3, 3, 3, 8, 8, 8, 8, 1,
|
||||
1, 1, 2, 2, 2, 2, 4, 4, 5, 6,
|
||||
11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
|
||||
11, 11, 9, 9, 9, 12, 12, 10,
|
||||
12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
|
||||
12, 12, 9, 9, 9, 13, 13, 11, 10, 10,
|
||||
}
|
||||
var yyR2 = [...]int{
|
||||
|
||||
0, 1, 1, 2, 4, 1, 1, 1, 1, 2,
|
||||
2, 1, 1, 1, 1, 3, 1, 1, 1, 3,
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
3, 3, 1, 2, 3, 1, 3, 1,
|
||||
3, 3, 1, 2, 3, 1, 3, 1, 1, 0,
|
||||
}
|
||||
var yyChk = [...]int{
|
||||
|
||||
-1000, -7, -3, -1, 24, 15, 19, 20, -2, -8,
|
||||
-4, -9, 17, 7, 8, 9, 10, -5, -6, -11,
|
||||
-4, -9, 17, 7, 8, 9, 10, -5, -6, -12,
|
||||
14, 13, 19, 20, 21, 22, 23, 25, 26, 27,
|
||||
28, 24, 29, 32, -1, 24, -1, -1, -1, 30,
|
||||
18, -12, -10, -1, -1, -1, -1, -1, -1, -1,
|
||||
18, -13, -11, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, 18, -5, 18,
|
||||
31, 16, -10,
|
||||
31, 16, -10, -1,
|
||||
}
|
||||
var yyDef = [...]int{
|
||||
|
||||
@ -161,7 +161,7 @@ var yyDef = [...]int{
|
||||
0, 0, 0, 0, 3, 0, 9, 10, 0, 0,
|
||||
33, 0, 35, 37, 20, 21, 22, 23, 24, 25,
|
||||
26, 27, 28, 29, 30, 31, 0, 15, 19, 34,
|
||||
0, 4, 36,
|
||||
39, 4, 36, 38,
|
||||
}
|
||||
var yyTok1 = [...]int{
|
||||
|
||||
@ -652,6 +652,11 @@ yydefault:
|
||||
{
|
||||
yyVAL.args = append(yyDollar[1].args, yyDollar[3].expr)
|
||||
}
|
||||
case 39:
|
||||
yyDollar = yyS[yypt-0 : yypt+1]
|
||||
{
|
||||
yyVAL.expr = NewEmptyExpr()
|
||||
}
|
||||
}
|
||||
goto yystack /* stack new state and value */
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ package formula
|
||||
}
|
||||
|
||||
%type <expr> formula formula1 initial reference referenceItem refFunctionCall
|
||||
%type <expr> start constant functionCall argument
|
||||
%type <expr> start constant functionCall argument argument1
|
||||
%type <expr> binOp
|
||||
%type <args> arguments
|
||||
|
||||
@ -96,10 +96,14 @@ functionCall:
|
||||
| tokenFunctionBultin arguments tokenRParen { $$ = NewFunction($1.val,$2)} ;
|
||||
|
||||
arguments:
|
||||
argument { $$ = append($$, $1) }
|
||||
argument1{ $$ = append($$, $1) }
|
||||
| arguments tokenComma argument { $$ = append($1,$3) }
|
||||
;
|
||||
|
||||
argument: formula;
|
||||
argument1: formula ;
|
||||
|
||||
argument:
|
||||
formula
|
||||
| /*empty*/ { $$ = NewEmptyExpr() } ;
|
||||
|
||||
%%
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -34,8 +34,8 @@ import (
|
||||
horizontalRange = '$'? [0-9]+ ':' '$'? [0-9]+;
|
||||
|
||||
# there is a function list at https://msdn.microsoft.com/en-us/library/dd906358(v=office.12).aspx
|
||||
builtinFunction = [A-Z]+ '(';
|
||||
excelFn = '_xlfn.' [A-Z_]+ '(';
|
||||
builtinFunction = [A-Z] [A-Z0-9]+ '(';
|
||||
excelFn = '_xlfn.' [A-Z_] [A-Z0-9.]+ '(';
|
||||
|
||||
sheetChar = ^['%\[\]\\:/?();{}#"=<>&+\-*/^%,_];
|
||||
enclosedSheetChar = ^['*\[\]\\:\/?];
|
||||
|
@ -20,7 +20,7 @@ type plex struct {
|
||||
}
|
||||
|
||||
func (f *plex) Lex(lval *yySymType) int {
|
||||
// yyDebug = 3
|
||||
//yyDebug = 3
|
||||
n := <-f.nodes
|
||||
if n != nil {
|
||||
lval.node = n
|
||||
@ -40,5 +40,8 @@ func Parse(r io.Reader) Expression {
|
||||
}
|
||||
|
||||
func ParseString(s string) Expression {
|
||||
if s == "" {
|
||||
return NewEmptyExpr()
|
||||
}
|
||||
return Parse(strings.NewReader(s))
|
||||
}
|
||||
|
@ -7,7 +7,10 @@
|
||||
|
||||
package formula
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// ResultType is the type of the result
|
||||
//go:generate stringer -type=ResultType
|
||||
@ -20,6 +23,7 @@ const (
|
||||
ResultTypeString
|
||||
ResultTypeList
|
||||
ResultTypeError
|
||||
ResultTypeEmpty
|
||||
)
|
||||
|
||||
// Result is the result of a formula or cell evaluation .
|
||||
@ -45,6 +49,29 @@ func (r Result) Value() string {
|
||||
}
|
||||
}
|
||||
|
||||
// AsNumber attempts to intepret a string cell value as a number. Upon success,
|
||||
// it returns a new number result, upon failure it returns the original result.
|
||||
// This is used as functions return strings that can then act like number (e.g.
|
||||
// LEFT(1.2345,3) + LEFT(1.2345,3) = 2.4)
|
||||
func (r Result) AsNumber() Result {
|
||||
if r.Type == ResultTypeString {
|
||||
f, err := strconv.ParseFloat(r.ValueString, 64)
|
||||
if err == nil {
|
||||
return MakeNumberResult(f)
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (r Result) AsString() Result {
|
||||
switch r.Type {
|
||||
case ResultTypeNumber:
|
||||
return MakeStringResult(r.Value())
|
||||
default:
|
||||
return r
|
||||
}
|
||||
}
|
||||
|
||||
// MakeNumberResult constructs a number result.
|
||||
func MakeNumberResult(v float64) Result {
|
||||
return Result{Type: ResultTypeNumber, ValueNumber: v}
|
||||
@ -106,3 +133,8 @@ func MakeErrorResultType(t ErrorType, msg string) Result {
|
||||
func MakeStringResult(s string) Result {
|
||||
return Result{Type: ResultTypeString, ValueString: s}
|
||||
}
|
||||
|
||||
// MakeEmptyResult is ued when parsing an empty argument.
|
||||
func MakeEmptyResult() Result {
|
||||
return Result{Type: ResultTypeEmpty}
|
||||
}
|
||||
|
@ -4,9 +4,9 @@ package formula
|
||||
|
||||
import "fmt"
|
||||
|
||||
const _ResultType_name = "ResultTypeUnknownResultTypeNumberResultTypeStringResultTypeListResultTypeError"
|
||||
const _ResultType_name = "ResultTypeUnknownResultTypeNumberResultTypeStringResultTypeListResultTypeErrorResultTypeEmpty"
|
||||
|
||||
var _ResultType_index = [...]uint8{0, 17, 33, 49, 63, 78}
|
||||
var _ResultType_index = [...]uint8{0, 17, 33, 49, 63, 78, 93}
|
||||
|
||||
func (i ResultType) String() string {
|
||||
if i >= ResultType(len(_ResultType_index)-1) {
|
||||
|
BIN
spreadsheet/formula/testdata/formulareference.xlsx
vendored
BIN
spreadsheet/formula/testdata/formulareference.xlsx
vendored
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user