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 Go (Golang) programming language. Inspired by the acclaimed JBOSS Drools, and done in a much simpler manner.

Like Drools, Grule has its own DSL or Domain-Specific Language.

Below is an example of Drools's DRL or Drools Rule Language:

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

Grule's GRL is as follows:

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 a Rule Engine

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),

The 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 knowledgebase 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 code, 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's dynamic nature becomes trivial. No need to re-build code or deploy as normal software development does - you only need to roll out sets of rules and apply them to knowledge repository.

Use Cases

The following cases are better solved with a rule-engine:

  1. An expert system that must evaluate facts to provide some sort of real-world conclusion. If not using a RETE-style rule engine, one would code up a cascading set of if/else statements, and the permutations of the combinations of how those might be evaluated would quickly become impossible to manage. A table-based rule engine might suffice, but it is still more brittle against change, and is not terribly easy to code. A system like Grule allows you to describe the rules and facts of your system, releasing you from the need to describe how the rules are evaluated against those facts, and hiding the bulk of that complexity from you.

  2. A rating system. For example, a bank system may want to create a "score" for each customer based on the customer's transaction records (facts). We could see their score change based on how often they interact with the bank, how much money they transfer in and out, how quickly they pay their bills, how much interest they accrue, how much they earn for themselves or for the bank, and so on. A rule engine could be provided by a developer, and the specification of the facts and rules can then be supplied by subject matter experts within the bank's customer analytics department. Decoupling these different teams puts the responsibilities where they should be.

  3. Computer games. Player status, rewards, penalties, damage, scores, and probability systems are many different examples of where rules play a significant part in most computer games. These rules can interact in very complex ways, often times in ways that the developer didn't foresee. Coding these dynamic situations through the use of a scripting language (e.g. Lua) can get quite complex, and a rule engine can help simplify the work tremendously.

  4. Classification systems. This is actually a generalization of the rating system described above. Using a rule engine, we can classify things such as credit eligibility, biochemical identification, risk assessment for insurance products, potential security threats, and many more.

  5. Advice/suggestion system. A "rule" is simply another kind of data, which makes it a prime candidate for definition by another program. This program can be another expert system or artificial intelligence. Rules can be manipulated by other systems in order to deal with new types of facts or newly discovered information about the domain which the ruleset is intending to model.

There are many other use-cases that would benefit from the use of a Rule-Engine. The above cases represent only a small number of the potential ones.

However, it is important to remember that a Rule-Engine not a silver bullet, of course. Many alternatives exist to solve "knowledge" problems in software, and those should be employed where they are most appropriate. One would not employ a rule engine where a simple if / else branch would suffice, for instance.

Theres's something else to note: some rule engine implementations are extremely expensive, yet many businesses gain so much value from them that the cost of running them is easily offset by that value. For even moderately complex use cases, the benefit of a strong rule engine that can decouple teams and tame business complexity seems to be quite clear.

Docs

Documentation page here

To dive into the Tutorial, see the Wiki Docs here on Github.

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

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

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

Weaviate is a cloud-native, modular, real-time vector search engine
Weaviate is a cloud-native, modular, real-time vector search engine

Weaviate is a cloud-native, real-time vector search engine (aka neural search engine or deep search engine). There are modules for specific use cases such as semantic search, plugins to integrate Weaviate in any application of your choice, and a console to visualize your data.

Jan 5, 2023
Self hosted search engine for data leaks and password dumps
Self hosted search engine for data leaks and password dumps

Self hosted search engine for data leaks and password dumps. Upload and parse multiple files, then quickly search through all stored items with the power of Elasticsearch.

Aug 2, 2021
IBus Engine for GoVarnam. An easy way to type Indian languages on GNU/Linux systems.

IBus Engine For GoVarnam An easy way to type Indian languages on GNU/Linux systems. goibus - golang implementation of libibus Thanks to sarim and haun

Feb 10, 2022
A BPMN engine, meant to be embedded in Go applications with minim hurdles, and a pleasant developer experience using it.

A BPMN engine, meant to be embedded in Go applications with minim hurdles, and a pleasant developer experience using it. This approach can increase transparency for non-developers.

Dec 29, 2022
Program to generate ruins using the Numenera Ruin Mapping Engine

Ruin Generator This is my attempt to build a program to generate ruins for Numenera using the rules from the Jade Colossus splatbook. The output only

Nov 7, 2021
An experimental vulkan 3d engine for linux (raspberry 4)

protomatter an experimental vulkan 3d engine for linux (raspberry 4).

Nov 14, 2021
A search engine for XKCD

xkcd_searchtool a search engine for XKCD What is it? This tool can crawling the comic transcripts from XKCD.com Users can search a comic using key wor

Sep 29, 2021
Nune - High-performance numerical engine based on generic tensors

Nune (v0.1) Numerical engine is a library for performing numerical computation i

Nov 9, 2022
Nune-go - High-performance numerical engine based on generic tensors

Nune (v0.1) Numerical engine is a library for performing numerical computation i

Nov 9, 2022
Zinc Search engine. A lightweight alternative to elasticsearch that requires minimal resources, written in Go.
Zinc Search engine. A lightweight alternative to elasticsearch that requires minimal resources, written in Go.

Zinc Search Engine Zinc is a search engine that does full text indexing. It is a lightweight alternative to Elasticsearch and runs using a fraction of

Jan 1, 2023
a quick golang implementation of google pubsub subscriber for testing with the emulator.

gosub a quick golang implementation of google pubsub subscriber for testing with the emulator. it does one thing which is subscribing to a topic and r

Oct 23, 2021
A simple and sussy project is an implementation of SOMMIP Lab 1 written in Golang
A simple and sussy project is an implementation of SOMMIP Lab 1 written in Golang

SOMMIP Lab 1 Isac Arthur Table of Contents About The Project Getting Started Prerequisites Installation Supported commands About The Project This very

Nov 10, 2021
The official golang implementation for Project Anatha.

Project Anatha The official golang implementation for Project Anatha. For instructions on setting up a validator on the Anatha network, view the guide

Nov 25, 2021
An ease to use finit state machine golang implementation.Turn any struct to a fsm with graphviz visualization supported.

go-fsm An ease to use finit state machine golang implementation.Turn any struct to a fsm with graphviz visualization supported. usage import github.co

Dec 26, 2021
klaytnBreak - Official golang implementation of the Klaytn protocol

Klaytn Official golang implementation of the Klaytn protocol. Please visit KlaytnDocs for more details on Klaytn design, node operation guides and app

Jan 13, 2022
Go-wordle - Wordle implementation in GoLang
Go-wordle - Wordle implementation in GoLang

go-wordle A golang implementation of the popular New York Times game Wordle. Usa

Dec 12, 2022
Go-sudoku - Sudoku generator and solver implementation in GoLang

go-sudoku An implementation of Sudoku generators and solvers in GoLang. Usage He

Nov 7, 2022
Swagger 2.0 implementation for go

Swagger 2.0 This package contains a golang implementation of Swagger 2.0 (aka OpenAPI 2.0): it knows how to serialize and deserialize swagger specific

Dec 30, 2022
Go implementation of the Rust `dbg` macro

godbg ?? godbg is an implementation of the Rust2018 builtin debugging macro dbg. The purpose of this package is to provide a better and more effective

Dec 14, 2022