Rule engine implementation in Golang

Gopheer Holds The Rule

Build Status Build Status Go Report Card License

"Gopher Holds The Rules"

Grule-Rule-Engine

import "github.com/hyperjumptech/grule-rule-engine"

Rule Engine for Go

Grule is a Rule Engine library for the Golang programming language. Inspired by the acclaimed JBOSS Drools, done in a much simple manner.

Like Drools, Grule have its own DSL comparable as follows.

Drools's DRL be like :

rule "SpeedUp"
    salience 10
    when
        $TestCar : TestCarClass( speedUp == true && speed < maxSpeed )
        $DistanceRecord : DistanceRecordClass()
    then
        $TestCar.setSpeed($TestCar.Speed + $TestCar.SpeedIncrement);
        update($TestCar);
        $DistanceRecord.setTotalDistance($DistanceRecord.getTotalDistance() + $TestCar.Speed);
        update($DistanceRecord);
end

And Grule's GRL be like :

rule SpeedUp "When testcar is speeding up we keep increase the speed." salience 10  {
    when
        TestCar.SpeedUp == true && TestCar.Speed < TestCar.MaxSpeed
    then
        TestCar.Speed = TestCar.Speed + TestCar.SpeedIncrement;
        DistanceRecord.TotalDistance = DistanceRecord.TotalDistance + TestCar.Speed;
}

What is RuleEngine

There isn't a better explanation than the article authored by Martin Fowler. You can read the article here (RulesEngine by Martin Fowler).

Taken from TutorialsPoint website (with slight modifications),

Grule Rule Engine is a Production Rule System that uses the rule-based approach to implement an Expert System. Expert Systems are knowledge-based systems that use knowledge representations to process acquired knowledge into a knowledge base that can be used for reasoning.

A Production Rule System is Turing complete with a focus on knowledge representation to express propositional and first-order logic in a concise, non-ambiguous and declarative manner.

The brain of a Production Rules System is an Inference Engine that can scale to a large number of rules and facts. The Inference Engine matches facts and data against Production Rules – also called Productions or just Rules – to infer conclusions which result in actions.

A Production Rule is a two-part structure that uses first-order logic for reasoning over knowledge representation. A business rule engine is a software system that executes one or more business rules in a runtime production environment.

A Rule Engine allows you to define “What to Do” and not “How to do it.”

What is a Rule

(also taken from TutorialsPoint)

Rules are pieces of knowledge often expressed as, "When some conditions occur, then do some tasks."

When
   <Condition is true>
Then
   <Take desired Action>

The most important part of a Rule is its when part. If the when part is satisfied, the then part is triggered.

rule  <rule_name> <rule_description>
   <attribute> <value> {
   when
      <conditions>

   then
      <actions>
}

Advantages of a Rule Engine

Declarative Programming

Rules make it easy to express solutions to difficult problems and get the verifications as well. Unlike codes, Rules are written with less complex language; Business Analysts can easily read and verify a set of rules.

Logic and Data Separation

The data resides in the Domain Objects and the business logic resides in the Rules. Depending upon the kind of project, this kind of separation can be very advantageous.

Centralization of Knowledge

By using Rules, you create a repository of knowledge (a knowledge base) which is executable. It is a single point of truth for business policy. Ideally, Rules are so readable that they can also serve as documentation.

Agility To Change

Since business rules are actually treated as data. Adjusting the rule according to business dynamic nature become trivial. No need to re-build codes, deploy as normal software development do, you only need to roll out sets of rule and apply them to knowledge repository.

Docs

Grule's Documentation now viewable in ViewDocs. http://hyperjumptech.viewdocs.io

Benchmark

Loading rules into KnowledgeBase:

  • To load 100 rules into knowledgeBase it took 99342047 ns/op (took the highest value) that is equal to ~99.342047ms and (49295906 B/op) ~49.295906MB memory per operation

  • To load 1000 rules into knowledgeBase it took 933617752 ns/op (took the highest value) that is equal to ~933.617752ms and (488126636 B/op) ~488.126636 memory per operation

Executing rules against a fact:

  • To execute a fact against 100 rules, Grule Engine took ~9697 ns/op (took the highest value as base) that is hardly ~0.009697ms and 3957 B/op which is pretty fast.

  • To execute a fact against 1000 rules, Grule Engine took ~568959 ns/op (took the highest value as base) that is hardly ~0.568959ms and 293710 B/op which is also pretty fast.

You can read the detail report here

Our Contributors



newm4n




jinagamvasubabu




niallnsec




inhuman




ariya




sapiderman




jtr860830




trancee




liouxiao




Troush




shanhuhai5739




derekwyatt




garychristianto




sourcesoft




sdowding-koho




yomashExpel




avisdsouza




zct




enricoojf




vlean


Tasks and Help Wanted

Yes. We need contributors to make Grule even better and useful to the Open Source Community.

  • Need to do more and more and more tests.
  • Better code coverage test.
  • Better commenting for go doc best practice.
  • Improve function argument handling to be more fluid and intuitive.

If you really want to help us, simply Fork the project and apply for Pull Request. Please read our Contribution Manual and Code of Conduct

Featured Projects Or Products

HYPERJUMP tech ( https://hyperjump.tech )


If your company or project is using Grule right now and don't mind tobe featured here, I would glad to add a name, logo (if you have one) and a link to your site. While it surely help us to know our users, your product might gain awareness by those who passing by. Please submit a "Feature me" issue, stating the company/product name, a link to the page and/or logo in the issue's description.

Owner
Hyperjump
Open-source first. Cloud native. DevOps excellence.
Hyperjump
Comments
  • Panic on workingMemory when remove a rule

    Panic on workingMemory when remove a rule

    Describe the bug Hey guys, I'm working with the grule engine and I came across the following situation.

    I created a microservice that uses grule to analyze data over rules. In this microservice I have an API that allows adding and deleting a rule. But the service is crashing when I remove an existing rule and add a new rule. So, I created a test application only using grule to analyze the problem and I came across a panic: DEBU[0000] Cloning working memory POCRules:0.0.1 lib=grule-rule-engine package=ast DEBU[0000] Cloning 7 expressionSnapshotMap entries lib=grule-rule-engine package=ast panic: expression BMF.Value1 is not on the clone table

    Apparently the problem is when removing the rule just on the ruleEntries but the workingMemory remains the same.

    I will index the sample code that reproduce this error.

    So my question is, Am I using rule removal correctly? Or is this a bug in the removal?

    To Reproduce Code: https://gist.github.com/vitbaq/35f52f30bdfb442eb7a7a892ddb1d5d3 Log: https://gist.github.com/vitbaq/88a6f00edcadbb8724218a17551d113f

    Testing environment:

    • Operating System/Platform: Ubuntu 18.04.5 LTS (Bionic Beaver)
    • Go version: go1.13.5 linux/amd64
    • Grule-rule-engine version: 1.9.0
  • Question: How to execute all the selected rules' actions instead of highest salience?

    Question: How to execute all the selected rules' actions instead of highest salience?

    Hi, I'm a bit confused or lost with this scenario, wonder if you could guide me: Imagine I have 20 rules. And with a given fact, 3 of them pass. The conflict set chooses the highest priority out of those 3. However I need all 3 to be executed (to run the action or then clause).

    Use case: I have a triggers/automations system that one trigger (an event like page created) could dispatch multiple actions (if the rules or conditions are met). Suppose that the user have created 2 automations (each automation consists of a trigger + GRL that has the action as a fact method) that their associated rules both pass for a given page creation event/trigger. One trigger/automation will send an email (one action or fact method), another will send a notification (another).

    Couldn't find a way to achieve this. Is there a better way to do this or am I missing something?

  • Retracted rule is not restored when the the engine is executed again

    Retracted rule is not restored when the the engine is executed again

    Describe the bug Retracted rules are not restored when the engine is executed again. From the Retract method's documentation, I'm assuming that when we call the engine.Execute(dataCtx, knowledgeBase) the retracted rule should be restored. However, this is not happening.

    To Reproduce Steps to reproduce the behavior: 1.Run the following code

    package examples
    
    import (
    	"fmt"
    	"strconv"
    	"testing"
    
    	"github.com/hyperjumptech/grule-rule-engine/ast"
    	"github.com/hyperjumptech/grule-rule-engine/builder"
    	"github.com/hyperjumptech/grule-rule-engine/engine"
    	"github.com/hyperjumptech/grule-rule-engine/pkg"
    	"github.com/stretchr/testify/assert"
    )
    
    type ComputedInputs struct {
    	Name string
    	Bid  float64
    	Rate float64
    }
    
    const Grl = `
    	rule SampleRule "Hero/Honda" {
    		When
    			ComputedInputs.Name == "Hero" || ComputedInputs.Name == "Honda"
    		Then
    			Log("SampleRule");
    			ComputedInputs.Bid = ComputedInputs.Rate;
    			Retract("SampleRule");
    	}
    `
    
    func TestItems(t *testing.T) {
    	testData := []*struct {
    		CI      *ComputedInputs
    		WantBid float64
    	}{
    		{
    			CI: &ComputedInputs{
    				Name: "Hero",
    				Rate: 10,
    			},
    			WantBid: 10,
    		},
    		{
    			CI: &ComputedInputs{
    				Name: "Honda",
    				Rate: 10,
    			},
    			WantBid: 10,
    		},
    	}
    
    	// Prepare knowledgebase library and load it with our rule.
    	lib := ast.NewKnowledgeLibrary()
    	rb := builder.NewRuleBuilder(lib)
    	byteArr := pkg.NewBytesResource([]byte(Grl))
    	err := rb.BuildRuleFromResource("Tutorial", "0.0.1", byteArr)
    	assert.NoError(t, err)
    
    	engine := &engine.GruleEngine{
    		MaxCycle: 10,
    	}
    	knowledgeBase := lib.NewKnowledgeBaseInstance("Tutorial", "0.0.1")
    
    	for i, td := range testData {
    		t.Run(strconv.Itoa(i), func(t *testing.T) {
    			dataCtx := ast.NewDataContext()
    			err := dataCtx.Add("ComputedInputs", td.CI)
    			assert.NoError(t, err)
    
    			rules, _ := engine.FetchMatchingRules(dataCtx, knowledgeBase)
    			fmt.Printf("\nNo. of matching rules: %d\n", len(rules))
    
    			knowledgeBase := lib.NewKnowledgeBaseInstance("Tutorial", "0.0.1")
    			err = engine.Execute(dataCtx, knowledgeBase)
    			assert.NoError(t, err)
    			assert.Equal(t, td.WantBid, td.CI.Bid)
    		})
    	}
    }
    
    

    Expected behavior All the tests should pass

    Additional context The tests are passing if we create a new instance of knowledgeBase on every execution. Do we have to create a new instance of knowledge base whenever a rule is retracted?

  • decouple rules and evaluations

    decouple rules and evaluations

    Hi there!

    Is your feature request related to a problem? Please describe. With the release of 1.6.0, we saw the ability to use the same KnowledgeBase for multiple concurrent threads. This was great, since it allowed us to only parse the rule library once, which was a CPU-intensive operation that was bogging down our system previously. After upgrading grule we saw CPU usage reduce significantly. I think it can go further, though.

    In my opinion, there are a couple of issues with the current implementation - namely KnowledgeBase cloning:

    1. cloning still takes CPU cycles, and for our system, which sees potentially millions of events per day, this can add up to quite a bit of CPU usage.
    2. cloning takes up quite a bit of memory, and may actually have a memory leak, unless the leak was actually caused by our own code, which is also possible (I didn't profile deep enough to make a strong claim). The point is, cloning takes a non-zero amount of memory per event, and at millions of events per day, the memory allocations are significant.

    As far as I understand grule, I don't think cloning KnowledgeBases is necessary at all. A KnowledgeBase can be created and parsed once, say at the time of bootstrapping an application, and then traversed and evaluated as read only. This would require the evaluations - the results of the traversal - to be stored separately from the rule definitions in the KnowledgeBase.

    Describe the solution you'd like Looking at the current code, I see the value of a rule being stored within that rule's type. This ties the rule instance to the evaluation/result instance, and since these are tied, the rule instance must be instantiated (cloned) per evaluation (hence KnowledgeBase.Clone()).

    For example, here's the ExpressionAtom type, which contains the definition of a rule (or part of a rule), as well as the evaluation result:

    // ExpressionAtom AST node graph
    type ExpressionAtom struct {
    	AstID   string
    	GrlText string
    
    	Variable     *Variable
    	FunctionCall *FunctionCall
    	Value        reflect.Value            // <-- evaluation result
    
    	Evaluated bool                        // <-- evaluation result
    }
    

    If the evaluation results were kept in a different type, then that type could be instantiated per evaluation. This would be

    • a lot cheaper memory-wise, since rule definitions aren't being copied in memory per execution, and
    • it would save any CPU usage that's lost to cloning a KnowledgeBase.

    A couple of ways to maintain results in a separate structure:

    Option 1 Utilize the unique AstID on each rule definition type, and maintain a map of evaluation results that can be instantiated each time a KnowledgeBase is evaluated, e.g.

    type evalResults struct {
    	Evaluated       bool
    	Value reflect.Value
    }
    
    // keep some map[string]*evalResults, and when a rule or partial rule is evaluated, write to this map using AstID as the key
    

    One concern here is that this solution may require a sync.Map to enable concurrent writes of evaluation results, which could be less performant.

    Option 2 More of an alternative approach to writes in Option 1, but a chan *evalResult could be used in place of a sync.Map, like this

    type evalResults struct {
            AstID    string    // <-- add the AstID and pass it along on the channel
    	evaluated       bool
    	evalValueResult reflect.Value
    	evalErrorResult error
    }
    
    // when rule is evaluated
    evalResultsChannel <- &evalResult{ AstID: myAstID, ... }
    
    // in some result collection routine
    for _, evalResult := range evalResultsChannel {
        resultsMap[evalRules.AstID] = evalResult
    }
    

    This avoids any slowdown from a sync.Map, but result writes become async, so it's hard to know when a rule's evaluation result is formally stored in the resultsMap.

    These are just a couple of ideas off the top of my head; there may be better solutions!

    Is this decoupling of rule definition and evaluation result possible? Thanks in advance!

    Describe alternatives you've considered none

    Additional context none

  • Getting Max cycle reached with 1.6.0

    Getting Max cycle reached with 1.6.0

    Describe the bug I just upgraded from 1.5.0 to the latest 1.6.0 and started getting Max cycle reached, none of my rules had Retract. I'm also using JSON to build the rules, and it works as expected if I add Retract to the rules.

    To Reproduce Steps to reproduce the behavior:

    1. Use v1.6.0
    2. Add one or multiple rule(s) without having Retract at the end for that/those rule(s)
    3. See error Max cycle reached

    Expected behavior Previous versions Retract wasn't mandatory

    UPDATE: also getting warning while adding rule entry : <rulename>. got rule entry <rulename> already exist, possibly already added by antlr listener which wasn't happening before the upgrade UPDATE 2: It's also running the THEN statements for all rules that passed instead of just one (highest priority), kinda like what i was looking for here

  • [Antlr4] Lexer and parser generation using Antlr4.9 and upgraded antlr4  runtime for to improve performance

    [Antlr4] Lexer and parser generation using Antlr4.9 and upgraded antlr4 runtime for to improve performance

    Description:

    • Current GruleV3 lexer and parser are generated using Antlt4 4.8, so upgraded to Antlr4 4.9.2
    • There was a go runtime performance issue has been fixed in Anltr4 Library. so I have upgraded Antlr4 library to the recent commit

    Reference: https://github.com/antlr/antlr4/pull/3243

    The core problem in Golang runtime: it wastes too much time on hash code computing because of the incorrect Murmur3 and Set and hasher implementation. Compare the Java and the original Go runtime, Go computes murmur3 ~80,000,000 times, but the Java runtime only ~600,000 times.
    
    Here's my benchmark in my MBP '16 2019, the g4 files are copied from https://github.com/antlr/grammars-v4/tree/master/sql/mysql/Positive-Technologies, the original Go runtime cost 4.8s, and this PR costs 171ms.
    

    Changes to go.mod:

    	github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210928164016-886d244c73ef
    
  • [Proposal] Is there any way to add a rule/s to existing knowledgebase ?

    [Proposal] Is there any way to add a rule/s to existing knowledgebase ?

    Is your feature request related to a problem? Please describe. Currently my application is using grule-rule-engine and its working perfectly fine. Thanks @newm4n this is a great work !!!! Coming to the implementation, i am saving all my rule DSL's in DB (postgres) and pulling them when the container boot up and create a new knowledgeBase and keep it in Memory. so far all fine. let's suppose if someone creates a new rule or rules then i need to pull all the rules again and recreate the knowledgeBase and this is a heavy process even though i know that there are no changes to the existing knowledgeBase except new rules i have to add to the existing knowledgeBase.

    Describe the solution you'd like What i am proposing is, when a new rule or rules are created i should be having a way to update the existing knowledgeBase with new rule instead of reloading all the N rules instead of creating a new knowledgeBase instance everytime with all the rules.

    I can contribute on this, just that i need some ideas or pointers from you to implement this feature :)

    Additional context Let me know if you need more info

  • Support logic NOT operator

    Support logic NOT operator

    I think it should be easy to support logic NOT (!) operator.

    The rule would looks like:

    rule RuleOne "Some rule description." salience 10 {
        when
            !(someContext.attributeA.Match("123") &&
              someContext.attributeA.Contains("1"))
        then
            ...
    

    Without NOT operator, I have to write the rule like:

    rule RuleOne "Some rule description." salience 10 {
        when
            someContext.attributeA.Match("123")==false ||
            someContext.attributeA.Contains("1")==false
        then
            ...
    
  • Condition is not re-evaluated while the fact changed

    Condition is not re-evaluated while the fact changed

    I try to create two simple rules similar with the tutorial stated in the docs. These are my rules:

    // Initial fact
    // MF.IntAttr = 1200000
    
    rule Rule1 "Desc of Rule1" salience 0 {
    	when
    		MF.IntAttr >= 100000
    	then
    		Log("Rule1 called");
    		Retract("Rule1");
    }
    
    rule Rule2 "Desc of Rule2" salience -1 {
    	when
    		MF.IntAttr / 500000 > 0.0
    	then
    		Log("Rule2 called");
    		MF.IntAttr = MF.IntAttr - 500000;
    }
    

    I expect the engine to re-evaluate the condition in the Rule2 whenever a new cycle begins. And at some point, the condition will be false and the cycle done. But instead, I got an error like this:

    panic: GruleEngine successfully selected rule candidate for execution after 5000 cycles, this could possibly caused by rule entry(s) that keep added into execution pool but when executed it does not change any data in context. Please evaluate your rule entries "When" and "Then" scope. You can adjust the maximum cycle using GruleEngine.MaxCycle variable.
    

    It seems the engine is not re-evaluating the condition in Rule2 and remember it as true causing the cycle to keep repeating again and again. Please advice if I am doing it wrong. Thanks.

  • Are Maps and Slices Not supported?

    Are Maps and Slices Not supported?

    Describe the bug when using a map in the data context and attempting to reference it in a rule GRL errors occur. For example the rule:

    rule testRule "set internet revenue" salience 10 {
        when
            ORDER["IntAttribute"] == 123
        then
            OUTPUT["result"] = "testResult";
            Retract("testRule");
    }
    

    Fails with the following errors:

    line 4:9 token recognition error at: '['
    line 4:24 token recognition error at: ']'
    line 4:10 mismatched input '"IntAttribute"' expecting THEN
    line 6:10 token recognition error at: '['
    line 6:19 token recognition error at: ']'
    ERRO[0000] GRL error, after '[salience 10 { when ORDER]' and then unexpected '"IntAttribute"'  lib=grule struct=GruleParserV2Listener
    ERRO[0000] GRL error, after '[salience 10 { when ORDER]' and then unexpected '=='  lib=grule struct=GruleParserV2Listener
    ERRO[0000] GRL error, after '[salience 10 { when ORDER]' and then unexpected '123'  lib=grule struct=GruleParserV2Listener
    ERRO[0000] GRL error, after '[salience 10 { when ORDER]' and then unexpected 'then'  lib=grule struct=GruleParserV2Listener
    ERRO[0000] GRL error, after '[salience 10 { when ORDER]' and then unexpected 'OUTPUT'  lib=grule struct=GruleParserV2Listener
    ERRO[0000] GRL error, after '[salience 10 { when ORDER]' and then unexpected '"result"'  lib=grule struct=GruleParserV2Listener
    ERRO[0000] GRL error, after '[salience 10 { when ORDER]' and then unexpected '='  lib=grule struct=GruleParserV2Listener
    ERRO[0000] GRL error, after '[salience 10 { when ORDER]' and then unexpected '"testResult"'  lib=grule struct=GruleParserV2Listener
    ERRO[0000] GRL error, after '[salience 10 { when ORDER]' and then unexpected ';'  lib=grule struct=GruleParserV2Listener
    ERRO[0000] GRL error, after '[salience 10 { when ORDER]' and then unexpected 'Retract'  lib=grule struct=GruleParserV2Listener
    ERRO[0000] GRL error, after '[salience 10 { when ORDER]' and then unexpected '('  lib=grule struct=GruleParserV2Listener
    ERRO[0000] GRL error, after '[salience 10 { when ORDER]' and then unexpected '"testRule"'  lib=grule struct=GruleParserV2Listener
    ERRO[0000] GRL error, after '[salience 10 { when ORDER]' and then unexpected ')'  lib=grule struct=GruleParserV2Listener
    ERRO[0000] GRL error, after '[salience 10 { when ORDER]' and then unexpected ';'  lib=grule struct=GruleParserV2Listener
    ERRO[0000] Loading rule resource : Byte array resources 170 bytes failed. Got GRL error, after '[salience 10 { when ORDER]' and then unexpected ';'. Time take 2 ms 
    panic: error were found before builder bailing out. Got GRL error, after '[salience 10 { when ORDER]' and then unexpected ';'
    

    To Reproduce Steps to reproduce the behavior:

    1. Use the following code:
    package main
    
    import (
    	json "encoding/json"
    	"fmt"
    
    	ast "github.com/hyperjumptech/grule-rule-engine/ast"
    	builder "github.com/hyperjumptech/grule-rule-engine/builder"
    	engine "github.com/hyperjumptech/grule-rule-engine/engine"
    	pkg "github.com/hyperjumptech/grule-rule-engine/pkg"
    )
    
    func main() {
    	// Create example JSON
    	inputjson := `{
    		"IntAttribute": 123,
        	"StringAttribute": "Some string value",
        	"BooleanAttribute": true,
        	"FloatAttribute": 1.234,
        	"TimeAttribute": "2020-03-11T10:00:00Z",
    	}`
    
    	// unmarshal the json into a map
    	var input map[string]interface{}
    	json.Unmarshal([]byte(inputjson), &input)
    
    	// create the new Data Context
    	dataCtx := ast.NewDataContext()
    	// Add the input
    	err := dataCtx.Add("INPUT", &input)
    	if err != nil {
    		panic(err)
    	}
    	// add an output map
    	var output map[string]interface{}
    	err = dataCtx.Add("OUTPUT", &output)
    	if err != nil {
    		panic(err)
    	}
    
    	workingMemory := ast.NewWorkingMemory()
    	knowledgeBase := ast.NewKnowledgeBase("test", "1.0.0")
    	ruleBuilder := builder.NewRuleBuilder(knowledgeBase, workingMemory)
    
    	// example rules
    	drls := `
    		rule testRule "set internet revenue" salience 10 {
    			when
    				ORDER["IntAttribute"] == 123
    			then
    				OUTPUT["result"] = "testResult";
    				Retract("testRule");
    		}
    	`
    
    	byteArr := pkg.NewBytesResource([]byte(drls))
    	err = ruleBuilder.BuildRuleFromResource(byteArr)
    	if err != nil {
    		panic(err)
    	}
    
    	engine := engine.NewGruleEngine()
    	err = engine.Execute(dataCtx, knowledgeBase, workingMemory)
    	if err != nil {
    		panic(err)
    	}
    
    	fmt.Println(output["result"])
    }
    
    1. compile and run.

    Expected behavior Expected the rule to run and produce the output testResult

  • How to apply default value if no rule matched

    How to apply default value if no rule matched

    How to apply default value if no rule matched ? add a default rule like ?

    rule is_girl "set girl" salience 0 { when mf.sex == "famale" then mf.result = "girl" }

    rule is_boy "set boy" salience 0 { when mf.sex == "male" then mf.result = "boy" }

    rule default "set default value if no rule matched" salience 0 { when true then mf.result = "unknown"; }

    Thanks

  • FetchMatchingRules returns an empty array

    FetchMatchingRules returns an empty array

    Describe the bug I want to print the rulename which got successfully executed based on the given input Fact. I tried to use ruleEntries, err := engine.FetchMatchingRules(dataCtx, knowledgeBase) But ruleEntries returns an empty array

    To Reproduce Steps to reproduce the behavior: This is my code for Evaulation

    func Evaluate(stock *StockInfo) string {
    
    	dataCtx := ast.NewDataContext()
    	err := dataCtx.Add("stock", stock)
    	if err != nil {
    		panic(err)
    	}
    	knowledgeLibrary := ast.NewKnowledgeLibrary()
    	ruleBuilder := builder.NewRuleBuilder(knowledgeLibrary)
    	fileRes := pkg.NewFileResource("StocksEvaluation/stock-rule.grl")
    	err = ruleBuilder.BuildRuleFromResource("StockEvaluator", "0.0.1", fileRes)
    	if err != nil {
    		panic(err)
    	}
    	knowledgeBase := knowledgeLibrary.NewKnowledgeBaseInstance("StockEvaluator", "0.0.1")
    	engine := engine.NewGruleEngine()
    
    	err = engine.Execute(dataCtx, knowledgeBase)
    	if err != nil {
    		panic(err)
    	}
    	ruleEntries, err := engine.FetchMatchingRules(dataCtx, knowledgeBase)
    	if err != nil {
    		panic(err)
    	}
    	for _, ruleEntry := range ruleEntries {
    		println(ruleEntry.RuleName)
    	}
    	return stock.Decision
    }
    
    1. My Fact Object
    type StockInfo struct {
    	StockName       string
    	CashFlow        float64
    	Profit          float64
    	MarketCap       float64
    	EnterpriseValue float64
    	Dividend        float64
    	PromoterHolding float64
    	Decision        string
    }
    
    func (stock *StockInfo) ShouldBuy(yesorno bool) string {
    	//print(yesorno)
    	if yesorno == true {
    		return fmt.Sprintf("Buy %s", stock.StockName)
    	} else {
    		return fmt.Sprintf("Don't Buy %s", stock.StockName)
    	}
    
    }
    

    My grl Rules

    rule CashFlowVsNetProfit "Cash Flow is greater than Net Profit" salience 10 {
        when
            stock.CashFlow >= stock.Profit
        then
            Log("CashFlowVsNetProfit evaluated");
            stock.Decision = stock.ShouldBuy(true);
            Retract("CashFlowVsNetProfit");
    }
    rule MarketCapVsEnterpriseValue "If the enterprise value is way more than market cap, then it can be an alarming sign." salience 8 {
        when
            stock.EnterpriseValue > stock.MarketCap
        then
            Log("MarketCapVsEnterpriseValue evaluated");
            stock.Decision = stock.ShouldBuy(false);
            Retract("MarketCapVsEnterpriseValue");
    }
    
    rule DividendYield "a company that gives a higher dividend might imply that it is not utilising its profits for growth." salience 7 {
        when
            stock.Dividend >= 3.0
        then
            Log("Dividend Yield is evaluated");
            stock.Decision = stock.ShouldBuy(false);
            Retract("DividendYield");
    }
    
    
    

    I see that the CasFlowVsNetProfit gets successfully evaluated based on my fact input as the log message gets printed. But the array is still an empty object

    Expected behavior The array should be an empty object and it should print the rule evaluated.

    Additional context None

  • Support for Kind Interface and Pointer to Facts

    Support for Kind Interface and Pointer to Facts

    I notice there is a similar Issue which was closed. However, I think there is some use case which can still be consider supporting interface / pointer.

    I'm currently providing the Facts through REST API which can be in any arbitrary form, hence map[string]interface{}. But rule evaluation gives me

    can not use data type of interface in EQ comparison
    

    or

    can not use data type of ptr in EQ comparison
    

    if I use pointer.

    Solution I propose that in reflectmath.go/Evaluate* handles these "reference" type with e.g.:

    func EvaluateEqual(left, right reflect.Value) (reflect.Value, error) {
    	if left.Kind() == reflect.Pointer || left.Kind() == reflect.Interface {
    		return EvaluateEqual(left.Elem(), right)
    	}
    
    	if right.Kind() == reflect.Pointer || right.Kind() == reflect.Interface {
    		return EvaluateEqual(left, right.Elem())
    	}
    ....
    }
    

    Would gladly make PR contribution but I would like to open this for discussion first to know what are the implications and if it's aligned with the design.

  • how to use GRULE Playground

    how to use GRULE Playground

    Describe the bug Not Bug

    Hi,noticed the latest editor has merged into master last week. After run Main.go in /editor/cmd, have no idea how to play in the playgroud.

    Hope anyone can share useful demos. Thx

  • Using maps like objects

    Using maps like objects

    Is your feature request related to a problem? Please describe. Noticed that I can visit a JSON Fact as a normal object or a map, but I can't visit a map like a normal object.

    Describe the solution you'd like

        drls := `rule Demo "demo" salience 10 {
        when 
            myMap.name == "foo"
        then
            myMap.name == "bar";
    	Retract("Demo");
        }`
    
        myMap := map[string]string{
            "name": "foo",
        }
        err := dataContext.AddMap("myMap", myMap)
    
    

    Due to the restrictions of the Go, there is no way to set the value of a struct's non-exported filed. If this feature can be supported, I am able to use non-exported fields when defining my DSL.

    Describe alternatives you've considered An alternative solution is define a tag, for example:

    type User struct {
        Name string `grule:"name"`
    }
    
        drls := `rule Demo "demo" salience 10 {
        when 
            user.name == "foo"
        then
            user.name == "bar";
    	Retract("Demo");
        }`
    
        user := User{
            Name: "foo",
        }
        err := dataContext.Add("user", &user)
    
    

    If the tag is specified, the grule engine can use it When calculating the rules.

    Additional context

    type User struct {
        Name string `json:"name"`
    }
    
    {
        "name": "foo"
    }
    

    Most APIs are defined in snake case. So I wish can define the rules in snake case.

  • Loading multiple files not working

    Loading multiple files not working

    Describe the bug

    Loading rules using pkg.NewFileResourceBundle() results in:

    panic: runtime error: invalid memory address or nil pointer dereference
    [signal 0xc0000005 code=0x0 addr=0x40 pc=0x627659]
    
    goroutine 1 [running]:
    github.com/hyperjumptech/grule-rule-engine/engine.(*GruleEngine).ExecuteWithContext(0xc00011de20, {0x801a68, 0xc000022088}, {0x805fb0?, 0xc00015ec00}, 0x0?)   
            C:/Users/tashi/go/pkg/mod/github.com/hyperjumptech/[email protected]/engine/GruleEngine.go:86 +0x59
    github.com/hyperjumptech/grule-rule-engine/engine.(*GruleEngine).Execute(...)
            C:/Users/tashi/go/pkg/mod/github.com/hyperjumptech/[email protected]/engine/GruleEngine.go:52
    main.main()
            C:/Users/tashi/go/src/gitlab.com/raaho/fin-service/main.go:47 +0x2f0
    

    To Reproduce

    Steps to reproduce the behavior:

    1. I have copied the tutorial code and tried to load the rules from an external .grl file. I used pkg.NewFileResourceBundle() to load it.
    2. Instead of running without errors and printing the updated fact, I see the given error.
    3. When I give the absolute path of rules, the code works without errors.

    Expected behavior

    The code compiles without errors and displays updated fact.

    Additional context My directory:

    - main.go
    - rules/
      - test.grl
    
  • Append function on JSON array not reflecting in the DataContext

    Append function on JSON array not reflecting in the DataContext

    Describe the bug Am using JSON Fact and appending values to an array. The array is not getting updated in the datacontext

    JSON Fact:

    {
    	"status": "start",
    	"payloadData": "Whatever data",
    	"logs": ["Hello"]
    }
    

    Rule:

    rule Testing {
    		when 
    			data.status == "start"
    		then 
    			data.status = "stop";
    			data.logs.Append("PERFECT1");
    	}
    

    Expected:

    {
    	"auditlogs": ["Hello", "PERFECT1"],
    	"payloadData": "Whatever data",
    	"status": "stop"
    }
    

    Actual Result:

    {
    	"auditlogs": ["Hello"],
    	"payloadData": "Whatever data",
    	"status": "stop"
    }
    

    To Reproduce Steps to reproduce the behavior:

    package main
    
    import (
    	"encoding/json"
    	"fmt"
    
    	"github.com/hyperjumptech/grule-rule-engine/ast"
    	"github.com/hyperjumptech/grule-rule-engine/builder"
    	"github.com/hyperjumptech/grule-rule-engine/engine"
    	"github.com/hyperjumptech/grule-rule-engine/pkg"
    )
    
    func main() {
    	dataContext := ast.NewDataContext()
    	err := dataContext.AddJSON("data", []byte(`{"status" : "start", "payloadData" : "Whatever data", "logs":["Hello"]}`))
    	if err != nil {
    		fmt.Println(err)
    	}
    
    	rule := `
    	rule Testing {
    		when 
    			data.status == "start"
    		then 
    			data.status = "stop";
    			data.logs.Append("PERFECT1");
    	}`
    
    	lib := ast.NewKnowledgeLibrary()
    	rb := builder.NewRuleBuilder(lib)
    	rb.BuildRuleFromResource("TestJSONBitComplex", "0.0.1", pkg.NewBytesResource([]byte(rule)))
    	kb := lib.NewKnowledgeBaseInstance("TestJSONBitComplex", "0.0.1")
    	eng := &engine.GruleEngine{MaxCycle: 5}
    	err = eng.Execute(dataContext, kb)
    	if err != nil {
    		fmt.Println(err)
    	}
    
    	result := dataContext.Get("data").Value().Interface().(map[string]interface{})
    	b, err := json.Marshal(result)
    	if err != nil {
    		fmt.Println(err)
    	}
    	fmt.Println(string(b))
    }
    

    Expected behavior The result printed should have the appended values in the logs field

Simple trie based auto-completion engine implementation in golang.
Simple trie based auto-completion engine implementation in golang.

Simple auto-complete engine implementation in golang. Quick start $ git clone https://github.com/benbarron/trie-auto-completion-engine $ cd trie-auto-

Jul 12, 2022
Business Process eXecution Engine

Problem: creating and maintaining robust business systems Creating a proof-of-concept business system is relatively easy. Happy execution path, no cat

Oct 28, 2022
User interface engine and widget library for Ebiten
User interface engine and widget library for Ebiten

Ebiten UI A user interface engine and widget library for Ebiten Ebiten UI is an extension to Ebiten that provides an engine to render a complete user

Nov 5, 2022
Vaku is a CLI and API for running path- and folder-based operations on the Vault Key/Value secrets engine.
Vaku is a CLI and API for running path- and folder-based operations on the Vault Key/Value secrets engine.

Vaku Vaku is a CLI and API for running path- and folder-based operations on the Vault Key/Value secrets engine. Vaku extends the existing Vault CLI an

Nov 28, 2022
vkectl is a tool to manage VKE(VolcanoEngine Kubernetes Engine) resources through a CLI

vkectl Introduction vkectl is a tool to manage VKE(VolcanoEngine Kubernetes Engine) resources through a CLI(Command Line Interface). It is written in

Aug 26, 2022
Readline is a pure go(golang) implementation for GNU-Readline kind library
Readline is a pure go(golang) implementation for GNU-Readline kind library

A powerful readline library in Linux macOS Windows Solaris Guide Demo Shortcut Repos using readline Feedback If you have any questions, please submit

Jan 8, 2023
Golang implementation of the research by @jonaslyk and the drafted PoC from @LloydLabs

Doge-SelfDelete Golang implementation of the research by @jonaslyk and the drafted PoC from @LloydLabs Golang 实现的文件自删除,来自@jonaslyk和@LloydLabs etc add

Oct 2, 2022
Golang implementation of Reflective load PE from memory

?? Frog For Automatic Scan ?? Doge For Defense Evasion & Offensive Security Doge-MemX Golang implementation of Reflective load PE from memory Only Sup

Dec 6, 2022
Nano API Implementation in Golang

nanoapi Nano API Implementation in GO TL;DR The idea is to create a very simple

Jan 9, 2022
An implementation of the Nano cryptocurrency protocol in golang

Go Nano An implementation of the Nano protocol written from scratch in Go (golang). About the Project A crypto currency has to be resilient to survive

Dec 7, 2022
Golisp-wtf - A lisp interpreter (still just a parser) implementation in golang. You may yell "What the fuck!?.." when you see the shitty code.

R6RS Scheme Lisp dialect interpreter This is an implementation of a subset of R6RS Scheme Lisp dialect in golang. The work is still in progress. At th

Jan 7, 2022
Doge-AddSSP - Load ssp dll golang implementation

Doge-AddSSP Load ssp dll golang implementation Administrator/System Privilege Us

Nov 9, 2022
golang implementation of Syswhisper2/Syswhisper3

Doge-Whisper golang implementation of Syswhisper2/Syswhisper3 按系统调用地址排序获取System Service Number(SSN)即为sysid以绕过hook, Sorting by System Call Address doge

Dec 10, 2022
An implementation of sed in Go. Just because!

Sed-Go An implementation of sed in Go. Just because! Status Command-Line processing: Done. It accepts '-e', '-f', '-n' and long versions of the same.

Aug 23, 2022
Reference go implementation of globalDCE protocol

globalDCE-go This is the reference implementation of the command line interface of globalDCE coded in the go programming language. This project is sti

Nov 15, 2021
Reference implementation of globaldce protocol coded in go

globaldce This is the reference implementation of the command line interface of globaldce coded in the go programming language. This project is still

Nov 15, 2021
A go language implementation of a CLI, based on input directory files

A go language implementation of a CLI, based on input directory files, using tree output of all file names, based on input database links, dynamic reading of database table information, based on input swagger files to automate the generation of the RESTFUL API.

Aug 23, 2022
An experimental AOT implementation of PHP

Trunk An experimental PHP implementation that transpiles PHP code into Go code.

Dec 31, 2022
A TUI implementation of the popular word quiz wordle!

gordle A TUI implementation of the popular word quiz Wordle! Building Build the cli command: $ go build ./cmd/cli <Empty output on build success> Buil

Dec 21, 2022