Skip to content

Commit

Permalink
Feat/serialize to fix #182 (#183)
Browse files Browse the repository at this point in the history
* added catalog to save into binnary, whats left is the reverse process

* Finished serializer, whats left is to save/load to file

* Get all test and function in library to read and write knowledgebase from reader and into writer

* Committed some license notice and a little bit more test

* Some additional node of the size of binary that inflated compared to the original GRL size
newm4n authored Apr 12, 2021
1 parent 90f4cf9 commit 8c7b275
Showing 22 changed files with 3,036 additions and 3 deletions.
21 changes: 21 additions & 0 deletions ast/ArgumentList.go
Original file line number Diff line number Diff line change
@@ -38,6 +38,27 @@ type ArgumentList struct {
Arguments []*Expression
}

// MakeCatalog will create a catalog entry from ArgumentList node.
func (e *ArgumentList) MakeCatalog(cat *Catalog) {
meta := &ArgumentListMeta{
NodeMeta: NodeMeta{
AstID: e.AstID,
GrlText: e.GrlText,
Snapshot: e.GetSnapshot(),
},
ArgumentASTIDs: nil,
}
if cat.AddMeta(e.AstID, meta) {
if e.Arguments != nil && len(e.Arguments) > 0 {
meta.ArgumentASTIDs = make([]string, len(e.Arguments))
for i, v := range e.Arguments {
meta.ArgumentASTIDs[i] = v.AstID
v.MakeCatalog(cat)
}
}
}
}

// Clone will clone this ArgumentList. The new clone will have an identical structure
func (e *ArgumentList) Clone(cloneTable *pkg.CloneTable) *ArgumentList {
clone := &ArgumentList{
17 changes: 17 additions & 0 deletions ast/ArrayMapSelector.go
Original file line number Diff line number Diff line change
@@ -39,6 +39,23 @@ type ArrayMapSelector struct {
Value reflect.Value
}

// MakeCatalog will create a catalog entry from ArrayMapSelector node.
func (e *ArrayMapSelector) MakeCatalog(cat *Catalog) {
meta := &ArrayMapSelectorMeta{
NodeMeta: NodeMeta{
AstID: e.AstID,
GrlText: e.GrlText,
Snapshot: e.GetSnapshot(),
},
}
if cat.AddMeta(e.AstID, meta) {
if e.Expression != nil {
meta.ExpressionID = e.Expression.AstID
e.Expression.MakeCatalog(cat)
}
}
}

// ArrayMapSelectorReceiver must be implemented by all other ast graph that uses map/array selector
type ArrayMapSelectorReceiver interface {
AcceptArrayMapSelector(sel *ArrayMapSelector) error
26 changes: 26 additions & 0 deletions ast/Assignment.go
Original file line number Diff line number Diff line change
@@ -42,6 +42,32 @@ type Assignment struct {
IsMulAssign bool
}

// MakeCatalog will create a catalog entry from Assignment node.
func (e *Assignment) MakeCatalog(cat *Catalog) {
meta := &AssigmentMeta{
NodeMeta: NodeMeta{
AstID: e.AstID,
GrlText: e.GrlText,
Snapshot: e.GetSnapshot(),
},
}
if cat.AddMeta(e.AstID, meta) {
if e.Variable != nil {
meta.VariableID = e.Variable.AstID
e.Variable.MakeCatalog(cat)
}
if e.Expression != nil {
meta.ExpressionID = e.Expression.AstID
e.Expression.MakeCatalog(cat)
}
meta.IsAssign = e.IsAssign
meta.IsPlusAssign = e.IsPlusAssign
meta.IsMinusAssign = e.IsMinusAssign
meta.IsDivAssign = e.IsDivAssign
meta.IsMulAssign = e.IsMulAssign
}
}

// AssignmentReceiver must be implemented by all other ast graph that uses an assigment expression
type AssignmentReceiver interface {
AcceptAssignment(assignment *Assignment) error
1 change: 1 addition & 0 deletions ast/Ast.go
Original file line number Diff line number Diff line change
@@ -61,4 +61,5 @@ type Node interface {
GetGrlText() string
GetSnapshot() string
SetGrlText(grlText string)
MakeCatalog(cat *Catalog)
}
44 changes: 44 additions & 0 deletions ast/Constant.go
Original file line number Diff line number Diff line change
@@ -16,8 +16,10 @@ package ast

import (
"bytes"
"encoding/binary"
"fmt"
"github.com/hyperjumptech/grule-rule-engine/ast/unique"
"math"
"reflect"

"github.com/hyperjumptech/grule-rule-engine/pkg"
@@ -41,6 +43,48 @@ type Constant struct {
IsNil bool
}

// MakeCatalog will create a catalog entry from Constant node.
func (e *Constant) MakeCatalog(cat *Catalog) {
meta := &ConstantMeta{
NodeMeta: NodeMeta{
AstID: e.AstID,
GrlText: e.GrlText,
Snapshot: e.GetSnapshot(),
},
}
if cat.AddMeta(e.AstID, meta) {
var buff bytes.Buffer
switch e.Value.Kind() {
case reflect.String:
meta.ValueType = TypeString
length := make([]byte, 8)
data := []byte(e.Value.String())
binary.LittleEndian.PutUint64(length, uint64(len(data)))
buff.Write(length)
buff.Write(data)
case reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Uint, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8, reflect.Int:
meta.ValueType = TypeInteger
intData := make([]byte, 8)
binary.LittleEndian.PutUint64(intData, uint64(e.Value.Int()))
buff.Write(intData)
case reflect.Float32, reflect.Float64:
meta.ValueType = TypeFloat
floatData := make([]byte, 8)
binary.LittleEndian.PutUint64(floatData, math.Float64bits(e.Value.Float()))
buff.Write(floatData)
case reflect.Bool:
meta.ValueType = TypeBoolean
if e.Value.Bool() {
buff.WriteByte(1)
} else {
buff.WriteByte(0)
}
}
meta.ValueBytes = buff.Bytes()
meta.IsNil = e.IsNil
}
}

// Clone will clone this Constant. The new clone will have an identical structure
func (e *Constant) Clone(cloneTable *pkg.CloneTable) *Constant {
clone := &Constant{
31 changes: 31 additions & 0 deletions ast/Expression.go
Original file line number Diff line number Diff line change
@@ -81,6 +81,37 @@ type Expression struct {
Evaluated bool
}

// MakeCatalog will create a catalog entry from Expression node.
func (e *Expression) MakeCatalog(cat *Catalog) {
meta := &ExpressionMeta{
NodeMeta: NodeMeta{
AstID: e.AstID,
GrlText: e.GrlText,
Snapshot: e.GetSnapshot(),
},
}
if cat.AddMeta(e.AstID, meta) {
if e.LeftExpression != nil {
meta.LeftExpressionID = e.LeftExpression.AstID
e.LeftExpression.MakeCatalog(cat)
}
if e.RightExpression != nil {
meta.RightExpressionID = e.RightExpression.AstID
e.RightExpression.MakeCatalog(cat)
}
if e.SingleExpression != nil {
meta.SingleExpressionID = e.SingleExpression.AstID
e.SingleExpression.MakeCatalog(cat)
}
if e.ExpressionAtom != nil {
meta.ExpressionAtomID = e.ExpressionAtom.AstID
e.ExpressionAtom.MakeCatalog(cat)
}
meta.Operator = e.Operator
meta.Negated = e.Negated
}
}

// Clone will clone this Expression. The new clone will have an identical structure
func (e *Expression) Clone(cloneTable *pkg.CloneTable) *Expression {
clone := &Expression{
40 changes: 38 additions & 2 deletions ast/ExpressionAtom.go
Original file line number Diff line number Diff line change
@@ -43,13 +43,49 @@ type ExpressionAtom struct {
Variable *Variable
Negated bool
ExpressionAtom *ExpressionAtom
Value reflect.Value
ValueNode model.ValueNode
ArrayMapSelector *ArrayMapSelector

Value reflect.Value
ValueNode model.ValueNode

Evaluated bool
}

// MakeCatalog will create a catalog entry from ExpressionAtom node.
func (e *ExpressionAtom) MakeCatalog(cat *Catalog) {
meta := &ExpressionAtomMeta{
NodeMeta: NodeMeta{
AstID: e.AstID,
GrlText: e.GrlText,
Snapshot: e.GetSnapshot(),
},
}
if cat.AddMeta(e.AstID, meta) {
if e.Constant != nil {
meta.ConstantID = e.Constant.AstID
e.Constant.MakeCatalog(cat)
}
if e.FunctionCall != nil {
meta.FunctionCallID = e.FunctionCall.AstID
e.FunctionCall.MakeCatalog(cat)
}
if e.Variable != nil {
meta.VariableID = e.Variable.AstID
e.Variable.MakeCatalog(cat)
}
if e.ExpressionAtom != nil {
meta.ExpressionAtomID = e.ExpressionAtom.AstID
e.ExpressionAtom.MakeCatalog(cat)
}
if e.ArrayMapSelector != nil {
meta.ArrayMapSelectorID = e.ArrayMapSelector.AstID
e.ArrayMapSelector.MakeCatalog(cat)
}
meta.VariableName = e.VariableName
meta.Negated = e.Negated
}
}

// ExpressionAtomReceiver contains function to be implemented by other AST graph to receive an ExpressionAtom AST graph
type ExpressionAtomReceiver interface {
AcceptExpressionAtom(exp *ExpressionAtom) error
18 changes: 18 additions & 0 deletions ast/FunctionCall.go
Original file line number Diff line number Diff line change
@@ -40,6 +40,24 @@ type FunctionCall struct {
Value reflect.Value
}

// MakeCatalog will create a catalog entry from FunctionCall node.
func (e *FunctionCall) MakeCatalog(cat *Catalog) {
meta := &FunctionCallMeta{
NodeMeta: NodeMeta{
AstID: e.AstID,
GrlText: e.GrlText,
Snapshot: e.GetSnapshot(),
},
}
if cat.AddMeta(e.AstID, meta) {
if e.ArgumentList != nil {
meta.ArgumentListID = e.ArgumentList.AstID
e.ArgumentList.MakeCatalog(cat)
}
meta.FunctionName = e.FunctionName
}
}

// Clone will clone this FunctionCall. The new clone will have an identical structure
func (e *FunctionCall) Clone(cloneTable *pkg.CloneTable) *FunctionCall {
clone := &FunctionCall{
71 changes: 71 additions & 0 deletions ast/KnowledgeBase.go
Original file line number Diff line number Diff line change
@@ -17,6 +17,8 @@ package ast
import (
"bytes"
"fmt"
"github.com/sirupsen/logrus"
"io"
"sort"
"strings"
"sync"
@@ -54,6 +56,50 @@ func (lib *KnowledgeLibrary) GetKnowledgeBase(name, version string) *KnowledgeBa
return kb
}

// LoadKnowledgeBaseFromReader will load the KnowledgeBase stored using StoreKnowledgeBaseToWriter function
// be it from file, or anywhere. The reader we needed is a plain io.Reader, thus closing the source stream is your responsibility.
// This should hopefully speedup loading huge ruleset by storing and reading them
// without having to parse the GRL.
func (lib *KnowledgeLibrary) LoadKnowledgeBaseFromReader(reader io.Reader, overwrite bool) (retKb *KnowledgeBase, retErr error) {
defer func() {
if r := recover(); r != nil {
retKb = nil
logrus.Panicf("panic recovered during LoadKnowledgeBaseFromReader. send us your report to https://github.com/hyperjumptech/grule-rule-engine/issues")
}
}()

catalog := &Catalog{}
err := catalog.ReadCatalogFromReader(reader)
if err != nil {
return nil, err
}
kb := catalog.BuildKnowledgeBase()
if overwrite {
lib.Library[fmt.Sprintf("%s:%s", kb.Name, kb.Version)] = kb
return kb, nil
}
if _, ok := lib.Library[fmt.Sprintf("%s:%s", kb.Name, kb.Version)]; !ok {
lib.Library[fmt.Sprintf("%s:%s", kb.Name, kb.Version)] = kb
return kb, nil
}
return nil, fmt.Errorf("KnowledgeBase %s version %s exist", kb.Name, kb.Version)
}

// StoreKnowledgeBaseToWriter will store a KnowledgeBase in binary form
// once store, the binary stream can be read using LoadKnowledgeBaseFromReader function.
// This should hopefully speedup loading huge ruleset by storing and reading them
// without having to parse the GRL.
//
// The stored binary file is greatly increased (easily 10x fold) due to lots of generated keys for AST Nodes
// that was also saved. To overcome this, the use of archive/zip package for Readers and Writers could cut down the
// binary size quite a lot.
func (lib *KnowledgeLibrary) StoreKnowledgeBaseToWriter(writer io.Writer, name, version string) error {
kb := lib.GetKnowledgeBase(name, version)
cat := kb.MakeCatalog()
err := cat.WriteCatalogToWriter(writer)
return err
}

// NewKnowledgeBaseInstance will create a new instance based on KnowledgeBase blue print
// identified by its name and version
func (lib *KnowledgeLibrary) NewKnowledgeBaseInstance(name, version string) *KnowledgeBase {
@@ -81,8 +127,33 @@ type KnowledgeBase struct {
RuleEntries map[string]*RuleEntry
}

// MakeCatalog will create a catalog entry for all AST Nodes under the KnowledgeBase
// the catalog can be used to save the knowledge base into a Writer, or to
// rebuild the KnowledgeBase from it.
// This function also will catalog the WorkingMemory.
func (e *KnowledgeBase) MakeCatalog() *Catalog {
catalog := &Catalog{
KnowledgeBaseName: e.Name,
KnowledgeBaseVersion: e.Version,
Data: nil,
MemoryName: "",
MemoryVersion: "",
MemoryVariableSnapshotMap: nil,
MemoryExpressionSnapshotMap: nil,
MemoryExpressionAtomSnapshotMap: nil,
MemoryExpressionVariableMap: nil,
MemoryExpressionAtomVariableMap: nil,
}
for _, v := range e.RuleEntries {
v.MakeCatalog(catalog)
}
e.WorkingMemory.MakeCatalog(catalog)
return catalog
}

// IsIdentical will validate if two KnoledgeBase is identical. Used to validate if the origin and clone is identical.
func (e *KnowledgeBase) IsIdentical(that *KnowledgeBase) bool {
// fmt.Printf("%s\n%s\n", e.GetSnapshot(), that.GetSnapshot())
return e.GetSnapshot() == that.GetSnapshot()
}

24 changes: 24 additions & 0 deletions ast/RuleEntry.go
Original file line number Diff line number Diff line change
@@ -47,6 +47,30 @@ type RuleEntry struct {
Retracted bool
}

// MakeCatalog will create a catalog entry from RuleEntry node.
func (e *RuleEntry) MakeCatalog(cat *Catalog) {
meta := &RuleEntryMeta{
NodeMeta: NodeMeta{
AstID: e.AstID,
GrlText: e.GrlText,
Snapshot: e.GetSnapshot(),
},
}
if cat.AddMeta(e.AstID, meta) {
if e.WhenScope != nil {
meta.WhenScopeID = e.WhenScope.AstID
e.WhenScope.MakeCatalog(cat)
}
if e.ThenScope != nil {
meta.ThenScopeID = e.ThenScope.AstID
e.ThenScope.MakeCatalog(cat)
}
meta.RuleName = e.RuleName
meta.RuleDescription = e.RuleDescription
meta.Salience = e.Salience
}
}

// RuleEntryReceiver should be implemented by any rule AST object that receive a RuleEntry
type RuleEntryReceiver interface {
ReceiveRuleEntry(entry *RuleEntry) error
2,319 changes: 2,319 additions & 0 deletions ast/Serializer.go

Large diffs are not rendered by default.

225 changes: 225 additions & 0 deletions ast/Serializer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
// Copyright hyperjumptech/grule-rule-engine Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ast

import (
"bytes"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"testing"
)

func TestReadWriteString(t *testing.T) {
str := uuid.New().String()
buff := &bytes.Buffer{}
err := WriteStringToWriter(buff, str)
assert.Nil(t, err)

buff2 := bytes.NewBuffer(buff.Bytes())
str2, err := ReadStringFromReader(buff2)
assert.Nil(t, err)

assert.Equal(t, str, str2)
}

func TestAssigmentMetaReadWrite(t *testing.T) {
assigment := &AssigmentMeta{
NodeMeta: NodeMeta{
AstID: uuid.New().String(),
GrlText: uuid.New().String(),
Snapshot: uuid.New().String(),
},
VariableID: uuid.New().String(),
ExpressionID: uuid.New().String(),
IsAssign: true,
IsPlusAssign: false,
IsMinusAssign: true,
IsDivAssign: false,
IsMulAssign: true,
}
buff := &bytes.Buffer{}
err := assigment.WriteMetaTo(buff)
assert.Nil(t, err)

buff2 := bytes.NewBuffer(buff.Bytes())

assigment2 := &AssigmentMeta{}
err = assigment2.ReadMetaFrom(buff2)
assert.Nil(t, err)

assert.True(t, assigment.Equals(assigment2))
}

func TestSerialization(t *testing.T) {
cat := &Catalog{
KnowledgeBaseName: uuid.New().String(),
KnowledgeBaseVersion: uuid.New().String(),
Data: map[string]Meta{
uuid.New().String(): &AssigmentMeta{
NodeMeta: NodeMeta{
AstID: uuid.New().String(),
GrlText: uuid.New().String(),
Snapshot: uuid.New().String(),
},
VariableID: uuid.New().String(),
ExpressionID: uuid.New().String(),
IsAssign: true,
IsPlusAssign: false,
IsMinusAssign: true,
IsDivAssign: false,
IsMulAssign: true,
},
uuid.New().String(): &ConstantMeta{
NodeMeta: NodeMeta{
AstID: uuid.New().String(),
GrlText: uuid.New().String(),
Snapshot: uuid.New().String(),
},
ValueType: TypeString,
ValueBytes: []byte(uuid.New().String()),
IsNil: true,
},
uuid.New().String(): &ExpressionMeta{
NodeMeta: NodeMeta{
AstID: uuid.New().String(),
GrlText: uuid.New().String(),
Snapshot: uuid.New().String(),
},
LeftExpressionID: uuid.New().String(),
RightExpressionID: uuid.New().String(),
SingleExpressionID: uuid.New().String(),
ExpressionAtomID: uuid.New().String(),
Operator: 3,
Negated: false,
},
uuid.New().String(): &ExpressionAtomMeta{
NodeMeta: NodeMeta{
AstID: uuid.New().String(),
GrlText: uuid.New().String(),
Snapshot: uuid.New().String(),
},
VariableName: uuid.New().String(),
ConstantID: uuid.New().String(),
FunctionCallID: uuid.New().String(),
VariableID: uuid.New().String(),
Negated: true,
ExpressionAtomID: uuid.New().String(),
ArrayMapSelectorID: uuid.New().String(),
},
uuid.New().String(): &FunctionCallMeta{
NodeMeta: NodeMeta{
AstID: uuid.New().String(),
GrlText: uuid.New().String(),
Snapshot: uuid.New().String(),
},
FunctionName: uuid.New().String(),
ArgumentListID: uuid.New().String(),
},
uuid.New().String(): &RuleEntryMeta{
NodeMeta: NodeMeta{
AstID: uuid.New().String(),
GrlText: uuid.New().String(),
Snapshot: uuid.New().String(),
},
RuleName: uuid.New().String(),
RuleDescription: uuid.New().String(),
Salience: 234,
WhenScopeID: uuid.New().String(),
ThenScopeID: uuid.New().String(),
},
uuid.New().String(): &ThenExpressionMeta{
NodeMeta: NodeMeta{
AstID: uuid.New().String(),
GrlText: uuid.New().String(),
Snapshot: uuid.New().String(),
},
AssignmentID: "",
ExpressionAtomID: "",
},
uuid.New().String(): &ThenExpressionListMeta{
NodeMeta: NodeMeta{
AstID: uuid.New().String(),
GrlText: uuid.New().String(),
Snapshot: uuid.New().String(),
},
ThenExpressionIDs: nil,
},
uuid.New().String(): &ThenScopeMeta{
NodeMeta: NodeMeta{
AstID: uuid.New().String(),
GrlText: uuid.New().String(),
Snapshot: uuid.New().String(),
},
ThenExpressionListID: "",
},
uuid.New().String(): &VariableMeta{
NodeMeta: NodeMeta{
AstID: uuid.New().String(),
GrlText: uuid.New().String(),
Snapshot: uuid.New().String(),
},
Name: uuid.New().String(),
VariableID: uuid.New().String(),
ArrayMapSelectorID: uuid.New().String(),
},
uuid.New().String(): &WhenScopeMeta{
NodeMeta: NodeMeta{
AstID: uuid.New().String(),
GrlText: uuid.New().String(),
Snapshot: uuid.New().String(),
},
ExpressionID: uuid.New().String(),
},
},
MemoryName: "MemoryName",
MemoryVersion: "MemoryVersion",
MemoryVariableSnapshotMap: map[string]string{
uuid.New().String(): uuid.New().String(),
uuid.New().String(): uuid.New().String(),
uuid.New().String(): uuid.New().String(),
},
MemoryExpressionAtomSnapshotMap: map[string]string{
uuid.New().String(): uuid.New().String(),
uuid.New().String(): uuid.New().String(),
uuid.New().String(): uuid.New().String(),
uuid.New().String(): uuid.New().String(),
},
MemoryExpressionVariableMap: map[string][]string{
uuid.New().String(): {uuid.New().String(), uuid.New().String(), uuid.New().String()},
uuid.New().String(): {uuid.New().String(), uuid.New().String(), uuid.New().String()},
uuid.New().String(): {uuid.New().String(), uuid.New().String(), uuid.New().String()},
uuid.New().String(): {uuid.New().String(), uuid.New().String(), uuid.New().String()},
uuid.New().String(): {uuid.New().String(), uuid.New().String(), uuid.New().String()},
},
MemoryExpressionAtomVariableMap: map[string][]string{
uuid.New().String(): {uuid.New().String(), uuid.New().String(), uuid.New().String()},
uuid.New().String(): {uuid.New().String(), uuid.New().String(), uuid.New().String()},
},
}

buffer := &bytes.Buffer{}

err := cat.WriteCatalogToWriter(buffer)
assert.Nil(t, err)

catBytes := buffer.Bytes()

buffer2 := bytes.NewBuffer(catBytes)
cat2 := &Catalog{}
err = cat2.ReadCatalogFromReader(buffer2)
assert.Nil(t, err)

assert.True(t, cat.Equals(cat2))
}
21 changes: 21 additions & 0 deletions ast/ThenExpression.go
Original file line number Diff line number Diff line change
@@ -36,6 +36,27 @@ type ThenExpression struct {
ExpressionAtom *ExpressionAtom
}

// MakeCatalog create a catalog entry for this AST Node
func (e *ThenExpression) MakeCatalog(cat *Catalog) {
meta := &ThenExpressionMeta{
NodeMeta: NodeMeta{
AstID: e.AstID,
GrlText: e.GrlText,
Snapshot: e.GetSnapshot(),
},
}
if cat.AddMeta(e.AstID, meta) {
if e.Assignment != nil {
meta.AssignmentID = e.Assignment.AstID
e.Assignment.MakeCatalog(cat)
}
if e.ExpressionAtom != nil {
meta.ExpressionAtomID = e.ExpressionAtom.AstID
e.ExpressionAtom.MakeCatalog(cat)
}
}
}

// ThenExpressionReceiver must be implemented by any AST object that will store a Then expression
type ThenExpressionReceiver interface {
AcceptThenExpression(expr *ThenExpression) error
20 changes: 20 additions & 0 deletions ast/ThenExpressionList.go
Original file line number Diff line number Diff line change
@@ -36,6 +36,26 @@ type ThenExpressionList struct {
ThenExpressions []*ThenExpression
}

// MakeCatalog create a catalog entry for this AST Node
func (e *ThenExpressionList) MakeCatalog(cat *Catalog) {
meta := &ThenExpressionListMeta{
NodeMeta: NodeMeta{
AstID: e.AstID,
GrlText: e.GrlText,
Snapshot: e.GetSnapshot(),
},
}
if cat.AddMeta(e.AstID, meta) {
if e.ThenExpressions != nil && len(e.ThenExpressions) > 0 {
meta.ThenExpressionIDs = make([]string, len(e.ThenExpressions))
for i, v := range e.ThenExpressions {
meta.ThenExpressionIDs[i] = v.AstID
v.MakeCatalog(cat)
}
}
}
}

// ThenExpressionListReceiver must be implemented by any AST object that hold a ThenExpression list AST object
type ThenExpressionListReceiver interface {
AcceptThenExpressionList(list *ThenExpressionList) error
17 changes: 17 additions & 0 deletions ast/ThenScope.go
Original file line number Diff line number Diff line change
@@ -35,6 +35,23 @@ type ThenScope struct {
ThenExpressionList *ThenExpressionList
}

// MakeCatalog create a catalog entry for this AST Node
func (e *ThenScope) MakeCatalog(cat *Catalog) {
meta := &ThenScopeMeta{
NodeMeta: NodeMeta{
AstID: e.AstID,
GrlText: e.GrlText,
Snapshot: e.GetSnapshot(),
},
}
if cat.AddMeta(e.AstID, meta) {
if e.ThenExpressionList != nil {
meta.ThenExpressionListID = e.ThenExpressionList.AstID
e.ThenExpressionList.MakeCatalog(cat)
}
}
}

// ThenScopeReceiver must be implemented by any AST object that will hold a ThenScope
type ThenScopeReceiver interface {
AcceptThenScope(thenScope *ThenScope) error
22 changes: 22 additions & 0 deletions ast/Variable.go
Original file line number Diff line number Diff line change
@@ -44,6 +44,28 @@ type Variable struct {
Value reflect.Value
}

// MakeCatalog create a catalog entry for this AST Node
func (e *Variable) MakeCatalog(cat *Catalog) {
meta := &VariableMeta{
NodeMeta: NodeMeta{
AstID: e.AstID,
GrlText: e.GrlText,
Snapshot: e.GetSnapshot(),
},
}
if cat.AddMeta(e.AstID, meta) {
if e.Variable != nil {
meta.VariableID = e.Variable.AstID
e.Variable.MakeCatalog(cat)
}
if e.ArrayMapSelector != nil {
meta.ArrayMapSelectorID = e.ArrayMapSelector.AstID
e.ArrayMapSelector.MakeCatalog(cat)
}
meta.Name = e.Name
}
}

// Clone will clone this Variable. The new clone will have an identical structure
func (e *Variable) Clone(cloneTable *pkg.CloneTable) *Variable {
clone := &Variable{
17 changes: 17 additions & 0 deletions ast/WhenScope.go
Original file line number Diff line number Diff line change
@@ -38,6 +38,23 @@ type WhenScope struct {
Expression *Expression
}

// MakeCatalog create a catalog entry for this AST Node
func (e *WhenScope) MakeCatalog(cat *Catalog) {
meta := &WhenScopeMeta{
NodeMeta: NodeMeta{
AstID: e.AstID,
GrlText: e.GrlText,
Snapshot: e.GetSnapshot(),
},
}
if cat.AddMeta(e.AstID, meta) {
if e.Expression != nil {
meta.ExpressionID = e.Expression.AstID
e.Expression.MakeCatalog(cat)
}
}
}

// WhenScopeReceiver must be implemented by AST object that stores WhenScope
type WhenScopeReceiver interface {
AcceptWhenScope(whenScope *WhenScope) error
32 changes: 32 additions & 0 deletions ast/WorkingMemory.go
Original file line number Diff line number Diff line change
@@ -49,6 +49,38 @@ type WorkingMemory struct {
ID string
}

// MakeCatalog create a catalog entry of this working memory
func (e *WorkingMemory) MakeCatalog(cat *Catalog) {
cat.MemoryName = e.Name
cat.MemoryVersion = e.Version
cat.MemoryExpressionSnapshotMap = make(map[string]string)
for k, v := range e.expressionSnapshotMap {
cat.MemoryExpressionSnapshotMap[k] = v.AstID
}
cat.MemoryExpressionAtomSnapshotMap = make(map[string]string)
for k, v := range e.expressionAtomSnapshotMap {
cat.MemoryExpressionAtomSnapshotMap[k] = v.AstID
}
cat.MemoryVariableSnapshotMap = make(map[string]string)
for k, v := range e.variableSnapshotMap {
cat.MemoryVariableSnapshotMap[k] = v.AstID
}
cat.MemoryExpressionVariableMap = make(map[string][]string)
for k, v := range e.expressionVariableMap {
cat.MemoryExpressionVariableMap[k.AstID] = make([]string, len(v))
for i, j := range v {
cat.MemoryExpressionVariableMap[k.AstID][i] = j.AstID
}
}
cat.MemoryExpressionAtomVariableMap = make(map[string][]string)
for k, v := range e.expressionAtomVariableMap {
cat.MemoryExpressionAtomVariableMap[k.AstID] = make([]string, len(v))
for i, j := range v {
cat.MemoryExpressionAtomVariableMap[k.AstID][i] = j.AstID
}
}
}

// DebugContent will shows the working memory mapping content
func (e *WorkingMemory) DebugContent() {
if AstLog.Level <= logrus.DebugLevel {
Empty file modified builder/RuleBuilder.go
100644 → 100755
Empty file.
13 changes: 12 additions & 1 deletion examples/PurcasingTaxSample_test.go
Original file line number Diff line number Diff line change
@@ -15,6 +15,7 @@
package examples

import (
"bytes"
"fmt"
"github.com/hyperjumptech/grule-rule-engine/ast"
"github.com/hyperjumptech/grule-rule-engine/builder"
@@ -134,11 +135,21 @@ func (cf *CashFlowCalculator) CalculatePurchases(t *testing.T) {

kb := lib.NewKnowledgeBaseInstance("Purchase Calculator", "0.0.1")

buff := &bytes.Buffer{}
cat := kb.MakeCatalog()
err = cat.WriteCatalogToWriter(buff)
assert.Nil(t, err)

buff2 := bytes.NewBuffer(buff.Bytes())
cat2 := &ast.Catalog{}
cat2.ReadCatalogFromReader(buff2)
nkb := cat2.BuildKnowledgeBase()

for _, purchase := range Purchases {
dctx := ast.NewDataContext()
dctx.Add("CashFlow", cashFlow)
dctx.Add("Purchase", purchase)
err = engine.Execute(dctx, kb)
err = engine.Execute(dctx, nkb)
assert.NoError(t, err)
}

46 changes: 46 additions & 0 deletions examples/Serialization_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright hyperjumptech/grule-rule-engine Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package examples

import (
"bytes"
"github.com/hyperjumptech/grule-rule-engine/ast"
"github.com/hyperjumptech/grule-rule-engine/builder"
"github.com/hyperjumptech/grule-rule-engine/pkg"
"github.com/stretchr/testify/assert"
"testing"
)

func TestSerialization(t *testing.T) {
lib := ast.NewKnowledgeLibrary()
rb := builder.NewRuleBuilder(lib)
err := rb.BuildRuleFromResource("Purchase Calculator", "0.0.1", pkg.NewFileResource("CashFlowRule.grl"))
assert.NoError(t, err)

kb := lib.GetKnowledgeBase("Purchase Calculator", "0.0.1")
cat := kb.MakeCatalog()

buff1 := &bytes.Buffer{}
err = cat.WriteCatalogToWriter(buff1)
assert.Nil(t, err)

buff2 := bytes.NewBuffer(buff1.Bytes())
cat2 := &ast.Catalog{}
err = cat2.ReadCatalogFromReader(buff2)
assert.Nil(t, err)

kb2 := cat2.BuildKnowledgeBase()
assert.True(t, kb.IsIdentical(kb2))
}
14 changes: 14 additions & 0 deletions examples/UnicodeRule_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
// Copyright hyperjumptech/grule-rule-engine Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package examples

import (

0 comments on commit 8c7b275

Please sign in to comment.