mirror of
https://github.com/unidoc/unioffice.git
synced 2025-05-08 19:29:25 +08:00

We can take advantage of cached formula results that Excel/LibreOffice insert to ensure that our formula results match the expected results.
131 lines
3.4 KiB
Go
131 lines
3.4 KiB
Go
// 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 "math"
|
|
|
|
// BinOpType is the binary operation operator type
|
|
//go:generate stringer -type=BinOpType
|
|
type BinOpType byte
|
|
|
|
// Operator type constants
|
|
const (
|
|
BinOpTypeUnknown BinOpType = iota
|
|
BinOpTypePlus
|
|
BinOpTypeMinus
|
|
BinOpTypeMult
|
|
BinOpTypeDiv
|
|
BinOpTypeExp
|
|
BinOpTypeLT
|
|
BinOpTypeGT
|
|
BinOpTypeEQ
|
|
BinOpTypeLEQ
|
|
BinOpTypeGEQ
|
|
BinOpTypeNE
|
|
BinOpTypeConcat // '&' in Excel
|
|
)
|
|
|
|
// BinaryExpr is a binary expression.
|
|
type BinaryExpr struct {
|
|
lhs, rhs Expression
|
|
op BinOpType
|
|
}
|
|
|
|
// NewBinaryExpr constructs a new binary expression with a given operator.
|
|
func NewBinaryExpr(lhs Expression, op BinOpType, rhs Expression) Expression {
|
|
return BinaryExpr{lhs, rhs, op}
|
|
}
|
|
|
|
// Eval evaluates the binary expression using the context given.
|
|
func (b BinaryExpr) Eval(ctx Context, ev Evaluator) Result {
|
|
lhs := b.lhs.Eval(ctx, ev)
|
|
rhs := b.rhs.Eval(ctx, ev)
|
|
|
|
// TODO: check for and add support for binary operators on boolean values
|
|
switch b.op {
|
|
case BinOpTypePlus:
|
|
if lhs.Type == rhs.Type {
|
|
if lhs.Type == ResultTypeNumber {
|
|
return MakeNumberResult(lhs.ValueNumber + rhs.ValueNumber)
|
|
}
|
|
}
|
|
case BinOpTypeMinus:
|
|
if lhs.Type == rhs.Type {
|
|
if lhs.Type == ResultTypeNumber {
|
|
return MakeNumberResult(lhs.ValueNumber - rhs.ValueNumber)
|
|
}
|
|
}
|
|
case BinOpTypeMult:
|
|
if lhs.Type == rhs.Type {
|
|
if lhs.Type == ResultTypeNumber {
|
|
return MakeNumberResult(lhs.ValueNumber * rhs.ValueNumber)
|
|
}
|
|
}
|
|
case BinOpTypeDiv:
|
|
if lhs.Type == rhs.Type {
|
|
if lhs.Type == ResultTypeNumber {
|
|
if rhs.ValueNumber == 0 {
|
|
return MakeErrorResultType(ErrorTypeDivideByZero, "divide by zero")
|
|
}
|
|
return MakeNumberResult(lhs.ValueNumber / rhs.ValueNumber)
|
|
}
|
|
}
|
|
case BinOpTypeExp:
|
|
if lhs.Type == rhs.Type {
|
|
if lhs.Type == ResultTypeNumber {
|
|
return MakeNumberResult(math.Pow(lhs.ValueNumber, rhs.ValueNumber))
|
|
}
|
|
}
|
|
case BinOpTypeLT:
|
|
if lhs.Type == rhs.Type {
|
|
if lhs.Type == ResultTypeNumber {
|
|
return MakeBoolResult(lhs.ValueNumber < rhs.ValueNumber)
|
|
}
|
|
}
|
|
case BinOpTypeGT:
|
|
if lhs.Type == rhs.Type {
|
|
if lhs.Type == ResultTypeNumber {
|
|
return MakeBoolResult(lhs.ValueNumber > rhs.ValueNumber)
|
|
}
|
|
}
|
|
case BinOpTypeEQ:
|
|
if lhs.Type == rhs.Type {
|
|
if lhs.Type == ResultTypeNumber {
|
|
// TODO: see what Excel does regarding floating point comparison
|
|
return MakeBoolResult(lhs.ValueNumber == rhs.ValueNumber)
|
|
}
|
|
}
|
|
case BinOpTypeNE:
|
|
if lhs.Type == rhs.Type {
|
|
if lhs.Type == ResultTypeNumber {
|
|
return MakeBoolResult(lhs.ValueNumber != rhs.ValueNumber)
|
|
}
|
|
}
|
|
case BinOpTypeLEQ:
|
|
if lhs.Type == rhs.Type {
|
|
if lhs.Type == ResultTypeNumber {
|
|
return MakeBoolResult(lhs.ValueNumber <= rhs.ValueNumber)
|
|
}
|
|
}
|
|
case BinOpTypeGEQ:
|
|
if lhs.Type == rhs.Type {
|
|
if lhs.Type == ResultTypeNumber {
|
|
return MakeBoolResult(lhs.ValueNumber >= rhs.ValueNumber)
|
|
}
|
|
}
|
|
case BinOpTypeConcat:
|
|
return MakeStringResult(lhs.Value() + rhs.Value())
|
|
}
|
|
|
|
return MakeErrorResult("unsupported binary op")
|
|
}
|
|
|
|
func (b BinaryExpr) Reference(ctx Context, ev Evaluator) Reference {
|
|
return ReferenceInvalid
|
|
}
|