Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
mrxrsd committed Aug 4, 2021
2 parents a08f2aa + 019fb76 commit a0b4d79
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 14 deletions.
1 change: 1 addition & 0 deletions astBuilder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ func getConstantRegistry() *constantRegistry {
func getFunctionRegistry() *functionRegistry {
return &functionRegistry{
caseSensitive: false,
functions: map[string]functionInfo{},
}
}

Expand Down
11 changes: 11 additions & 0 deletions benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,14 @@ func BenchmarkExpr(bench *testing.B) {
formula(parameters)
}
}

func BenchmarkComplexPrecendence(bench *testing.B) {

engine := createEngine()
formula, _ := engine.Build("1+2-3*4/5+6-7*8/9+0")

bench.ResetTimer()
for i := 0; i < bench.N; i++ {
formula(nil)
}
}
28 changes: 14 additions & 14 deletions operation.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func newAddOperation(dataType operationDataType, operationOne operation, operati
meta := operationMetadata{
DataType: dataType,
DependsOnVariables: operationOne.OperationMetadata().DependsOnVariables || operationTwo.OperationMetadata().DependsOnVariables,
IsIdempotent: operationOne.OperationMetadata().IsIdempotent && operationTwo.OperationMetadata().DependsOnVariables,
IsIdempotent: operationOne.OperationMetadata().IsIdempotent && operationTwo.OperationMetadata().IsIdempotent,
}

return &addOperation{
Expand All @@ -78,7 +78,7 @@ func newAndOperation(dataType operationDataType, operationOne operation, operati
meta := operationMetadata{
DataType: dataType,
DependsOnVariables: operationOne.OperationMetadata().DependsOnVariables || operationTwo.OperationMetadata().DependsOnVariables,
IsIdempotent: operationOne.OperationMetadata().IsIdempotent && operationTwo.OperationMetadata().DependsOnVariables,
IsIdempotent: operationOne.OperationMetadata().IsIdempotent && operationTwo.OperationMetadata().IsIdempotent,
}

return &andOperation{
Expand Down Expand Up @@ -123,7 +123,7 @@ func newDivisorOperation(dataType operationDataType, dividend operation, divisor
meta := operationMetadata{
DataType: dataType,
DependsOnVariables: dividend.OperationMetadata().DependsOnVariables || divisor.OperationMetadata().DependsOnVariables,
IsIdempotent: dividend.OperationMetadata().IsIdempotent && divisor.OperationMetadata().DependsOnVariables,
IsIdempotent: dividend.OperationMetadata().IsIdempotent && divisor.OperationMetadata().IsIdempotent,
}

return &divisorOperation{
Expand All @@ -147,7 +147,7 @@ func newEqualOperation(dataType operationDataType, operationOne operation, opera
meta := operationMetadata{
DataType: dataType,
DependsOnVariables: operationOne.OperationMetadata().DependsOnVariables || operationTwo.OperationMetadata().DependsOnVariables,
IsIdempotent: operationOne.OperationMetadata().IsIdempotent && operationTwo.OperationMetadata().DependsOnVariables,
IsIdempotent: operationOne.OperationMetadata().IsIdempotent && operationTwo.OperationMetadata().IsIdempotent,
}

return &equalOperation{
Expand All @@ -171,7 +171,7 @@ func newExponentiationOperation(dataType operationDataType, base operation, expo
meta := operationMetadata{
DataType: dataType,
DependsOnVariables: base.OperationMetadata().DependsOnVariables || exponent.OperationMetadata().DependsOnVariables,
IsIdempotent: base.OperationMetadata().IsIdempotent && exponent.OperationMetadata().DependsOnVariables,
IsIdempotent: base.OperationMetadata().IsIdempotent && exponent.OperationMetadata().IsIdempotent,
}

return &exponentiationOperation{
Expand Down Expand Up @@ -226,7 +226,7 @@ func newGreaterOrEqualThanOperation(dataType operationDataType, operationOne ope
meta := operationMetadata{
DataType: dataType,
DependsOnVariables: operationOne.OperationMetadata().DependsOnVariables || operationTwo.OperationMetadata().DependsOnVariables,
IsIdempotent: operationOne.OperationMetadata().IsIdempotent && operationTwo.OperationMetadata().DependsOnVariables,
IsIdempotent: operationOne.OperationMetadata().IsIdempotent && operationTwo.OperationMetadata().IsIdempotent,
}

return &greaterOrEqualThanOperation{
Expand All @@ -250,7 +250,7 @@ func newGreaterThanOperation(dataType operationDataType, operationOne operation,
meta := operationMetadata{
DataType: dataType,
DependsOnVariables: operationOne.OperationMetadata().DependsOnVariables || operationTwo.OperationMetadata().DependsOnVariables,
IsIdempotent: operationOne.OperationMetadata().IsIdempotent && operationTwo.OperationMetadata().DependsOnVariables,
IsIdempotent: operationOne.OperationMetadata().IsIdempotent && operationTwo.OperationMetadata().IsIdempotent,
}

return &greaterThanOperation{
Expand All @@ -274,7 +274,7 @@ func newLessOrEqualThanOperation(dataType operationDataType, operationOne operat
meta := operationMetadata{
DataType: dataType,
DependsOnVariables: operationOne.OperationMetadata().DependsOnVariables || operationTwo.OperationMetadata().DependsOnVariables,
IsIdempotent: operationOne.OperationMetadata().IsIdempotent && operationTwo.OperationMetadata().DependsOnVariables,
IsIdempotent: operationOne.OperationMetadata().IsIdempotent && operationTwo.OperationMetadata().IsIdempotent,
}

return &lessOrEqualThanOperation{
Expand All @@ -298,7 +298,7 @@ func newLessThanOperation(dataType operationDataType, operationOne operation, op
meta := operationMetadata{
DataType: dataType,
DependsOnVariables: operationOne.OperationMetadata().DependsOnVariables || operationTwo.OperationMetadata().DependsOnVariables,
IsIdempotent: operationOne.OperationMetadata().IsIdempotent && operationTwo.OperationMetadata().DependsOnVariables,
IsIdempotent: operationOne.OperationMetadata().IsIdempotent && operationTwo.OperationMetadata().IsIdempotent,
}

return &lessThanOperation{
Expand All @@ -322,7 +322,7 @@ func newModuloOperation(dataType operationDataType, dividend operation, divisor
meta := operationMetadata{
DataType: dataType,
DependsOnVariables: dividend.OperationMetadata().DependsOnVariables || divisor.OperationMetadata().DependsOnVariables,
IsIdempotent: dividend.OperationMetadata().IsIdempotent && divisor.OperationMetadata().DependsOnVariables,
IsIdempotent: dividend.OperationMetadata().IsIdempotent && divisor.OperationMetadata().IsIdempotent,
}

return &moduloOperation{
Expand All @@ -346,7 +346,7 @@ func newMultiplicationOperation(dataType operationDataType, operationOne operati
meta := operationMetadata{
DataType: dataType,
DependsOnVariables: operationOne.OperationMetadata().DependsOnVariables || operationTwo.OperationMetadata().DependsOnVariables,
IsIdempotent: operationOne.OperationMetadata().IsIdempotent && operationTwo.OperationMetadata().DependsOnVariables,
IsIdempotent: operationOne.OperationMetadata().IsIdempotent && operationTwo.OperationMetadata().IsIdempotent,
}

return &multiplicationOperation{
Expand All @@ -370,7 +370,7 @@ func newNotEqualOperation(dataType operationDataType, operationOne operation, op
meta := operationMetadata{
DataType: dataType,
DependsOnVariables: operationOne.OperationMetadata().DependsOnVariables || operationTwo.OperationMetadata().DependsOnVariables,
IsIdempotent: operationOne.OperationMetadata().IsIdempotent && operationTwo.OperationMetadata().DependsOnVariables,
IsIdempotent: operationOne.OperationMetadata().IsIdempotent && operationTwo.OperationMetadata().IsIdempotent,
}

return &notEqualOperation{
Expand All @@ -394,7 +394,7 @@ func newOrOperation(dataType operationDataType, operationOne operation, operatio
meta := operationMetadata{
DataType: dataType,
DependsOnVariables: operationOne.OperationMetadata().DependsOnVariables || operationTwo.OperationMetadata().DependsOnVariables,
IsIdempotent: operationOne.OperationMetadata().IsIdempotent && operationTwo.OperationMetadata().DependsOnVariables,
IsIdempotent: operationOne.OperationMetadata().IsIdempotent && operationTwo.OperationMetadata().IsIdempotent,
}

return &orOperation{
Expand All @@ -418,7 +418,7 @@ func newSubtractionOperation(dataType operationDataType, operationOne operation,
meta := operationMetadata{
DataType: dataType,
DependsOnVariables: operationOne.OperationMetadata().DependsOnVariables || operationTwo.OperationMetadata().DependsOnVariables,
IsIdempotent: operationOne.OperationMetadata().IsIdempotent && operationTwo.OperationMetadata().DependsOnVariables,
IsIdempotent: operationOne.OperationMetadata().IsIdempotent && operationTwo.OperationMetadata().IsIdempotent,
}

return &subtractionOperation{
Expand Down
24 changes: 24 additions & 0 deletions optimizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,30 @@ func optimize(executor interpreter, op operation, functionRegistry *functionRegi
cop.Base = optimize(executor, cop.Base, functionRegistry, constantRegistry)
cop.Exponent = optimize(executor, cop.Exponent, functionRegistry, constantRegistry)

} else if cop, ok := op.(*greaterThanOperation); ok {
cop.OperationOne = optimize(executor, cop.OperationOne, functionRegistry, constantRegistry)
cop.OperationTwo = optimize(executor, cop.OperationTwo, functionRegistry, constantRegistry)

} else if cop, ok := op.(*greaterOrEqualThanOperation); ok {
cop.OperationOne = optimize(executor, cop.OperationOne, functionRegistry, constantRegistry)
cop.OperationTwo = optimize(executor, cop.OperationTwo, functionRegistry, constantRegistry)

} else if cop, ok := op.(*andOperation); ok {
cop.OperationOne = optimize(executor, cop.OperationOne, functionRegistry, constantRegistry)
cop.OperationTwo = optimize(executor, cop.OperationTwo, functionRegistry, constantRegistry)

} else if cop, ok := op.(*orOperation); ok {
cop.OperationOne = optimize(executor, cop.OperationOne, functionRegistry, constantRegistry)
cop.OperationTwo = optimize(executor, cop.OperationTwo, functionRegistry, constantRegistry)

} else if cop, ok := op.(*lessThanOperation); ok {
cop.OperationOne = optimize(executor, cop.OperationOne, functionRegistry, constantRegistry)
cop.OperationTwo = optimize(executor, cop.OperationTwo, functionRegistry, constantRegistry)

} else if cop, ok := op.(*lessOrEqualThanOperation); ok {
cop.OperationOne = optimize(executor, cop.OperationOne, functionRegistry, constantRegistry)
cop.OperationTwo = optimize(executor, cop.OperationTwo, functionRegistry, constantRegistry)

} else if cop, ok := op.(*functionOperation); ok {
optimizedArguments := make([]operation, len(cop.Arguments))

Expand Down
114 changes: 114 additions & 0 deletions optimizer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,62 @@ import (
"testing"
)

func TestOptimizerIdempotentFunction(test *testing.T) {
interpreter := &interpreter{}
optimizer := &optimizer{executor: *interpreter}
reader := newTokenReader('.', ',')

fnRegistry := getFunctionRegistry()
fnRegistry.registerFunction("test", func(arguments ...float64) (float64, error) {
return arguments[0] + arguments[1], nil
}, false, true)

astBuilder := newAstBuilder(false, fnRegistry, getConstantRegistry(), nil)

tokens, _ := reader.read("test(var1, (2+3) * 500)")
operation, _ := astBuilder.build(tokens)
optimizedOperation := optimizer.optimize(operation, fnRegistry, getConstantRegistry())

if reflect.TypeOf(optimizedOperation).String() != "*gojacego.functionOperation" {
test.Errorf("expected: functionOperation, got: %s", reflect.TypeOf(optimizedOperation).String())
}

fnOperation := optimizedOperation.(*functionOperation)
fnArgument := fnOperation.Arguments[1]

if reflect.TypeOf(fnArgument).String() != "*gojacego.constantOperation" {
test.Errorf("Expected: *gojacego.constantOperation, got: %s", reflect.TypeOf(fnArgument).String())
}
}

func TestOptimizerNonIdempotentFunction(test *testing.T) {
interpreter := &interpreter{}
optimizer := &optimizer{executor: *interpreter}
reader := newTokenReader('.', ',')

fnRegistry := getFunctionRegistry()
fnRegistry.registerFunction("test", func(arguments ...float64) (float64, error) {
return arguments[0], nil
}, false, false)

astBuilder := newAstBuilder(false, fnRegistry, getConstantRegistry(), nil)

tokens, _ := reader.read("test(500)")
operation, _ := astBuilder.build(tokens)
optimizedOperation := optimizer.optimize(operation, fnRegistry, getConstantRegistry())

if reflect.TypeOf(optimizedOperation).String() != "*gojacego.functionOperation" {
test.Errorf("expected: functionOperation, got: %s", reflect.TypeOf(optimizedOperation).String())
}

fnOperation := optimizedOperation.(*functionOperation)
fnArgument := fnOperation.Arguments[0]

if reflect.TypeOf(fnArgument).String() != "*gojacego.constantOperation" {
test.Errorf("Expected: *gojacego.constantOperation, got: %s", reflect.TypeOf(fnArgument).String())
}
}

func TestOptimizerMultiplicationByZero(test *testing.T) {
interpreter := &interpreter{}
optimizer := &optimizer{executor: *interpreter}
Expand All @@ -23,3 +79,61 @@ func TestOptimizerMultiplicationByZero(test *testing.T) {
test.Errorf("Expected: 0.0, got: %f", optimizedOperation.(*constantOperation).Value)
}
}

func TestBasicOptimizer(test *testing.T) {
interpreter := &interpreter{}
optimizer := &optimizer{executor: *interpreter}
reader := newTokenReader('.', ',')
astBuilder := newAstBuilder(false, getFunctionRegistry(), getConstantRegistry(), nil)

tokens, _ := reader.read("2 + 2")
operation, _ := astBuilder.build(tokens)
optimizedOperation := optimizer.optimize(operation, getFunctionRegistry(), getConstantRegistry())

if reflect.TypeOf(optimizedOperation).String() != "*gojacego.constantOperation" {
test.Errorf("expected: ConstantOperation, got: %s", reflect.TypeOf(optimizedOperation).String())
}

if optimizedOperation.(*constantOperation).Value != 4.0 {
test.Errorf("Expected: 4.0, got: %f", optimizedOperation.(*constantOperation).Value)
}
}

func TestBooleanOperationOptimizer(test *testing.T) {
interpreter := &interpreter{}
optimizer := &optimizer{executor: *interpreter}
reader := newTokenReader('.', ',')
astBuilder := newAstBuilder(false, getFunctionRegistry(), getConstantRegistry(), nil)

tokens, _ := reader.read("4 > 2")
operation, _ := astBuilder.build(tokens)
optimizedOperation := optimizer.optimize(operation, getFunctionRegistry(), getConstantRegistry())

if reflect.TypeOf(optimizedOperation).String() != "*gojacego.constantOperation" {
test.Errorf("expected: ConstantOperation, got: %s", reflect.TypeOf(optimizedOperation).String())
}

if optimizedOperation.(*constantOperation).Value != 1.0 {
test.Errorf("Expected: 1.0, got: %f", optimizedOperation.(*constantOperation).Value)
}
}

func TestBooleanOperation2Optimizer(test *testing.T) {
interpreter := &interpreter{}
optimizer := &optimizer{executor: *interpreter}
reader := newTokenReader('.', ',')
astBuilder := newAstBuilder(false, getFunctionRegistry(), getConstantRegistry(), nil)

tokens, _ := reader.read("(4 > 2) && var1")
operation, _ := astBuilder.build(tokens)
optimizedOperation := optimizer.optimize(operation, getFunctionRegistry(), getConstantRegistry())

if reflect.TypeOf(optimizedOperation).String() != "*gojacego.andOperation" {
test.Errorf("expected: andOperation, got: %s", reflect.TypeOf(optimizedOperation).String())
}
andOperation := optimizedOperation.(*andOperation)

if andOperation.OperationOne.(*constantOperation).Value != 1.0 {
test.Errorf("Expected: 1.0, got: %f", andOperation.OperationOne.(*constantOperation).Value)
}
}

0 comments on commit a0b4d79

Please sign in to comment.