From 6966fba61104f7c63a203de83ace5a4a556af576 Mon Sep 17 00:00:00 2001 From: mrxrsd Date: Tue, 3 Aug 2021 21:27:08 -0300 Subject: [PATCH 1/3] fix operation factory (optimizer now works) --- astBuilder_test.go | 1 + calculationEngine_test.go | 10 +++++++++ operation.go | 28 +++++++++++------------ optimizer_test.go | 47 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 72 insertions(+), 14 deletions(-) diff --git a/astBuilder_test.go b/astBuilder_test.go index b861688..4abb7b8 100644 --- a/astBuilder_test.go +++ b/astBuilder_test.go @@ -14,6 +14,7 @@ func getConstantRegistry() *constantRegistry { func getFunctionRegistry() *functionRegistry { return &functionRegistry{ caseSensitive: false, + functions: map[string]functionInfo{}, } } diff --git a/calculationEngine_test.go b/calculationEngine_test.go index 2eef299..4e7466e 100644 --- a/calculationEngine_test.go +++ b/calculationEngine_test.go @@ -12,6 +12,16 @@ type CalculationTestScenario struct { fnCallback func(float64) float64 } +func TestDebug(t *testing.T) { + engine := NewCalculationEngine() + // result, _ := engine.Calculate("1+2-3*4/5+6-7*8/9+0", nil) + result, _ := engine.Calculate("2+2", nil) + + if result != 1.0 { + + } + +} func TestCalculationDefaultEngine(t *testing.T) { engine := NewCalculationEngine() diff --git a/operation.go b/operation.go index b8eef4a..2659838 100644 --- a/operation.go +++ b/operation.go @@ -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{ @@ -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{ @@ -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{ @@ -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{ @@ -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{ @@ -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{ @@ -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{ @@ -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{ @@ -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{ @@ -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{ @@ -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{ @@ -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 ¬EqualOperation{ @@ -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{ @@ -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{ diff --git a/optimizer_test.go b/optimizer_test.go index 062618e..1a6c7ce 100644 --- a/optimizer_test.go +++ b/optimizer_test.go @@ -5,6 +5,34 @@ 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 TestOptimizerMultiplicationByZero(test *testing.T) { interpreter := &interpreter{} optimizer := &optimizer{executor: *interpreter} @@ -23,3 +51,22 @@ 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: 0.0, got: %f", optimizedOperation.(*constantOperation).Value) + // } +} From dbd4e87b1e7c5d7663b8d07d278b631c223073a7 Mon Sep 17 00:00:00 2001 From: mrxrsd Date: Tue, 3 Aug 2021 21:33:36 -0300 Subject: [PATCH 2/3] bechmark test --- benchmark_test.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/benchmark_test.go b/benchmark_test.go index ca40d30..2cf3251 100644 --- a/benchmark_test.go +++ b/benchmark_test.go @@ -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) + } +} From 019fb76722ab1f641ab418a99e58b4ab0be16610 Mon Sep 17 00:00:00 2001 From: mrxrsd Date: Tue, 3 Aug 2021 23:07:28 -0300 Subject: [PATCH 3/3] optimizer.go --- calculationEngine_test.go | 10 ------ optimizer.go | 24 +++++++++++++ optimizer_test.go | 73 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 94 insertions(+), 13 deletions(-) diff --git a/calculationEngine_test.go b/calculationEngine_test.go index 4e7466e..2eef299 100644 --- a/calculationEngine_test.go +++ b/calculationEngine_test.go @@ -12,16 +12,6 @@ type CalculationTestScenario struct { fnCallback func(float64) float64 } -func TestDebug(t *testing.T) { - engine := NewCalculationEngine() - // result, _ := engine.Calculate("1+2-3*4/5+6-7*8/9+0", nil) - result, _ := engine.Calculate("2+2", nil) - - if result != 1.0 { - - } - -} func TestCalculationDefaultEngine(t *testing.T) { engine := NewCalculationEngine() diff --git a/optimizer.go b/optimizer.go index 528a11d..610ba86 100644 --- a/optimizer.go +++ b/optimizer.go @@ -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)) diff --git a/optimizer_test.go b/optimizer_test.go index 1a6c7ce..0ab267d 100644 --- a/optimizer_test.go +++ b/optimizer_test.go @@ -33,6 +33,34 @@ func TestOptimizerIdempotentFunction(test *testing.T) { } } +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} @@ -66,7 +94,46 @@ func TestBasicOptimizer(test *testing.T) { test.Errorf("expected: ConstantOperation, got: %s", reflect.TypeOf(optimizedOperation).String()) } - // if optimizedOperation.(*constantOperation).Value != 4.0 { - // test.Errorf("Expected: 0.0, got: %f", optimizedOperation.(*constantOperation).Value) - // } + 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) + } }