├── .circleci └── config.yml ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── build-test-workflow.yml ├── .gitignore ├── .golangci.yaml ├── .travis.yml ├── ANTLR.md ├── CHANGELOG.md ├── CODE_OF_CONDUCTS.md ├── CONTRIBUTING.md ├── LICENSE-2.0.txt ├── LICENSE.txt ├── Makefile ├── README.md ├── antlr ├── GruleParser.go ├── GruleParserV3Listener.go ├── GruleParserV3_test.go ├── ParserCommon.go ├── Stack.go ├── grulev3.g4 ├── parser │ └── grulev3 │ │ ├── grulev3.interp │ │ ├── grulev3.tokens │ │ ├── grulev3Lexer.interp │ │ ├── grulev3Lexer.tokens │ │ ├── grulev3_base_listener.go │ │ ├── grulev3_base_visitor.go │ │ ├── grulev3_lexer.go │ │ ├── grulev3_listener.go │ │ ├── grulev3_parser.go │ │ └── grulev3_visitor.go ├── sample.grl ├── sample2.grl ├── sample3.grl └── sample4.grl ├── ast ├── ArgumentList.go ├── ArrayMapSelector.go ├── Assignment.go ├── Ast.go ├── BuiltInFunctions.go ├── Constant.go ├── DataContext.go ├── DataContext_test.go ├── Expression.go ├── ExpressionAtom.go ├── FunctionCall.go ├── Grl.go ├── KnowledgeBase.go ├── Literal.go ├── RuleEntry.go ├── Salience.go ├── Serializer.go ├── Serializer_test.go ├── ThenExpression.go ├── ThenExpressionList.go ├── ThenScope.go ├── Variable.go ├── WhenScope.go ├── WhenScope_test.go ├── WorkingMemory.go ├── WorkingMemory_test.go └── unique │ ├── Unique.go │ └── Unique_test.go ├── azure-pipelines.yml ├── builder ├── RuleBuilder.go └── RuleBuilder_test.go ├── dectab └── decision_table.md ├── docs ├── CONTRIBUTING_TRANSLATION.md ├── Documentation.md ├── cn │ ├── About_cn.md │ ├── Benchmarking_cn.md │ ├── Binary_Rule_File_cn.md │ ├── FAQ_cn.md │ ├── Function_cn.md │ ├── GRL_JSON_cn.md │ ├── GRL_Literals_cn.md │ ├── GRL_cn.md │ ├── JSON_Fact_cn.md │ ├── MatchingRules_cn.md │ ├── RETE_cn.md │ ├── RuleEngine_cn.md │ └── Tutorial_cn.md ├── de │ ├── About_de.md │ ├── Benchmarking_de.md │ ├── Binary_Rule_File_de.md │ ├── FAQ_de.md │ ├── Function_de.md │ ├── GRL_JSON_de.md │ ├── GRL_Literals_de.md │ ├── GRL_de.md │ ├── JSON_Fact_de.md │ ├── MatchingRules_de.md │ ├── RETE_de.md │ ├── RuleEngine_de.md │ └── Tutorial_de.md ├── en │ ├── About_en.md │ ├── Benchmarking_en.md │ ├── Binary_Rule_File_en.md │ ├── FAQ_en.md │ ├── Function_en.md │ ├── GRL_JSON_en.md │ ├── GRL_Literals_en.md │ ├── GRL_en.md │ ├── JSON_Fact_en.md │ ├── MatchingRules_en.md │ ├── RETE_en.md │ ├── RuleEngine_en.md │ └── Tutorial_en.md ├── id │ ├── About_id.md │ ├── Benchmarking_id.md │ ├── Binary_Rule_File_id.md │ ├── FAQ_id.md │ ├── Function_id.md │ ├── GRL_JSON_id.md │ ├── GRL_Literals_id.md │ ├── GRL_id.md │ ├── JSON_Fact_id.md │ ├── MatchingRules_id.md │ ├── RETE_id.md │ ├── RuleEngine_id.md │ └── Tutorial_id.md ├── pl │ ├── About_pl.md │ ├── Benchmarking_pl.md │ ├── Binary_Rule_File_pl.md │ ├── FAQ_pl.md │ ├── Function_pl.md │ ├── GRL_JSON_pl.md │ ├── GRL_Literals_pl.md │ ├── GRL_pl.md │ ├── JSON_Fact_pl.md │ ├── MatchingRules_pl.md │ ├── RETE_pl.md │ ├── RuleEngine_pl.md │ └── Tutorial_pl.md └── template.html ├── editor ├── EvaluationRoute.go ├── Server.go ├── StaticRoute.go ├── cmd │ └── Main.go ├── mime │ └── Mime.go └── statics │ ├── css │ ├── bootstrap-grid.css │ ├── bootstrap-grid.css.map │ ├── bootstrap-grid.min.css │ ├── bootstrap-grid.min.css.map │ ├── bootstrap-grid.rtl.css │ ├── bootstrap-grid.rtl.css.map │ ├── bootstrap-grid.rtl.min.css │ ├── bootstrap-grid.rtl.min.css.map │ ├── bootstrap-reboot.css │ ├── bootstrap-reboot.css.map │ ├── bootstrap-reboot.min.css │ ├── bootstrap-reboot.min.css.map │ ├── bootstrap-reboot.rtl.css │ ├── bootstrap-reboot.rtl.css.map │ ├── bootstrap-reboot.rtl.min.css │ ├── bootstrap-reboot.rtl.min.css.map │ ├── bootstrap-utilities.css │ ├── bootstrap-utilities.css.map │ ├── bootstrap-utilities.min.css │ ├── bootstrap-utilities.min.css.map │ ├── bootstrap-utilities.rtl.css │ ├── bootstrap-utilities.rtl.css.map │ ├── bootstrap-utilities.rtl.min.css │ ├── bootstrap-utilities.rtl.min.css.map │ ├── bootstrap.css │ ├── bootstrap.css.map │ ├── bootstrap.min.css │ ├── bootstrap.min.css.map │ ├── bootstrap.rtl.css │ ├── bootstrap.rtl.css.map │ ├── bootstrap.rtl.min.css │ ├── bootstrap.rtl.min.css.map │ └── index.css │ ├── index.html │ └── js │ ├── bootstrap.bundle.js │ ├── bootstrap.bundle.js.map │ ├── bootstrap.bundle.min.js │ ├── bootstrap.bundle.min.js.map │ ├── bootstrap.esm.js │ ├── bootstrap.esm.js.map │ ├── bootstrap.esm.min.js │ ├── bootstrap.esm.min.js.map │ ├── bootstrap.js │ ├── bootstrap.js.map │ ├── bootstrap.min.js │ ├── bootstrap.min.js.map │ ├── index.js │ ├── jquery-3.6.1.min.js │ ├── jquery.mark.js │ └── popper.min.js ├── engine ├── GruleEngine.go ├── GruleEngineListener.go └── GruleEngine_test.go ├── examples ├── AgeCheckSample_test.go ├── ArraySliceMap_test.go ├── CallingFactFunctionExample_test.go ├── CallingLogExample_test.go ├── CashFlowRule.grl ├── CloneTableIssue_test.go ├── Concurrency_test.go ├── EvaluateMissingDataContext_test.go ├── FunctionCallChaining_test.go ├── GrlParseErrorDetection_test.go ├── InterfaceDataContext_test.go ├── Issue108_test.go ├── Issue328_test.go ├── Issue4_test.go ├── Issue5_test.go ├── Issue7_test.go ├── ItemArrayExample_test.go ├── JSONDataContext_test.go ├── KnowledgeBaseInstancePanic_test.go ├── MatchingRules_test.go ├── MemoizeSliceFunction_test.go ├── Multipe_knowledgebases_test.go ├── NegationSymbolExample_test.go ├── NilKnowledgeBasePanic_test.go ├── NumberExponentExample_test.go ├── PurcasingTaxSample_test.go ├── RemoveRuleEntry_test.go ├── SalienceExample_test.go ├── Serialization_test.go ├── StringFunctions_test.go ├── TutorialExample_test.go ├── UnicodeRule_test.go └── benchmark │ ├── 1000_rules.grl │ ├── 100_rules.grl │ ├── DummyRuleMaker.go │ ├── ExecRules_benchmark_test.go │ ├── GRBPerformance_test.go │ ├── LoadRules_benchmark_test.go │ └── words.txt ├── go.mod ├── go.sum ├── gopher-grule.png ├── logger ├── Logger.go ├── Logrus.go └── Zap.go ├── model ├── DataAccessLayer.go ├── DataAccessLayer_test.go ├── GoDataAccessLayer.go ├── GoDataAccessLayer_test.go ├── JsonDataAccessLayer.go ├── JsonDataAccessLayer_test.go ├── TimeFormat.go └── TimeFormat_test.go └── pkg ├── CloneTool.go ├── JsonResource.go ├── JsonResource_test.go ├── embeddedResource.go ├── embeddedResource_test.go ├── errorReporter.go ├── gitresource.go ├── gitresource_1.10.go ├── jsontool ├── JsonDom.go └── JsonDom_test.go ├── reflectmath.go ├── reflectmath_test.go ├── reflectools.go ├── reflectools_test.go ├── resource.go ├── resource_test.go └── test ├── subfold1 ├── GrlFile11.grl └── GrlFile12.grl └── subfold2 ├── GrlFile21.grl ├── GrlFile22.grl └── subfold21 ├── GrlFile211.grl └── GrlFile212.grl /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Golang CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-go/ for more details 4 | version: 2 5 | jobs: 6 | build: 7 | docker: 8 | # specify the version 9 | - image: circleci/golang:1.13 10 | 11 | # Specify service dependencies here if necessary 12 | # CircleCI maintains a library of pre-built images 13 | # documented at https://circleci.com/docs/2.0/circleci-images/ 14 | # - image: circleci/postgres:9.4 15 | 16 | #### TEMPLATE_NOTE: go expects specific checkout path representing url 17 | #### expecting it in the form of 18 | #### /go/src/github.com/circleci/go-tool 19 | #### /go/src/bitbucket.org/circleci/go-tool 20 | working_directory: /go/src/github.com/hyperjumptech/grule-rule-engine 21 | steps: 22 | - checkout 23 | 24 | # specify any bash command here prefixed with `run: ` 25 | - run: go get -v -t -d ./... 26 | - run: go get -u golang.org/x/lint/golint 27 | - run: go install github.com/newm4n/goornogo 28 | - run: make test-short -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. I create code for this and that 16 | 2. With a test for this and that 17 | 3. Instead of seeng this 18 | 4. I see that 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Additional context** 24 | Add any other context about the problem here. 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/build-test-workflow.yml: -------------------------------------------------------------------------------- 1 | name: Grule-Rule-Engine 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/setup-go@v4 13 | with: 14 | go-version: '1.20' 15 | - uses: actions/checkout@v3 16 | - name: Fetching dependencies 17 | run : go get -v -t -d ./... 18 | - name: Execute test 19 | run : make test-short 20 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.13.x 5 | 6 | script: 7 | - go get -u golang.org/x/lint/golint 8 | - go install github.com/newm4n/goornogo 9 | - make test-short -------------------------------------------------------------------------------- /ANTLR.md: -------------------------------------------------------------------------------- 1 | # ANTLR 2 | 3 | *grule-rule-engine* is heavily using ANTLR4 lexer/parser to parse GRL rule. The parser 4 | `.go` files were generated by ANTLR4 tools program. 5 | 6 | ## Download ANTLR4 tools 7 | 8 | The latest version can be downloaded [here (antlr-4.9.2-complete.jar)](https://www.antlr.org/download/antlr-4.9.2-complete.jar) 9 | Or you can always go to [ANTLR4 download page](https://www.antlr.org/download.html) 10 | 11 | Download the `.jar` file and place it somewhere a directory. 12 | 13 | Yes, you need Java for this. You can download java from [Oracle's Java Download Page](https://www.oracle.com/java/technologies/javase-downloads.html). 14 | Install java and you're ready to work with ANTLR4. 15 | 16 | ## Making ANTLR alias. 17 | 18 | ```bash 19 | alias antlr='java -jar /path/to/downloaded/antlr-4.9.2-complete.jar' 20 | ``` 21 | 22 | ## Executing ANTLR 23 | 24 | To build ANTLR4's golang listener or visitor, go to the directory where you 25 | have your antlr4 grammar `.g4` file. And then execute the following command. 26 | 27 | ```bash 28 | antlr -Dlanguage=Go -o parser -package grulev3 -lib . -listener -visitor grulev3.g4 29 | ``` 30 | 31 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCTS.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | 3 | ### Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ### Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ### Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ### Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ### Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at `oss@hyperjump.tech`. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ### Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue, 4 | email, or any other method with the owners of this repository before making a change. 5 | 6 | Please note we have a code of conduct, please follow it in all your interactions with the project. 7 | 8 | ## Fork Process 9 | 10 | 1. Ensure that you've installed the Golang (minimum 1.13) in your system. 11 | 2. For this project into your own Github account. 12 | 3. Clone the `grule-rule-engine` forked repository on your account. 13 | 4. Enter the cloned directory. 14 | 5. Apply new "upstream" to original `hyperjumptech/grule-rule-engine` git 15 | 4. Now you can work on your account 16 | 5. Remember to pull from your upstream often. `git pull upstream master` 17 | 18 | ## Pull Request Process 19 | 20 | 1. Make sure you always have the most recent update from your upstream. `git pull upstream master` 21 | 2. Resolve all conflict, if any. 22 | 3. Make sure `make test` always successful (you wont be able to create pull request if this fail, circle-ci, travis-ci and azure-devops will make sure of this.) 23 | 4. Push your code to your project's master repository. 24 | 5. Create PullRequest. 25 | * Go to `github.com/hyperjumptech/grule-rule-engine` 26 | * Select `Pull Request` tab 27 | * Click "New pull request" button 28 | * Click "compare across fork" 29 | * Change the source head repository from your fork and target is `hyperjumptech/grule-rule-engine` 30 | * Hit the "Create pull request" button 31 | * Fill in all necessary information to help us understand about your pull request. 32 | 33 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2019 hyperjump.tech 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GO111MODULE=on 2 | 3 | .PHONY: all test test-short fix-antlr4-bug build 4 | 5 | fix-antlr4-bug: 6 | sed -i.origin.bak "s/1[ ]*< 0 { 45 | theRune, multibyte, ss, err := strconv.UnquoteChar(theStr, quote) 46 | if err != nil { 47 | return "", err 48 | } 49 | theStr = ss 50 | if theRune < utf8.RuneSelf || !multibyte { 51 | buf = append(buf, byte(theRune)) 52 | } else { 53 | n := utf8.EncodeRune(runeTmp[:], theRune) 54 | buf = append(buf, runeTmp[:n]...) 55 | } 56 | } 57 | 58 | return string(buf), nil 59 | } 60 | 61 | func contains(s string, c byte) bool { 62 | return strings.IndexByte(s, c) != -1 63 | } 64 | -------------------------------------------------------------------------------- /antlr/Stack.go: -------------------------------------------------------------------------------- 1 | // Copyright hyperjumptech/grule-rule-engine Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package antlr 16 | 17 | type ( 18 | stack struct { 19 | top *node 20 | length int 21 | } 22 | node struct { 23 | value interface{} 24 | prev *node 25 | } 26 | ) 27 | 28 | // Create a new stack 29 | func newStack() *stack { 30 | 31 | return &stack{nil, 0} 32 | } 33 | 34 | // Len return the number of items in the stack 35 | func (s *stack) Len() int { 36 | 37 | return s.length 38 | } 39 | 40 | // Peek views the top item on the stack 41 | func (s *stack) Peek() interface{} { 42 | if s.length == 0 { 43 | 44 | return nil 45 | } 46 | 47 | return s.top.value 48 | } 49 | 50 | // Pop the top item of the stack and return it 51 | func (s *stack) Pop() interface{} { 52 | if s.length == 0 { 53 | 54 | return nil 55 | } 56 | 57 | n := s.top 58 | s.top = n.prev 59 | s.length-- 60 | 61 | return n.value 62 | } 63 | 64 | // Push a value onto the top of the stack 65 | func (s *stack) Push(value interface{}) { 66 | n := &node{value, s.top} 67 | s.top = n 68 | s.length++ 69 | } 70 | -------------------------------------------------------------------------------- /antlr/parser/grulev3/grulev3.tokens: -------------------------------------------------------------------------------- 1 | T__0=1 2 | PLUS=2 3 | MINUS=3 4 | DIV=4 5 | MUL=5 6 | MOD=6 7 | DOT=7 8 | SEMICOLON=8 9 | LR_BRACE=9 10 | RR_BRACE=10 11 | LR_BRACKET=11 12 | RR_BRACKET=12 13 | LS_BRACKET=13 14 | RS_BRACKET=14 15 | RULE=15 16 | WHEN=16 17 | THEN=17 18 | AND=18 19 | OR=19 20 | TRUE=20 21 | FALSE=21 22 | NIL_LITERAL=22 23 | NEGATION=23 24 | SALIENCE=24 25 | EQUALS=25 26 | ASSIGN=26 27 | PLUS_ASIGN=27 28 | MINUS_ASIGN=28 29 | DIV_ASIGN=29 30 | MUL_ASIGN=30 31 | GT=31 32 | LT=32 33 | GTE=33 34 | LTE=34 35 | NOTEQUALS=35 36 | BITAND=36 37 | BITOR=37 38 | SIMPLENAME=38 39 | DQUOTA_STRING=39 40 | SQUOTA_STRING=40 41 | DECIMAL_FLOAT_LIT=41 42 | DECIMAL_EXPONENT=42 43 | HEX_FLOAT_LIT=43 44 | HEX_EXPONENT=44 45 | DEC_LIT=45 46 | HEX_LIT=46 47 | OCT_LIT=47 48 | SPACE=48 49 | COMMENT=49 50 | LINE_COMMENT=50 51 | ','=1 52 | '+'=2 53 | '-'=3 54 | '/'=4 55 | '*'=5 56 | '%'=6 57 | '.'=7 58 | ';'=8 59 | '{'=9 60 | '}'=10 61 | '('=11 62 | ')'=12 63 | '['=13 64 | ']'=14 65 | '&&'=18 66 | '||'=19 67 | '!'=23 68 | '=='=25 69 | '='=26 70 | '+='=27 71 | '-='=28 72 | '/='=29 73 | '*='=30 74 | '>'=31 75 | '<'=32 76 | '>='=33 77 | '<='=34 78 | '!='=35 79 | '&'=36 80 | '|'=37 81 | -------------------------------------------------------------------------------- /antlr/parser/grulev3/grulev3Lexer.tokens: -------------------------------------------------------------------------------- 1 | T__0=1 2 | PLUS=2 3 | MINUS=3 4 | DIV=4 5 | MUL=5 6 | MOD=6 7 | DOT=7 8 | SEMICOLON=8 9 | LR_BRACE=9 10 | RR_BRACE=10 11 | LR_BRACKET=11 12 | RR_BRACKET=12 13 | LS_BRACKET=13 14 | RS_BRACKET=14 15 | RULE=15 16 | WHEN=16 17 | THEN=17 18 | AND=18 19 | OR=19 20 | TRUE=20 21 | FALSE=21 22 | NIL_LITERAL=22 23 | NEGATION=23 24 | SALIENCE=24 25 | EQUALS=25 26 | ASSIGN=26 27 | PLUS_ASIGN=27 28 | MINUS_ASIGN=28 29 | DIV_ASIGN=29 30 | MUL_ASIGN=30 31 | GT=31 32 | LT=32 33 | GTE=33 34 | LTE=34 35 | NOTEQUALS=35 36 | BITAND=36 37 | BITOR=37 38 | SIMPLENAME=38 39 | DQUOTA_STRING=39 40 | SQUOTA_STRING=40 41 | DECIMAL_FLOAT_LIT=41 42 | DECIMAL_EXPONENT=42 43 | HEX_FLOAT_LIT=43 44 | HEX_EXPONENT=44 45 | DEC_LIT=45 46 | HEX_LIT=46 47 | OCT_LIT=47 48 | SPACE=48 49 | COMMENT=49 50 | LINE_COMMENT=50 51 | ','=1 52 | '+'=2 53 | '-'=3 54 | '/'=4 55 | '*'=5 56 | '%'=6 57 | '.'=7 58 | ';'=8 59 | '{'=9 60 | '}'=10 61 | '('=11 62 | ')'=12 63 | '['=13 64 | ']'=14 65 | '&&'=18 66 | '||'=19 67 | '!'=23 68 | '=='=25 69 | '='=26 70 | '+='=27 71 | '-='=28 72 | '/='=29 73 | '*='=30 74 | '>'=31 75 | '<'=32 76 | '>='=33 77 | '<='=34 78 | '!='=35 79 | '&'=36 80 | '|'=37 81 | -------------------------------------------------------------------------------- /antlr/sample.grl: -------------------------------------------------------------------------------- 1 | // This is a comment 2 | // And this 3 | 4 | /* And also this */ 5 | /* 6 | As well as this 7 | */ 8 | 9 | rule RuleOne "Some rule description." salience 10 { 10 | when 11 | (someContext.attributeA.attributeA == 123 && 12 | someContext.attributeA.attributeB > 123 || 13 | someContext.attributeA.attributeB < 123 && 14 | someContext.attributeA.attributeB >= 123 && 15 | (someContext.attributeA.attributeB <= 123 && someContext.attributeA.attributeB != 123) && 16 | someContext.attributeA.attributeB > 123) || 17 | someContext.subContext.methodAbc( 23 + someContext.subContext.another() ) 18 | || 19 | someContext.attributeA.attributeA == 123.32 20 | || 21 | someContext.attributeA.attributeS == "abcdefg" 22 | || 23 | trim(someContext.attributeA.attributeS) == "abcdefg" 24 | || 25 | someContext.date.value < now( 12 - 32) 26 | then 27 | otherContext.property.attr1 = 12; // comment here 28 | otherContext.property.attr1 = otherContext.property.attr1-12; 29 | /* 30 | another comment 31 | */ 32 | otherContext.property.attr1 = func(otherContext.property.attr1-12) / 45; 33 | } 34 | 35 | 36 | rule RuleTwo "Some rule description." { 37 | when 38 | (someContext.attributeA.attributeA == 123 && 39 | someContext.attributeA.attributeB > 123 || 40 | someContext.attributeA.attributeB < 123 && 41 | someContext.attributeA.attributeB >= 123 && 42 | (someContext.attributeA.attributeB <= 123 && someContext.attributeA.attributeB != 123) && 43 | someContext.attributeA.attributeB > 123) 44 | || 45 | someContext.attributeA.attributeA == 123.32 46 | || 47 | someContext.attributeA.attributeS == "abcdefg" 48 | || 49 | trim(someContext.attributeA.attributeS) == "abcdefg" 50 | || 51 | someContext.date.value < now() 52 | then 53 | otherContext.property.attr1 = 12; 54 | otherContext.property.attr1 = otherContext.property.attr1-12; 55 | // commented code 56 | // otherContext.property.attr1 = func(otherContext.property.attr1-12) / 45; 57 | log("this is to be logged"); 58 | } -------------------------------------------------------------------------------- /antlr/sample2.grl: -------------------------------------------------------------------------------- 1 | rule OperatorPrecedence "Test Operator Precedence" { 2 | when 3 | 1 + 2 + 3 * 5 == 18 4 | then 5 | Log("Hurrah"); 6 | Retract("OperatorPrecedence"); 7 | } 8 | 9 | rule TaxingLuxuryItems "When its a luxury Item, you tax them 15 percent." salience 10 { 10 | when 11 | Purchase.IgnoredPurchase == false && Purchase.Tax == 0 && Purchase.ItemType == "LUXURY" 12 | then 13 | Purchase.Tax = Purchase.Price + (Purchase.Price * 0.15); 14 | } 15 | 16 | rule TaxingNormalItems "When its a Normal Item, you tax them 10 percent." salience 8 { 17 | when 18 | Purchase.IgnoredPurchase == false && Purchase.Tax == 0 && Purchase.ItemType == "NORMAL" 19 | then 20 | Purchase.Tax = Purchase.Price + (Purchase.Price * 0.1); 21 | } 22 | 23 | rule TaxingOtherTypeItems "When its not Normal or Luxury Item, you tax them 20 percent." salience 7 { 24 | when 25 | Purchase.IgnoredPurchase == false && Purchase.Tax == 0 && Purchase.ItemType != "NORMAL" && Purchase.ItemType != "LUXURY" 26 | then 27 | Purchase.Tax = Purchase.Price + (Purchase.Price * 0.2); 28 | } 29 | 30 | rule CalculatePriceAfterTax "When tax is calculated, time to calculate price after tax" { 31 | when 32 | Purchase.Tax > 0 && Purchase.PriceAfterTax == 0 33 | then 34 | Purchase.PriceAfterTax = Purchase.Price + Purchase.Tax; 35 | } 36 | 37 | rule SumUpPurchase "When price after tax calculated, sum it up" { 38 | when 39 | Purchase.PriceAfterTax > 0 40 | then 41 | CashFlow.PurchaseCount = PurchaseCount + 1; 42 | CashFlow.TotalPurchases = CashFlow.TotalPurchases + Purchase.Price; 43 | CashFlow.TotalTax = CashFlow.TotalTax + Purchase.Tax; 44 | CashFlow.PurchasesAfterTax = CashFlow.PurchasesAfterTax + Purchase.PriceAfterTax; 45 | Retract("SumUpPurchase"); 46 | } 47 | 48 | rule OnlyCalculatePurchaseInYear2019 "All other purchase dated not in 2019, we should ignore" salience 100 { 49 | when 50 | Purchase.IgnoredPurchase == false && Purchase.PurchaseDate.Year() != 2019 && Purchase.PriceAfterTax == 0 51 | then 52 | Purchase.IgnoredPurchase = true; 53 | Retract("OnlyCalculatePurchaseInYear2019"); 54 | } 55 | 56 | -------------------------------------------------------------------------------- /antlr/sample3.grl: -------------------------------------------------------------------------------- 1 | rule Simple "Simple variables" { 2 | when 3 | true && 4 | variable == true && 5 | class.variable == true && 6 | class.member.variable == true 7 | then 8 | funcInvoke(); 9 | variable.funcInvoke(); 10 | class.variable.funcInvoke(); 11 | class.member.variable.funcInvoke(); 12 | class.member.variable = class.member.variable.funcInvoke(); 13 | } -------------------------------------------------------------------------------- /antlr/sample4.grl: -------------------------------------------------------------------------------- 1 | rule Simple "Simple variables" { 2 | when 3 | true && 4 | variable == true && 5 | class.variable == true && 6 | class.member.real == 6.67428e-11 && 7 | class.member.variable == true || 8 | var[12].function(chain).func() == function("asfe")[asef] 9 | then 10 | funcInvoke(); 11 | variable.funcInvoke(); 12 | class.variable.funcInvoke(); 13 | class.member.variable.funcInvoke(); 14 | class.member.variable = class.member.variable.funcInvoke(); 15 | var["toassign"] = var[12].function(chain).func(); 16 | abc.function("asfe").funcchain(); 17 | } -------------------------------------------------------------------------------- /ast/Ast.go: -------------------------------------------------------------------------------- 1 | // Copyright hyperjumptech/grule-rule-engine Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ast 16 | 17 | import ( 18 | "github.com/hyperjumptech/grule-rule-engine/logger" 19 | "github.com/sirupsen/logrus" 20 | "go.uber.org/zap" 21 | ) 22 | 23 | const ( 24 | // ARGUMENTLIST signature for argument list snapshot 25 | ARGUMENTLIST = "AL" 26 | // MAPARRAYSELECTOR signature for map array snapshot 27 | MAPARRAYSELECTOR = "MAS" 28 | // ASSIGMENT signature for assignment snapshot 29 | ASSIGMENT = "AS" 30 | // CONSTANT signature for constant snapshot 31 | CONSTANT = "C" 32 | // EXPRESSION signature for expression snapshot 33 | EXPRESSION = "E" 34 | // EXPRESSIONATOM signature for expression atom snapshot 35 | EXPRESSIONATOM = "A" 36 | // FUNCTIONCALL signature for function call snapshot 37 | FUNCTIONCALL = "F" 38 | // RULEENTRY signature for rule entry snapshot 39 | RULEENTRY = "R" 40 | // THENEXPRESSION signature for then expression snapshot 41 | THENEXPRESSION = "TE" 42 | // THENEXPRESSIONLIST signature for then expression list snapshot 43 | THENEXPRESSIONLIST = "TEL" 44 | // THENSCOPE signature for then scope snapshot 45 | THENSCOPE = "TS" 46 | // WHENSCOPE signature for when scope snapshot 47 | WHENSCOPE = "WS" 48 | // VARIABLE signature for variable snapshot 49 | VARIABLE = "V" 50 | ) 51 | 52 | var ( 53 | // astLogFields default fields for grule 54 | astLogFields = logger.Fields{ 55 | "package": "ast", 56 | } 57 | 58 | // AstLog is a logger instance twith default fields for grule 59 | AstLog = logger.Log.WithFields(astLogFields) 60 | ) 61 | 62 | // SetLogger changes default logger on external 63 | func SetLogger(log interface{}) { 64 | var entry logger.LogEntry 65 | 66 | switch log.(type) { 67 | case *zap.Logger: 68 | log, ok := log.(*zap.Logger) 69 | if !ok { 70 | 71 | return 72 | } 73 | entry = logger.NewZap(log) 74 | case *logrus.Logger: 75 | log, ok := log.(*logrus.Logger) 76 | if !ok { 77 | 78 | return 79 | } 80 | entry = logger.NewLogrus(log) 81 | default: 82 | 83 | return 84 | } 85 | 86 | AstLog = entry.WithFields(astLogFields) 87 | GrlLogger = entry.WithFields(grlLoggerFields) 88 | } 89 | 90 | // Node defines interface to implement by all AST node models 91 | type Node interface { 92 | GetAstID() string 93 | GetGrlText() string 94 | GetSnapshot() string 95 | SetGrlText(grlText string) 96 | MakeCatalog(cat *Catalog) 97 | } 98 | -------------------------------------------------------------------------------- /ast/DataContext_test.go: -------------------------------------------------------------------------------- 1 | // Copyright hyperjumptech/grule-rule-engine Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ast 16 | 17 | import ( 18 | "fmt" 19 | ) 20 | 21 | type TestAStruct struct { 22 | BStruct *TestBStruct 23 | } 24 | 25 | type TestBStruct struct { 26 | CStruct *TestCStruct 27 | } 28 | 29 | type TestCStruct struct { 30 | Str string 31 | It int 32 | } 33 | 34 | func (tcs *TestCStruct) EchoMethod(s string) { 35 | fmt.Println(s) 36 | } 37 | 38 | func (tcs *TestCStruct) EchoVariad(ss ...string) int { 39 | for _, s := range ss { 40 | fmt.Println(s) 41 | } 42 | 43 | return len(ss) 44 | } 45 | -------------------------------------------------------------------------------- /ast/Grl.go: -------------------------------------------------------------------------------- 1 | // Copyright hyperjumptech/grule-rule-engine Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ast 16 | 17 | import "fmt" 18 | 19 | // NewGrl creates new GRL instance 20 | func NewGrl() *Grl { 21 | 22 | return &Grl{ 23 | RuleEntries: make(map[string]*RuleEntry, 0), 24 | } 25 | } 26 | 27 | // Grl will contains multiple RuleEntries 28 | type Grl struct { 29 | RuleEntries map[string]*RuleEntry 30 | } 31 | 32 | // GrlReceiver is interface for objects that should hold a GRL, will be called by ANTLR walker. 33 | type GrlReceiver interface { 34 | AcceptGrl(grl *Grl) error 35 | } 36 | 37 | // ReceiveRuleEntry will make this GRL to accept rule entries created by ANTLR walker 38 | func (g *Grl) ReceiveRuleEntry(entry *RuleEntry) error { 39 | if g.RuleEntries == nil { 40 | g.RuleEntries = make(map[string]*RuleEntry) 41 | } 42 | if _, ok := g.RuleEntries[entry.RuleName]; ok { 43 | 44 | return fmt.Errorf("duplicate rule entry %s", entry.RuleName) 45 | } 46 | g.RuleEntries[entry.RuleName] = entry 47 | 48 | return nil 49 | } 50 | -------------------------------------------------------------------------------- /ast/Literal.go: -------------------------------------------------------------------------------- 1 | // Copyright hyperjumptech/grule-rule-engine Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ast 16 | 17 | // IntegerLiteral will hold IntegerLiteral constant AST data 18 | type IntegerLiteral struct { 19 | Integer int64 20 | } 21 | 22 | // StringLiteral will hold StringLiteral constant AST data 23 | type StringLiteral struct { 24 | String string 25 | } 26 | 27 | // FloatLiteral will hold FloatLiteral constant AST data 28 | type FloatLiteral struct { 29 | Float float64 30 | } 31 | 32 | // BooleanLiteral will hold BooleanLiteral constant AST data 33 | type BooleanLiteral struct { 34 | Boolean bool 35 | } 36 | 37 | // IntegerLiteralReceiver should be implemented by AST graph node to receive a IntegerLiteral AST graph node 38 | type IntegerLiteralReceiver interface { 39 | AcceptIntegerLiteral(fun *IntegerLiteral) 40 | } 41 | 42 | // StringLiteralReceiver should be implemented by AST graph node to receive a StringLiteral AST graph node 43 | type StringLiteralReceiver interface { 44 | AcceptStringLiteral(fun *StringLiteral) 45 | } 46 | 47 | // FloatLiteralReceiver should be implemented by AST graph node to receive a FloatLiteral AST graph node 48 | type FloatLiteralReceiver interface { 49 | AcceptFloatLiteral(fun *FloatLiteral) 50 | } 51 | 52 | // BooleanLiteralReceiver should be implemented by AST graph node to receive a BooleanLiteral AST graph node 53 | type BooleanLiteralReceiver interface { 54 | AcceptBooleanLiteral(fun *BooleanLiteral) 55 | } 56 | -------------------------------------------------------------------------------- /ast/Salience.go: -------------------------------------------------------------------------------- 1 | // Copyright hyperjumptech/grule-rule-engine Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ast 16 | 17 | // NewSalience create new Salience AST object 18 | func NewSalience(val int) *Salience { 19 | 20 | return &Salience{ 21 | SalienceValue: val, 22 | } 23 | } 24 | 25 | // Salience is a simple AST object that stores salience 26 | type Salience struct { 27 | SalienceValue int 28 | } 29 | 30 | // SalienceReceiver must be implemented by any AST object that stores salience 31 | type SalienceReceiver interface { 32 | AcceptSalience(salience *Salience) error 33 | } 34 | 35 | // AcceptIntegerLiteral accept the assigned integer 36 | func (sal *Salience) AcceptIntegerLiteral(lit *IntegerLiteral) { 37 | sal.SalienceValue = int(lit.Integer) 38 | } 39 | -------------------------------------------------------------------------------- /ast/ThenScope.go: -------------------------------------------------------------------------------- 1 | // Copyright hyperjumptech/grule-rule-engine Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ast 16 | 17 | import ( 18 | "bytes" 19 | "github.com/hyperjumptech/grule-rule-engine/ast/unique" 20 | "github.com/hyperjumptech/grule-rule-engine/pkg" 21 | ) 22 | 23 | // NewThenScope will create new instance of ThenScope 24 | func NewThenScope() *ThenScope { 25 | 26 | return &ThenScope{ 27 | AstID: unique.NewID(), 28 | } 29 | } 30 | 31 | // ThenScope AST graph node 32 | type ThenScope struct { 33 | AstID string 34 | GrlText string 35 | 36 | ThenExpressionList *ThenExpressionList 37 | } 38 | 39 | // MakeCatalog create a catalog entry for this AST Node 40 | func (e *ThenScope) MakeCatalog(cat *Catalog) { 41 | meta := &ThenScopeMeta{ 42 | NodeMeta: NodeMeta{ 43 | AstID: e.AstID, 44 | GrlText: e.GrlText, 45 | Snapshot: e.GetSnapshot(), 46 | }, 47 | } 48 | if cat.AddMeta(e.AstID, meta) { 49 | if e.ThenExpressionList != nil { 50 | meta.ThenExpressionListID = e.ThenExpressionList.AstID 51 | e.ThenExpressionList.MakeCatalog(cat) 52 | } 53 | } 54 | } 55 | 56 | // ThenScopeReceiver must be implemented by any AST object that will hold a ThenScope 57 | type ThenScopeReceiver interface { 58 | AcceptThenScope(thenScope *ThenScope) error 59 | } 60 | 61 | // Clone will clone this ThenScope. The new clone will have an identical structure 62 | func (e *ThenScope) Clone(cloneTable *pkg.CloneTable) *ThenScope { 63 | clone := &ThenScope{ 64 | AstID: unique.NewID(), 65 | GrlText: e.GrlText, 66 | } 67 | 68 | if e.ThenExpressionList != nil { 69 | if cloneTable.IsCloned(e.ThenExpressionList.AstID) { 70 | clone.ThenExpressionList = cloneTable.Records[e.ThenExpressionList.AstID].CloneInstance.(*ThenExpressionList) 71 | } else { 72 | cloned := e.ThenExpressionList.Clone(cloneTable) 73 | clone.ThenExpressionList = cloned 74 | cloneTable.MarkCloned(e.ThenExpressionList.AstID, cloned.AstID, e.ThenExpressionList, cloned) 75 | } 76 | } 77 | 78 | return clone 79 | } 80 | 81 | // AcceptThenExpressionList will accept ThenExpressionList graph into this ThenScope 82 | func (e *ThenScope) AcceptThenExpressionList(list *ThenExpressionList) error { 83 | e.ThenExpressionList = list 84 | 85 | return nil 86 | } 87 | 88 | // GetAstID get the UUID asigned for this AST graph node 89 | func (e *ThenScope) GetAstID() string { 90 | 91 | return e.AstID 92 | } 93 | 94 | // GetGrlText get the expression syntax related to this graph when it wast constructed 95 | func (e *ThenScope) GetGrlText() string { 96 | 97 | return e.GrlText 98 | } 99 | 100 | // GetSnapshot will create a structure signature or AST graph 101 | func (e *ThenScope) GetSnapshot() string { 102 | var buff bytes.Buffer 103 | buff.WriteString(THENSCOPE) 104 | buff.WriteString("(") 105 | if e.ThenExpressionList != nil { 106 | buff.WriteString(e.ThenExpressionList.GetSnapshot()) 107 | } 108 | buff.WriteString(")") 109 | 110 | return buff.String() 111 | } 112 | 113 | // SetGrlText set the expression syntax related to this graph when it was constructed. Only ANTLR4 listener should 114 | // call this function. 115 | func (e *ThenScope) SetGrlText(grlText string) { 116 | e.GrlText = grlText 117 | } 118 | 119 | // Execute will execute this graph in the Then scope 120 | func (e *ThenScope) Execute(dataContext IDataContext, memory *WorkingMemory) error { 121 | if e.ThenExpressionList == nil { 122 | AstLog.Warnf("Can not execute nil expression list") 123 | } 124 | 125 | return e.ThenExpressionList.Execute(dataContext, memory) 126 | } 127 | -------------------------------------------------------------------------------- /ast/WhenScope.go: -------------------------------------------------------------------------------- 1 | // Copyright hyperjumptech/grule-rule-engine Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ast 16 | 17 | import ( 18 | "bytes" 19 | "errors" 20 | "github.com/hyperjumptech/grule-rule-engine/ast/unique" 21 | "reflect" 22 | 23 | "github.com/hyperjumptech/grule-rule-engine/pkg" 24 | ) 25 | 26 | // NewWhenScope creates new instance of WhenScope 27 | func NewWhenScope() *WhenScope { 28 | 29 | return &WhenScope{ 30 | AstID: unique.NewID(), 31 | } 32 | } 33 | 34 | // WhenScope AST graph node 35 | type WhenScope struct { 36 | AstID string 37 | GrlText string 38 | 39 | Expression *Expression 40 | } 41 | 42 | // MakeCatalog create a catalog entry for this AST Node 43 | func (e *WhenScope) MakeCatalog(cat *Catalog) { 44 | meta := &WhenScopeMeta{ 45 | NodeMeta: NodeMeta{ 46 | AstID: e.AstID, 47 | GrlText: e.GrlText, 48 | Snapshot: e.GetSnapshot(), 49 | }, 50 | } 51 | if cat.AddMeta(e.AstID, meta) { 52 | if e.Expression != nil { 53 | meta.ExpressionID = e.Expression.AstID 54 | e.Expression.MakeCatalog(cat) 55 | } 56 | } 57 | } 58 | 59 | // WhenScopeReceiver must be implemented by AST object that stores WhenScope 60 | type WhenScopeReceiver interface { 61 | AcceptWhenScope(whenScope *WhenScope) error 62 | } 63 | 64 | // Clone will clone this Clone. The new clone will have an identical structure 65 | func (e *WhenScope) Clone(cloneTable *pkg.CloneTable) *WhenScope { 66 | clone := &WhenScope{ 67 | AstID: unique.NewID(), 68 | GrlText: e.GrlText, 69 | } 70 | 71 | if e.Expression != nil { 72 | if cloneTable.IsCloned(e.Expression.AstID) { 73 | clone.Expression = cloneTable.Records[e.Expression.AstID].CloneInstance.(*Expression) 74 | } else { 75 | cloned := e.Expression.Clone(cloneTable) 76 | clone.Expression = cloned 77 | cloneTable.MarkCloned(e.Expression.AstID, cloned.AstID, e.Expression, cloned) 78 | } 79 | } 80 | 81 | return clone 82 | } 83 | 84 | // AcceptExpression will accept Expression AST graph node into this node 85 | func (e *WhenScope) AcceptExpression(exp *Expression) error { 86 | if e.Expression == nil { 87 | e.Expression = exp 88 | 89 | return nil 90 | } 91 | 92 | return errors.New("expression for when scope already assigned") 93 | } 94 | 95 | // GetAstID get the UUID asigned for this AST graph node 96 | func (e *WhenScope) GetAstID() string { 97 | 98 | return e.AstID 99 | } 100 | 101 | // GetGrlText get the expression syntax related to this graph when it wast constructed 102 | func (e *WhenScope) GetGrlText() string { 103 | 104 | return e.GrlText 105 | } 106 | 107 | // GetSnapshot will create a structure signature or AST graph 108 | func (e *WhenScope) GetSnapshot() string { 109 | var buff bytes.Buffer 110 | buff.WriteString(WHENSCOPE) 111 | buff.WriteString("(") 112 | if e.Expression != nil { 113 | buff.WriteString(e.Expression.GetSnapshot()) 114 | } 115 | buff.WriteString(")") 116 | 117 | return buff.String() 118 | } 119 | 120 | // SetGrlText set the expression syntax related to this graph when it was constructed. Only ANTLR4 listener should 121 | // call this function. 122 | func (e *WhenScope) SetGrlText(grlText string) { 123 | e.GrlText = grlText 124 | } 125 | 126 | // Evaluate will evaluate this AST graph for when scope evaluation 127 | func (e *WhenScope) Evaluate(dataContext IDataContext, memory *WorkingMemory) (reflect.Value, error) { 128 | 129 | return e.Expression.Evaluate(dataContext, memory) 130 | } 131 | -------------------------------------------------------------------------------- /ast/WhenScope_test.go: -------------------------------------------------------------------------------- 1 | // Copyright hyperjumptech/grule-rule-engine Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ast 16 | 17 | import ( 18 | "github.com/stretchr/testify/assert" 19 | "reflect" 20 | "testing" 21 | ) 22 | 23 | type TestStructShenScope struct { 24 | StringA string 25 | StringB string 26 | } 27 | 28 | func TestNewWhenScope(t *testing.T) { 29 | 30 | expr1 := &Expression{ 31 | AstID: "abc", 32 | LeftExpression: &Expression{ExpressionAtom: &ExpressionAtom{Constant: &Constant{Value: reflect.ValueOf("Whooho")}}}, 33 | RightExpression: &Expression{ExpressionAtom: &ExpressionAtom{Constant: &Constant{Value: reflect.ValueOf("Whooho")}}}, 34 | Operator: OpEq, 35 | } 36 | 37 | ws := NewWhenScope() 38 | ws.SetGrlText("a == b") 39 | assert.Equal(t, "a == b", ws.GetGrlText()) 40 | assert.Nil(t, ws.AcceptExpression(expr1), "Error when first time accept expression") 41 | assert.NotNil(t, ws.AcceptExpression(expr1), "Not Error when second time time accept expression") 42 | 43 | wm := NewWorkingMemory("T", "1") 44 | dt := NewDataContext() 45 | test := &TestStructShenScope{ 46 | StringA: "abc", 47 | StringB: "abc", 48 | } 49 | dt.Add("Struct", test) 50 | 51 | t.Logf("%s Snapshot : %s", ws.GetAstID(), ws.GetSnapshot()) 52 | 53 | val, err := ws.Evaluate(dt, wm) 54 | assert.NoError(t, err) 55 | assert.True(t, val.Bool()) 56 | } 57 | 58 | func TestNewWhenScopeEvaluate(t *testing.T) { 59 | expr1 := &Expression{ 60 | AstID: "abc", 61 | SingleExpression: &Expression{ 62 | ExpressionAtom: &ExpressionAtom{ 63 | Constant: &Constant{ 64 | Value: reflect.ValueOf(123), 65 | }, 66 | }, 67 | }, 68 | } 69 | wm := NewWorkingMemory("T", "1") 70 | dt := NewDataContext() 71 | val, err := expr1.Evaluate(dt, wm) 72 | assert.NoError(t, err) 73 | assert.Equal(t, 123, int(val.Int())) 74 | 75 | ws := NewWhenScope() 76 | assert.Nil(t, ws.AcceptExpression(expr1)) 77 | val, err = ws.Evaluate(dt, wm) 78 | assert.NoError(t, err) 79 | assert.Equal(t, 123, int(val.Int())) 80 | 81 | } 82 | -------------------------------------------------------------------------------- /ast/WorkingMemory_test.go: -------------------------------------------------------------------------------- 1 | // Copyright hyperjumptech/grule-rule-engine Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ast 16 | 17 | import ( 18 | "github.com/stretchr/testify/assert" 19 | "testing" 20 | ) 21 | 22 | func TestWorkingMemory_Add(t *testing.T) { 23 | 24 | a := &Variable{GrlText: "a", Name: "a"} 25 | b := &Variable{GrlText: "b", Name: "b"} 26 | aa := &Variable{GrlText: "a", Name: "a"} 27 | bb := &Variable{GrlText: "b", Name: "b"} 28 | c := &Variable{GrlText: "c", Name: "c"} 29 | d := &Variable{GrlText: "d", Name: "d"} 30 | 31 | expr1 := &Expression{ 32 | AstID: "abc", 33 | LeftExpression: &Expression{ExpressionAtom: &ExpressionAtom{Variable: a}}, 34 | RightExpression: &Expression{ExpressionAtom: &ExpressionAtom{Variable: b}}, 35 | Operator: OpMul, 36 | } 37 | expr2 := &Expression{ 38 | AstID: "cde", 39 | LeftExpression: &Expression{ExpressionAtom: &ExpressionAtom{Variable: aa}}, 40 | RightExpression: &Expression{ExpressionAtom: &ExpressionAtom{Variable: bb}}, 41 | Operator: OpMul, 42 | } 43 | expr3 := &Expression{ 44 | AstID: "fgh", 45 | LeftExpression: &Expression{ExpressionAtom: &ExpressionAtom{Variable: c}}, 46 | RightExpression: &Expression{ExpressionAtom: &ExpressionAtom{Variable: d}}, 47 | Operator: OpMul, 48 | } 49 | wm := NewWorkingMemory("T", "1") 50 | wm.AddVariable(a) 51 | wm.AddVariable(b) 52 | wm.AddVariable(aa) 53 | wm.AddVariable(bb) 54 | wm.AddVariable(c) 55 | wm.AddVariable(d) 56 | 57 | wm.AddExpression(expr1) 58 | wm.AddExpression(expr2) 59 | wm.AddExpression(expr3) 60 | 61 | wm.IndexVariables() 62 | assert.True(t, wm.Reset("a")) 63 | assert.False(t, wm.Reset("some.variable.z")) 64 | assert.True(t, wm.ResetAll()) 65 | } 66 | -------------------------------------------------------------------------------- /ast/unique/Unique.go: -------------------------------------------------------------------------------- 1 | // Copyright hyperjumptech/grule-rule-engine Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package unique 16 | 17 | import ( 18 | "fmt" 19 | "sync" 20 | "time" 21 | ) 22 | 23 | var ( 24 | offset int64 = 0 25 | lastMS int64 = 0 26 | mutex sync.Mutex 27 | ) 28 | 29 | // NewID will create a new unique ID string for this runtime. 30 | // Uniqueness between system or apps is not necessary. 31 | func NewID() string { 32 | mutex.Lock() 33 | defer mutex.Unlock() 34 | millisUnix := time.Now().Unix() 35 | if lastMS == millisUnix { 36 | offset++ 37 | 38 | return fmt.Sprintf("%d-%d", lastMS, offset) 39 | } 40 | lastMS = millisUnix 41 | offset = 0 42 | 43 | return fmt.Sprintf("%d-%d", lastMS, offset) 44 | } 45 | -------------------------------------------------------------------------------- /ast/unique/Unique_test.go: -------------------------------------------------------------------------------- 1 | // Copyright hyperjumptech/grule-rule-engine Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package unique 16 | 17 | import "testing" 18 | 19 | func TestNewId(t *testing.T) { 20 | checkMap := make(map[string]int) 21 | for i := 0; i < 100000; i++ { 22 | id := NewID() 23 | if _, ok := checkMap[id]; ok { 24 | t.FailNow() 25 | } 26 | checkMap[id] = 1 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | trigger: 2 | - master 3 | 4 | variables: 5 | GO111MODULE: 'on' 6 | GOBIN: '$(GOPATH)/bin' # Go binaries path 7 | GOROOT: '/usr/local/go1.13' # Go installation path 8 | GOPATH: '$(system.defaultWorkingDirectory)/gopath' # Go workspace path 9 | modulePath: '$(GOPATH)/src/github.com/hyperjumptech/grule' # Path to the module's code 10 | 11 | steps: 12 | - script: | 13 | mkdir -p '$(GOBIN)' 14 | mkdir -p '$(GOPATH)/pkg' 15 | mkdir -p '$(modulePath)' 16 | shopt -s extglob 17 | mv !(gopath) '$(modulePath)' 18 | echo '##vso[task.prependpath]$(GOBIN)' 19 | echo '##vso[task.prependpath]$(GOROOT)/bin' 20 | echo '##vso[task.setvariable variable=path]$(PATH):$(GOBIN)' 21 | displayName: 'Set up the Go workspace' 22 | - script: go get -v -t -d ./... 23 | workingDirectory: '$(modulePath)' 24 | displayName: 'go get dependencies' 25 | - script: go build -v ./... 26 | workingDirectory: '$(modulePath)' 27 | displayName: 'Build' 28 | - script: | 29 | go get -u golang.org/x/lint/golint 30 | go install github.com/newm4n/goornogo 31 | go test ./... -v -covermode=count -coverprofile=coverage.out -short 32 | goornogo -i coverage.out -c 45.3 33 | golint -set_exit_status builder/... engine/... examples/... ast/... pkg/... antlr/. model/... 34 | workingDirectory: '$(modulePath)' 35 | displayName: 'Run tests' -------------------------------------------------------------------------------- /builder/RuleBuilder_test.go: -------------------------------------------------------------------------------- 1 | // Copyright hyperjumptech/grule-rule-engine Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package builder 16 | 17 | import ( 18 | "github.com/hyperjumptech/grule-rule-engine/ast" 19 | "github.com/hyperjumptech/grule-rule-engine/pkg" 20 | "github.com/stretchr/testify/assert" 21 | "testing" 22 | ) 23 | 24 | func TestNoPanic(t *testing.T) { 25 | GRL := `rule TestNoDesc { when true then Ok(); }` 26 | lib := ast.NewKnowledgeLibrary() 27 | ruleBuilder := NewRuleBuilder(lib) 28 | err := ruleBuilder.BuildRuleFromResource("CallingLog", "0.1.1", pkg.NewBytesResource([]byte(GRL))) 29 | assert.NoError(t, err) 30 | } 31 | 32 | func TestRuleEntry_Clone(t *testing.T) { 33 | testRule := `rule CloneRule "Duplicate Rule 1" salience 5 { 34 | when 35 | (Fact.Distance > 5000 && Fact.Duration > 120) && (Fact.Result == false) 36 | Then 37 | Fact.NetAmount=143.320007; 38 | Fact.Result=true; 39 | }` 40 | lib := ast.NewKnowledgeLibrary() 41 | rb := NewRuleBuilder(lib) 42 | err := rb.BuildRuleFromResource("testrule", "0.1.1", pkg.NewBytesResource([]byte(testRule))) 43 | assert.NoError(t, err) 44 | kb := lib.GetKnowledgeBase("testrule", "0.1.1") 45 | re := kb.RuleEntries["CloneRule"] 46 | 47 | ct := &pkg.CloneTable{Records: make(map[string]*pkg.CloneRecord)} 48 | reClone := re.Clone(ct) 49 | 50 | assert.Equal(t, re.GetSnapshot(), reClone.GetSnapshot()) 51 | } 52 | -------------------------------------------------------------------------------- /docs/Documentation.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | --- 4 | 5 | :construction_worker: Inviting opensource translators. **grule-rule-engine**'s documentation is being translated. Please read [CONTRIBUTING](../CONTRIBUTING.md) and [CONTRIBUTING TRANSLATION](CONTRIBUTING_TRANSLATION.md) guidelines. 6 | 7 | :writing_hand: Submit your PR !! 8 | 9 | --- 10 | 11 | - [![About_cn](https://github.com/yammadev/flag-icons/blob/master/png/CN.png?raw=true)](cn/About_cn.md) [Chinese](cn/About_cn.md) (:writing_hand: need translation) 12 | - [![About_de](https://github.com/yammadev/flag-icons/blob/master/png/DE.png?raw=true)](de/About_de.md) [German](de/About_de.md) (:writing_hand: need translation) 13 | - [![About_en](https://github.com/yammadev/flag-icons/blob/master/png/GB.png?raw=true)](en/About_en.md) [English](en/About_en.md) 14 | - [![About_id](https://github.com/yammadev/flag-icons/blob/master/png/ID.png?raw=true)](id/About_id.md) [Bahasa Indonesia](id/About_id.md) (:writing_hand: need translation) 15 | - [![About_pl](https://github.com/yammadev/flag-icons/blob/master/png/PL.png?raw=true)](id/About_pl.md) [Polish](pl/About_pl.md) 16 | -------------------------------------------------------------------------------- /docs/cn/About_cn.md: -------------------------------------------------------------------------------- 1 | [![Gopher Holds The Rules](https://github.com/hyperjumptech/grule-rule-engine/blob/master/gopher-grule.png?raw=true)](https://github.com/hyperjumptech/grule-rule-engine/blob/master/gopher-grule.png?raw=true) 2 | 3 | 4 | __"Gopher 遵守规则"__ 5 | 6 | 7 | 8 | [![About_cn](https://github.com/yammadev/flag-icons/blob/master/png/CN.png?raw=true)](../cn/About_cn.md) 9 | [![About_de](https://github.com/yammadev/flag-icons/blob/master/png/DE.png?raw=true)](../de/About_de.md) 10 | [![About_en](https://github.com/yammadev/flag-icons/blob/master/png/GB.png?raw=true)](../en/About_en.md) 11 | [![About_id](https://github.com/yammadev/flag-icons/blob/master/png/ID.png?raw=true)](../id/About_id.md) 12 | [![About_pl](https://github.com/yammadev/flag-icons/blob/master/png/PL.png?raw=true)](../pl/About_pl.md) 13 | 14 | [About](About_cn.md) | [Tutorial](Tutorial_cn.md) | [Rule Engine](RuleEngine_cn.md) | [GRL](GRL_cn.md) | [GRL JSON](GRL_JSON_cn.md) | [RETE Algorithm](RETE_cn.md) | [Functions](Function_cn.md) | [FAQ](FAQ_cn.md) | [Benchmark](Benchmarking_cn.md) 15 | 16 | # Grule 17 | 18 | ```go 19 | import "github.com/hyperjumptech/grule-rule-engine" 20 | ``` 21 | 22 | ## Go 规则引擎 23 | 24 | Grule是Golang实现的规则引擎库。受业内称赞的JBOSS Drools的启发,我们实现了一种简单的规则引擎。 25 | 26 | 正如**Drools**,**Grule**也有自己的*DSL*,对比如下。 27 | 28 | Drool的DRL如下: 29 | 30 | ```go 31 | rule "SpeedUp" 32 | salience 10 33 | when 34 | $TestCar : TestCarClass( speedUp == true && speed < maxSpeed ) 35 | $DistanceRecord : DistanceRecordClass() 36 | then 37 | $TestCar.setSpeed($TestCar.Speed + $TestCar.SpeedIncrement); 38 | update($TestCar); 39 | $DistanceRecord.setTotalDistance($DistanceRecord.getTotalDistance() + $TestCar.Speed) 40 | update($DistanceRecord) 41 | end 42 | ``` 43 | 44 | 同时Grule的GRL如下: 45 | 46 | ```go 47 | rule SpeedUp "When testcar is speeding up we increase the speed." salience 10 { 48 | when 49 | TestCar.SpeedUp == true && TestCar.Speed < TestCar.MaxSpeed 50 | then 51 | TestCar.Speed = TestCar.Speed + TestCar.SpeedIncrement; 52 | DistanceRecord.TotalDistance = DistanceRecord.TotalDistance + TestCar.Speed; 53 | } 54 | ``` 55 | 56 | # 什么是 RuleEngine 57 | 58 | 59 | 60 | 对于 Martin Fowler的文章,这不是一个比较好的诠释。你可以看原文[RulesEngine by Martin Fowler](https://martinfowler.com/bliki/RulesEngine.html)。 61 | 62 | 以下内容摘自 **TutorialsPoint** 网站(做了一些小改动) 63 | 64 | **Grule**规则引擎是一种生产规则系统,使用了基于规则的方案去实现一个专家系统。专家系统是一种基于知识的系统,可以使用知识描述将获取的知识处理成可以用来推理的知识库。 65 | 66 | 生产规则系统是图灵完备的,专注于使用知识描述以一种简洁、明确和声明式的方式去表达一阶的命题逻辑。 67 | 68 | 生产规则系统的大脑是推理引擎,可以扩展到大量的规则(rule)和事实(fact)。推理引擎用事实和数据去匹配生产规则(也称为生产或者规则),从而推理出一个可以产生行动的结论。 69 | 70 | 生产规则系统是一个两部分结构,使用了一阶逻辑对知识描述进行一个推理。业务规则引擎是在运行时生产环境中执行一个或多个业务规则的软件系统。 71 | 72 | 规则引擎允许你定义 做什么(what)而不是怎么做(How)。 73 | 74 | ## 什么是规则(Rule) 75 | 76 | 规则是知识片段,经常被描述成 "当满足某些条件时,然后做一些动作"。 77 | 78 | ```go 79 | When 80 | 81 | Then 82 | 83 | ``` 84 | 85 | 规则最重要的部分是`when`部分。如果`when`中的条件被满足了,则可以触发`then`操作。 86 | 87 | ```go 88 | rule 89 | { 90 | when 91 | 92 | 93 | then 94 | 95 | } 96 | ``` 97 | 98 | ## 规则引擎的优势 99 | 100 | ### 声明式编程 101 | 102 | 规则可以很容易地表达对困难问题的解决方案并获得验证。不像代码,规则可以使用不复杂的语言描述。业务分析师可以轻松阅读和验证一组规则。 103 | 104 | ### 逻辑与数据分离 105 | 106 | 数据驻留在域对象中,业务逻辑驻留在规则中。 根据项目的类型,这种分离可能非常有利。 107 | 108 | ### 知识集中化 109 | 110 | 通过使用规则,您可以创建一个可执行的知识库(知识库)。 这是商业政策的一个真理。 理想情况下,规则具有可读性,它们也可以用作文档。 111 | 112 | ### 变化敏捷 113 | 114 | 由于业务规则实际上被视为数据。 根据业务动态性质调整规则变得很容易。 无需像普通软件开发那样重新构建代码、部署,您只需要推出规则集并将它们应用到知识库中。 115 | 116 | -------------------------------------------------------------------------------- /docs/cn/Binary_Rule_File_cn.md: -------------------------------------------------------------------------------- 1 | # 恢复和加载 GRB 文件 2 | 3 | 4 | 5 | 6 | [![Binary_Rule_File_cn](https://github.com/yammadev/flag-icons/blob/master/png/CN.png?raw=true)](../cn/Binary_Rule_File_cn.md) 7 | [![Binary_Rule_File_de](https://github.com/yammadev/flag-icons/blob/master/png/DE.png?raw=true)](../de/Binary_Rule_File_de.md) 8 | [![Binary_Rule_File_en](https://github.com/yammadev/flag-icons/blob/master/png/GB.png?raw=true)](../en/Binary_Rule_File_en.md) 9 | [![Binary_Rule_File_id](https://github.com/yammadev/flag-icons/blob/master/png/ID.png?raw=true)](../id/Binary_Rule_File_id.md) 10 | [![Binary_Rule_File_pl](https://github.com/yammadev/flag-icons/blob/master/png/PL.png?raw=true)](../pl/Binary_Rule_File_id.md) 11 | 12 | [About](About_cn.md) | [Tutorial](Tutorial_cn.md) | [Rule Engine](RuleEngine_cn.md) | [GRL](GRL_cn.md) | [GRL JSON](GRL_JSON_cn.md) | [RETE Algorithm](RETE_cn.md) | [Functions](Function_cn.md) | [FAQ](FAQ_cn.md) | [Benchmark](Benchmarking_cn.md) 13 | 14 | --- 15 | 16 | 当你从GRL脚本中加载大量的(几百以上)规则到`KnowledgeLibrary`, 比如当你启动引擎的时候,你可能会注意到会花费很长的时候去加载,有时候可能会花几分钟。这个主要归结于ANTLR4语法解析。别误会,ANTLR是一个伟大的工具,能够很好的完成工作。但是明显的,对于任意解析工具,数十个几千行的脚本文件都不是一个小工作。 17 | 18 | 所以解决方案是,把所有的规则存储到二进制文件。这样下次的加载的时候会更快。就如编译器的工作原理一样。你可以编译你的GRL脚本,从二进制(GRB)中加载结果要快10倍。 19 | 20 | 工作流将会编程:作为规则的作者,你依然可以编辑文本的GRL脚本。当你想要发布你的规则集合时,你可以编译成GRB二进制文件。你发布GRB到你的服务,然后服务从GRB加载。 21 | 22 | ## 存储 KnowledgeBase 到 GRB 23 | 24 | First, you should have a `KnowledgeLibrary` containing the `KnowledgeBase` you want to store into GRB. 25 | Normally you would load a GRL into your library as follows : 26 | 27 | 首先你得有一个包含想要存储到GRB的`KnowledgeBase`的`KnowledgeLibrary`。如下,你可以正常加载GRL到你的知识库。 28 | 29 | ```go 30 | lib := ast.NewKnowledgeLibrary() 31 | rb := builder.NewRuleBuilder(lib) 32 | err := rb.BuildRuleFromResource("HugeRuleSet", "0.0.1", pkg.NewFileResource("HugeRuleSet.grl")) 33 | assert.NoError(t, err) 34 | ``` 35 | 36 | 然后,你可以如下存储knowledge base到GRB: 37 | 38 | ```go 39 | f, err := os.OpenFile("HugeRuleSet.grb", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) 40 | assert.Nil(t, err) 41 | 42 | // Save the knowledge base into the file and close it. 43 | err = lib.StoreKnowledgeBaseToWriter(f, "HugeRuleSet", "0.0.1") 44 | assert.Nil(t, err) 45 | _ = f.Close() 46 | ``` 47 | 48 | 你的GRB文件现在包含了knowledge base的所有规则,然后已经可以被用来加载。 49 | 50 | 是的,相对于GRL来说,GRB文件大小将会膨胀大概10倍。但是正如大部分时候,其他编译程序将脚本编译成二进制格式。(.java vs .class, .c vs .exe, go vs executable) 51 | 52 | ## 加载 GRB 到 KnowledgeLibrary 53 | 54 | 加载GRB更简单,不需要builder。 55 | 56 | ```go 57 | lib := ast.NewKnowledgeLibrary() 58 | 59 | // Open the existing safe file 60 | f, err := os.Open("HugeRuleSet.grb") 61 | assert.Nil(t, err) 62 | 63 | // Load the file directly into the library and close the file 64 | // btw, you should not use the blueprint_kb in your engine execution. 65 | bluerint_kb, err := lib.LoadKnowledgeBaseFromReader(f2, true) 66 | assert.Nil(t, err) 67 | _ = f.Close() 68 | ``` 69 | 70 | 恭喜你, GRB 会被加载到 `KnowledgeLibrary`,你可以像往常 一样获取knowledge base。 71 | 72 | ```go 73 | kb := lib.NewKnowledgeBaseInstance("HugeRuleSet", "0.0.1") 74 | ``` 75 | 76 | 还有一件事,如果你的`KnowledgeLibrary`包含了名字和版本号和GRB中一样的`KnowledgeBase`,`KnowledgeBase`将会被覆盖。 -------------------------------------------------------------------------------- /docs/cn/GRL_Literals_cn.md: -------------------------------------------------------------------------------- 1 | # Grule Rule Language (GRL) Literals 2 | 3 | [![GRL_Literals_cn](https://github.com/yammadev/flag-icons/blob/master/png/CN.png?raw=true)](../cn/GRL_Literals_cn.md) 4 | [![GRL_Literals_de](https://github.com/yammadev/flag-icons/blob/master/png/DE.png?raw=true)](../de/GRL_Literals_de.md) 5 | [![GRL_Literals_en](https://github.com/yammadev/flag-icons/blob/master/png/GB.png?raw=true)](../en/GRL_Literals_en.md) 6 | [![GRL_Literals_id](https://github.com/yammadev/flag-icons/blob/master/png/ID.png?raw=true)](../id/GRL_Literals_id.md) 7 | [![GRL_Literals_pl](https://github.com/yammadev/flag-icons/blob/master/png/PL.png?raw=true)](../pl/GRL_Literals_pl.md) 8 | 9 | [About](About_cn.md) | [Tutorial](Tutorial_cn.md) | [Rule Engine](RuleEngine_cn.md) | [GRL](GRL_cn.md) | [GRL JSON](GRL_JSON_cn.md) | [RETE Algorithm](RETE_cn.md) | [Functions](Function_cn.md) | [FAQ](GRL_Literals_cn.md) | [Benchmark](Benchmarking_cn.md) 10 | 11 | --- 12 | 13 | ## 字符串字面变量 14 | 15 | 在GRL中,字符串是一个被单引号 `'` 或者双引号`"`包围的连续的字符. 16 | 17 | 如果是以单引号开始的,必须以单引号结束。对双引号也是如此。 18 | 19 | 举例 20 | 21 | ```go 22 | "a quick brown fox jumps over a lazy dog" 23 | ``` 24 | 25 | 或者 26 | 27 | ```go 28 | 'a quick brown fox jumps over a lazy dog' 29 | ``` 30 | 31 | 字符串字面变量可以包含空格字符,比如 `space`, `tab` 或者`carriage-return` 32 | 33 | 举例 34 | 35 | ```go 36 | "A quick brown fox 37 | Jumps 38 | Over a lazy dog" 39 | ``` 40 | 41 | 字符串中为了包含特殊字符,你需要跟Go一样进行转义。 42 | 43 | 举例 44 | 45 | ```go 46 | "This string contains \" Double Quote" 47 | ``` 48 | 49 | ## 数字字面变量 50 | 51 | GRL中数字字面变量 跟Golang指定的尽可能相同。它可以理解各种各样的数字格式,比如10进制,8进制,和16进制。二进制目前还没实现。 52 | 53 | ### 整型字面变量 54 | 55 | #### 十进制 56 | 57 | 十进制,举例 58 | 59 | ```go 60 | 0 61 | 123 62 | 34592 63 | -1 64 | -47234 65 | ``` 66 | 67 | 8进制,举例 68 | 69 | ```go 70 | 01 71 | 07 72 | 010 73 | 017 74 | -034 75 | -045 76 | 04328 (error : invalid octal number) 77 | ``` 78 | 79 | 16进制,举例 80 | 81 | ```go 82 | 0x1 83 | 0xF 84 | 0x10 85 | 0x1F 86 | 0xFF00 87 | -0x12 88 | -0x00ABCD 89 | -0x890AbCdEf 90 | ``` 91 | 92 | ### 实数或者浮点数字面变量 93 | 94 | 十进制,举例 95 | 96 | ```go 97 | 0. 98 | 72.40 99 | 072.40 100 | 2.71828 101 | 1.e+0 102 | 6.67428e-11 103 | 1E6 104 | .25 105 | .12345E+5 106 | -072.40 107 | -2.71828 108 | -1.e+0 109 | ``` 110 | 111 | 16进制举例 112 | 113 | ```go 114 | 0x1p-2 115 | 0x2.p10 116 | 0x1.Fp+0 117 | 0X.8p-0 118 | 0X_1FFFP-16 119 | 0x15e-2 120 | ``` 121 | 122 | ## 布尔字面变量 123 | 124 | ```go 125 | true 126 | TRUE 127 | True 128 | TrUe 129 | false 130 | False 131 | FALSE 132 | FaLsE 133 | ``` 134 | -------------------------------------------------------------------------------- /docs/cn/MatchingRules_cn.md: -------------------------------------------------------------------------------- 1 | ## 获取匹配的规则 (按优先级排序) 2 | 3 | [![MatchingRules_cn](https://github.com/yammadev/flag-icons/blob/master/png/CN.png?raw=true)](../cn/MatchingRules_cn.md) 4 | [![MatchingRules_de](https://github.com/yammadev/flag-icons/blob/master/png/DE.png?raw=true)](../de/MatchingRules_de.md) 5 | [![MatchingRules_en](https://github.com/yammadev/flag-icons/blob/master/png/GB.png?raw=true)](../en/MatchingRules_en.md) 6 | [![MatchingRules_id](https://github.com/yammadev/flag-icons/blob/master/png/ID.png?raw=true)](../id/MatchingRules_id.md) 7 | [![MatchingRules_pl](https://github.com/yammadev/flag-icons/blob/master/png/PL.png?raw=true)](../pl/MatchingRules_pl.md) 8 | 9 | `GruleEngine.go`中`FetchMatchingRules`函数的将会获取所有能够满足事实的所有有效规则,并返回按优先级排序的 `ast.RuleEntry` 列表。 10 | 11 | ##### 规则: 12 | 13 | ```go 14 | rule DuplicateRule1 "Duplicate Rule 1" salience 5 { 15 | when 16 | (Fact.Distance > 5000 && Fact.Duration > 120) && (Fact.Result == false) 17 | Then 18 | Fact.NetAmount=143.320007; 19 | Fact.Result=true; 20 | } 21 | 22 | rule DuplicateRule2 "Duplicate Rule 2" salience 6 { 23 | when 24 | (Fact.Distance > 5000 && Fact.Duration > 120) && (Fact.Result == false) 25 | Then 26 | Fact.NetAmount=143.320007; 27 | Fact.Result=true; 28 | } 29 | 30 | rule DuplicateRule3 "Duplicate Rule 3" salience 7 { 31 | when 32 | (Fact.Distance > 5000 && Fact.Duration > 120) && (Fact.Result == false) 33 | Then 34 | Fact.NetAmount=143.320007; 35 | Fact.Result=true; 36 | } 37 | 38 | rule DuplicateRule4 "Duplicate Rule 4" salience 8 { 39 | when 40 | (Fact.Distance > 5000 && Fact.Duration > 120) && (Fact.Result == false) 41 | Then 42 | Fact.NetAmount=143.320007; 43 | Fact.Result=true; 44 | } 45 | 46 | rule UniqueRule5 "Unique Rule 5" salience 0 { 47 | when 48 | (Fact.Distance > 5000 && Fact.Duration == 120) && (Fact.Result == false) 49 | Then 50 | Output.NetAmount=143.320007; 51 | Fact.Result=true; 52 | } 53 | ``` 54 | 55 | 除了 `UniqueRule5`规则之外,所有以上的规则除了优先级不一样之外都是重复的. 正如我们所了解的,有更高优先级的规则将会在发生冲突的时候会被优先执行。 56 | 57 | ```go 58 | fact := &Fact{ 59 | Distance: 6000, 60 | Duration: 121, 61 | } 62 | ``` 63 | 64 | ##### 调用FetchMatchingRules: 65 | 66 | ```go 67 | engine := engine.NewGruleEngine() 68 | ruleEntries, err := engine.FetchMatchingRules(dctx, kb) 69 | if err != nil { 70 | panic(err) 71 | } 72 | ``` 73 | 74 | #### 返回结果: 75 | 76 | ```go 77 | Returns []*ast.RuleEntry (All Matching Rule Entries sorted by Salience) 78 | 79 | rule DuplicateRule4 "Duplicate Rule 4" salience 8 { 80 | when 81 | (Fact.Distance > 5000 && Fact.Duration > 120) && (Fact.Result == false) 82 | Then 83 | Fact.NetAmount=143.320007; 84 | Fact.Result=true; 85 | } 86 | 87 | rule DuplicateRule3 "Duplicate Rule 3" salience 7 { 88 | when 89 | (Fact.Distance > 5000 && Fact.Duration > 120) && (Fact.Result == false) 90 | Then 91 | Fact.NetAmount=143.320007; 92 | Fact.Result=true; 93 | } 94 | 95 | rule DuplicateRule2 "Duplicate Rule 2" salience 6 { 96 | when 97 | (Fact.Distance > 5000 && Fact.Duration > 120) && (Fact.Result == false) 98 | Then 99 | Fact.NetAmount=143.320007; 100 | Fact.Result=true; 101 | } 102 | 103 | rule DuplicateRule1 "Duplicate Rule 1" salience 5 { 104 | when 105 | (Fact.Distance > 5000 && Fact.Duration > 120) && (Fact.Result == false) 106 | Then 107 | Fact.NetAmount=143.320007; 108 | Fact.Result=true; 109 | } 110 | ``` 111 | -------------------------------------------------------------------------------- /docs/de/Binary_Rule_File_de.md: -------------------------------------------------------------------------------- 1 | # Storing and Loading GRB file. 2 | 3 | --- 4 | 5 | :construction: 6 | __THIS PAGE IS BEING TRANSLATED__ 7 | :construction: 8 | 9 | :construction_worker: Contributors are invited. Please read [CONTRIBUTING](../../CONTRIBUTING.md) and [CONTRIBUTING TRANSLATION](../CONTRIBUTING_TRANSLATION.md) guidelines. 10 | 11 | :vulcan_salute: Please remove this note once you're done translating. 12 | 13 | --- 14 | 15 | 16 | [![Binary_Rule_File_cn](https://github.com/yammadev/flag-icons/blob/master/png/CN.png?raw=true)](../cn/Binary_Rule_File_cn.md) 17 | [![Binary_Rule_File_de](https://github.com/yammadev/flag-icons/blob/master/png/DE.png?raw=true)](../de/Binary_Rule_File_de.md) 18 | [![Binary_Rule_File_en](https://github.com/yammadev/flag-icons/blob/master/png/GB.png?raw=true)](../en/Binary_Rule_File_en.md) 19 | [![Binary_Rule_File_id](https://github.com/yammadev/flag-icons/blob/master/png/ID.png?raw=true)](../id/Binary_Rule_File_id.md) 20 | [![Binary_Rule_File_pl](https://github.com/yammadev/flag-icons/blob/master/png/PL.png?raw=true)](../pl/Binary_Rule_File_pl.md) 21 | 22 | [About](About_de.md) | [Tutorial](Tutorial_de.md) | [Rule Engine](RuleEngine_de.md) | [GRL](GRL_de.md) | [GRL JSON](GRL_JSON_de.md) | [RETE Algorithm](RETE_de.md) | [Functions](Function_de.md) | [FAQ](FAQ_de.md) | [Benchmark](Benchmarking_de.md) 23 | 24 | --- 25 | 26 | When you loading huge amount (hundreds) of rules in GRL script into `KnowledgeLibrary`, e.g. when you start 27 | the engine, you may notice that it may took some time to load, some time it could go up to a couple of minutes. 28 | This is due to the syntax parsing done by ANTLR4. Don't get me wrong, ANTLR is a great tools and it done the job very well. 29 | But obviously, tens of thousands of lines in a script file is no small task, for any parser tools. 30 | 31 | So the idea is, to store all the rule in a binary file. So it load faster next time. Just like 32 | a compiler. You compile your text GRL script and the result is a binary file (GRB) which load 10 time faster. 33 | 34 | The workflow is as following : As a Rule author, you still work in the textual GRL script. When you want to release your rule set, 35 | you can "compile" it into GRB binary file. The you ditribute that GRB into your server and have the server load 36 | from GRB. 37 | 38 | ## Storing KnowledgeBase into GRB 39 | 40 | First, you should have a `KnowledgeLibrary` containing the `KnowledgeBase` you want to store into GRB. 41 | Normally you would load a GRL into your library as follows : 42 | 43 | ```go 44 | lib := ast.NewKnowledgeLibrary() 45 | rb := builder.NewRuleBuilder(lib) 46 | err := rb.BuildRuleFromResource("HugeRuleSet", "0.0.1", pkg.NewFileResource("HugeRuleSet.grl")) 47 | assert.NoError(t, err) 48 | ``` 49 | 50 | Second, you can now save the knowledge base into GRB as follows: 51 | 52 | ```go 53 | f, err := os.OpenFile("HugeRuleSet.grb", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) 54 | assert.Nil(t, err) 55 | 56 | // Save the knowledge base into the file and close it. 57 | err = lib.StoreKnowledgeBaseToWriter(f, "HugeRuleSet", "0.0.1") 58 | assert.Nil(t, err) 59 | _ = f.Close() 60 | ``` 61 | 62 | Your GRB file is now contains all rules in the specified knowledge base 63 | and ready for future loading. 64 | 65 | Yes, the GRB file size is inflated like 10 times compared to the GRL one, 66 | But it, most of the time, like that when you compile some script into its 67 | compiled binary form. (.java vs .class, .c vs .exe, go vs executable) 68 | 69 | ## Loading GRB into KnowledgeLibrary 70 | 71 | Loading GRB is much simpler. No need a builder. 72 | 73 | ```go 74 | lib := ast.NewKnowledgeLibrary() 75 | 76 | // Open the existing safe file 77 | f, err := os.Open("HugeRuleSet.grb") 78 | assert.Nil(t, err) 79 | 80 | // Load the file directly into the library and close the file 81 | // btw, you should not use the blueprint_kb in your engine execution. 82 | bluerint_kb, err := lib.LoadKnowledgeBaseFromReader(f2, true) 83 | assert.Nil(t, err) 84 | _ = f.Close() 85 | ``` 86 | 87 | There you go !!!, the GRB is loaded into `KnowledgeLibrary` 88 | You can obtain the knowledge base normally. 89 | 90 | ```go 91 | kb := lib.NewKnowledgeBaseInstance("HugeRuleSet", "0.0.1") 92 | ``` 93 | 94 | One thing, if in your `KnowledgeLibrary` already contains the same `KnowledgeBase` name and version 95 | to the one in the GRB, that `KnowledgeBase` in the library will be overwritten. -------------------------------------------------------------------------------- /docs/de/GRL_Literals_de.md: -------------------------------------------------------------------------------- 1 | # Grule Rule Language (GRL) Literals 2 | 3 | --- 4 | 5 | :construction: 6 | __THIS PAGE IS BEING TRANSLATED__ 7 | :construction: 8 | 9 | :construction_worker: Contributors are invited. Please read [CONTRIBUTING](../../CONTRIBUTING.md) and [CONTRIBUTING TRANSLATION](../CONTRIBUTING_TRANSLATION.md) guidelines. 10 | 11 | :vulcan_salute: Please remove this note once you're done translating. 12 | 13 | --- 14 | 15 | 16 | [![GRL_Literals_cn](https://github.com/yammadev/flag-icons/blob/master/png/CN.png?raw=true)](../cn/GRL_Literals_cn.md) 17 | [![GRL_Literals_de](https://github.com/yammadev/flag-icons/blob/master/png/DE.png?raw=true)](../de/GRL_Literals_de.md) 18 | [![GRL_Literals_en](https://github.com/yammadev/flag-icons/blob/master/png/GB.png?raw=true)](../en/GRL_Literals_en.md) 19 | [![GRL_Literals_id](https://github.com/yammadev/flag-icons/blob/master/png/ID.png?raw=true)](../id/GRL_Literals_id.md) 20 | [![GRL_Literals_pl](https://github.com/yammadev/flag-icons/blob/master/png/PL.png?raw=true)](../pl/GRL_Literals_pl.md) 21 | 22 | [About](About_de.md) | [Tutorial](Tutorial_de.md) | [Rule Engine](RuleEngine_de.md) | [GRL](GRL_de.md) | [GRL JSON](GRL_JSON_de.md) | [RETE Algorithm](RETE_de.md) | [Functions](Function_de.md) | [FAQ](FAQ_de.md) | [Benchmark](Benchmarking_de.md) 23 | 24 | --- 25 | 26 | ## String Literals 27 | 28 | In GRL, a string is any sequence of characters surrounded by either a single `'` or double `"` quotes. 29 | If the literal is started with single quote, then it must be terminated by single quote. The same for double quote. 30 | 31 | For example 32 | 33 | ```go 34 | "a quick brown fox jumps over a lazy dog" 35 | ``` 36 | 37 | or 38 | 39 | ```go 40 | 'a quick brown fox jumps over a lazy dog' 41 | ``` 42 | 43 | A string literal may contain white space characters such as `space`, `tab` or a 44 | `carriage-return` 45 | 46 | For example 47 | 48 | ```go 49 | "A quick brown fox 50 | Jumps 51 | Over a lazy dog" 52 | ``` 53 | 54 | To include special characters in string, you can *escape* them as is normal in Go 55 | 56 | For example 57 | 58 | ```go 59 | "This string contains \" Double Quote" 60 | ``` 61 | 62 | ## Number Literals 63 | 64 | GRL follows literal numbering as specified by the Golang specification as best 65 | as it can. It understands various numbers notation such as 66 | Base10 (Decimals), Base8 (Octal) and Base16 (Hex). Base2 (Binary) is not yet implemented. 67 | 68 | ### Integer Literal 69 | 70 | #### Decimals 71 | 72 | In Base 10 - For Example 73 | 74 | ```go 75 | 0 76 | 123 77 | 34592 78 | -1 79 | -47234 80 | ``` 81 | 82 | In Base 8 - For Example 83 | 84 | ```go 85 | 01 86 | 07 87 | 010 88 | 017 89 | -034 90 | -045 91 | 04328 (error : invalid octal number) 92 | ``` 93 | 94 | In Base 16 - For Example 95 | 96 | ```go 97 | 0x1 98 | 0xF 99 | 0x10 100 | 0x1F 101 | 0xFF00 102 | -0x12 103 | -0x00ABCD 104 | -0x890AbCdEf 105 | ``` 106 | 107 | ### Real Numbers / Float Literals 108 | 109 | In Base 10 - For Example 110 | 111 | ```go 112 | 0. 113 | 72.40 114 | 072.40 115 | 2.71828 116 | 1.e+0 117 | 6.67428e-11 118 | 1E6 119 | .25 120 | .12345E+5 121 | -072.40 122 | -2.71828 123 | -1.e+0 124 | ``` 125 | 126 | In Base 16 - For Example 127 | 128 | ```go 129 | 0x1p-2 130 | 0x2.p10 131 | 0x1.Fp+0 132 | 0X.8p-0 133 | 0X_1FFFP-16 134 | 0x15e-2 135 | ``` 136 | 137 | ## Boolean Literal 138 | 139 | ```go 140 | true 141 | TRUE 142 | True 143 | TrUe 144 | false 145 | False 146 | FALSE 147 | FaLsE 148 | ``` 149 | -------------------------------------------------------------------------------- /docs/de/MatchingRules_de.md: -------------------------------------------------------------------------------- 1 | ## Fetch Matching Rules (Order by Salience) 2 | 3 | --- 4 | 5 | :construction: 6 | __THIS PAGE IS BEING TRANSLATED__ 7 | :construction: 8 | 9 | :construction_worker: Contributors are invited. Please read [CONTRIBUTING](../../CONTRIBUTING.md) and [CONTRIBUTING TRANSLATION](../CONTRIBUTING_TRANSLATION.md) guidelines. 10 | 11 | :vulcan_salute: Please remove this note once you're done translating. 12 | 13 | --- 14 | 15 | 16 | [![MatchingRules_cn](https://github.com/yammadev/flag-icons/blob/master/png/CN.png?raw=true)](../cn/MatchingRules_cn.md) 17 | [![MatchingRules_de](https://github.com/yammadev/flag-icons/blob/master/png/DE.png?raw=true)](../de/MatchingRules_de.md) 18 | [![MatchingRules_en](https://github.com/yammadev/flag-icons/blob/master/png/GB.png?raw=true)](../en/MatchingRules_en.md) 19 | [![MatchingRules_id](https://github.com/yammadev/flag-icons/blob/master/png/ID.png?raw=true)](../id/MatchingRules_id.md) 20 | [![MatchingRules_pl](https://github.com/yammadev/flag-icons/blob/master/png/PL.png?raw=true)](../pl/MatchingRules_pl.md) 21 | 22 | `FetchMatchingRules` in `GruleEngine.go` fetches all the rules valid for a given fact and returns a list of `ast.RuleEntry` values ordered by salience property. 23 | 24 | ##### Rules: 25 | 26 | ```go 27 | rule DuplicateRule1 "Duplicate Rule 1" salience 5 { 28 | when 29 | (Fact.Distance > 5000 && Fact.Duration > 120) && (Fact.Result == false) 30 | Then 31 | Fact.NetAmount=143.320007; 32 | Fact.Result=true; 33 | } 34 | 35 | rule DuplicateRule2 "Duplicate Rule 2" salience 6 { 36 | when 37 | (Fact.Distance > 5000 && Fact.Duration > 120) && (Fact.Result == false) 38 | Then 39 | Fact.NetAmount=143.320007; 40 | Fact.Result=true; 41 | } 42 | 43 | rule DuplicateRule3 "Duplicate Rule 3" salience 7 { 44 | when 45 | (Fact.Distance > 5000 && Fact.Duration > 120) && (Fact.Result == false) 46 | Then 47 | Fact.NetAmount=143.320007; 48 | Fact.Result=true; 49 | } 50 | 51 | rule DuplicateRule4 "Duplicate Rule 4" salience 8 { 52 | when 53 | (Fact.Distance > 5000 && Fact.Duration > 120) && (Fact.Result == false) 54 | Then 55 | Fact.NetAmount=143.320007; 56 | Fact.Result=true; 57 | } 58 | 59 | rule UniqueRule5 "Unique Rule 5" salience 0 { 60 | when 61 | (Fact.Distance > 5000 && Fact.Duration == 120) && (Fact.Result == false) 62 | Then 63 | Output.NetAmount=143.320007; 64 | Fact.Result=true; 65 | } 66 | ``` 67 | 68 | All the above rules are duplicate ones except for a differing salience value, except `UniqueRule5`. As we all know, a rule with higher salience has a higher priority and will get executed before a rule with lower salience if there is a conflict. 69 | 70 | ```go 71 | fact := &Fact{ 72 | Distance: 6000, 73 | Duration: 121, 74 | } 75 | ``` 76 | 77 | ##### FetchMatchingRules: 78 | 79 | ```go 80 | engine := engine.NewGruleEngine() 81 | ruleEntries, err := engine.FetchMatchingRules(dctx, kb) 82 | if err != nil { 83 | panic(err) 84 | } 85 | ``` 86 | 87 | #### result: 88 | 89 | ```go 90 | Returns []*ast.RuleEntry (All Matching Rule Entries sorted by Salience) 91 | 92 | rule DuplicateRule4 "Duplicate Rule 4" salience 8 { 93 | when 94 | (Fact.Distance > 5000 && Fact.Duration > 120) && (Fact.Result == false) 95 | Then 96 | Fact.NetAmount=143.320007; 97 | Fact.Result=true; 98 | } 99 | 100 | rule DuplicateRule3 "Duplicate Rule 3" salience 7 { 101 | when 102 | (Fact.Distance > 5000 && Fact.Duration > 120) && (Fact.Result == false) 103 | Then 104 | Fact.NetAmount=143.320007; 105 | Fact.Result=true; 106 | } 107 | 108 | rule DuplicateRule2 "Duplicate Rule 2" salience 6 { 109 | when 110 | (Fact.Distance > 5000 && Fact.Duration > 120) && (Fact.Result == false) 111 | Then 112 | Fact.NetAmount=143.320007; 113 | Fact.Result=true; 114 | } 115 | 116 | rule DuplicateRule1 "Duplicate Rule 1" salience 5 { 117 | when 118 | (Fact.Distance > 5000 && Fact.Duration > 120) && (Fact.Result == false) 119 | Then 120 | Fact.NetAmount=143.320007; 121 | Fact.Result=true; 122 | } 123 | ``` 124 | -------------------------------------------------------------------------------- /docs/en/Binary_Rule_File_en.md: -------------------------------------------------------------------------------- 1 | # Storing and Loading GRB file. 2 | 3 | [![Binary_Rule_File_cn](https://github.com/yammadev/flag-icons/blob/master/png/CN.png?raw=true)](../cn/Binary_Rule_File_cn.md) 4 | [![Binary_Rule_File_de](https://github.com/yammadev/flag-icons/blob/master/png/DE.png?raw=true)](../de/Binary_Rule_File_de.md) 5 | [![Binary_Rule_File_en](https://github.com/yammadev/flag-icons/blob/master/png/GB.png?raw=true)](../en/Binary_Rule_File_en.md) 6 | [![Binary_Rule_File_id](https://github.com/yammadev/flag-icons/blob/master/png/ID.png?raw=true)](../id/Binary_Rule_File_id.md) 7 | [![Binary_Rule_File_pl](https://github.com/yammadev/flag-icons/blob/master/png/PL.png?raw=true)](../pl/Binary_Rule_File_pl.md) 8 | 9 | [About](About_en.md) | [Tutorial](Tutorial_en.md) | [Rule Engine](RuleEngine_en.md) | [GRL](GRL_en.md) | [GRL JSON](GRL_JSON_en.md) | [RETE Algorithm](RETE_en.md) | [Functions](Function_en.md) | [FAQ](FAQ_en.md) | [Benchmark](Benchmarking_en.md) 10 | 11 | --- 12 | 13 | When you loading huge amount (hundreds) of rules in GRL script into `KnowledgeLibrary`, e.g. when you start 14 | the engine, you may notice that it may take some time to load, some times it could go up for a couple of minutes. 15 | This is due to the syntax parsing done by ANTLR4. Don't get me wrong, ANTLR is a great tool and it does its job very well. 16 | But obviously, tens of thousands of lines in a script file is no small task, for any parser tools. 17 | 18 | So the idea is, to store all the rule in a binary file. So it load faster next time. Just like 19 | a compiler. You compile your text GRL script and the result is a binary file (GRB) which load 10 time faster. 20 | 21 | The workflow is as following : As a Rule author, you still work in the textual GRL script. When you want to release your rule set, 22 | you can "compile" it into GRB binary file. The you ditribute that GRB into your server and have the server load 23 | from GRB. 24 | 25 | ## Storing KnowledgeBase into GRB 26 | 27 | First, you should have a `KnowledgeLibrary` containing the `KnowledgeBase` you want to store into GRB. 28 | Normally you would load a GRL into your library as follows : 29 | 30 | ```go 31 | lib := ast.NewKnowledgeLibrary() 32 | rb := builder.NewRuleBuilder(lib) 33 | err := rb.BuildRuleFromResource("HugeRuleSet", "0.0.1", pkg.NewFileResource("HugeRuleSet.grl")) 34 | assert.NoError(t, err) 35 | ``` 36 | 37 | Second, you can now save the knowledge base into GRB as follows: 38 | 39 | ```go 40 | f, err := os.OpenFile("HugeRuleSet.grb", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) 41 | assert.Nil(t, err) 42 | 43 | // Save the knowledge base into the file and close it. 44 | err = lib.StoreKnowledgeBaseToWriter(f, "HugeRuleSet", "0.0.1") 45 | assert.Nil(t, err) 46 | _ = f.Close() 47 | ``` 48 | 49 | Your GRB file is now contains all rules in the specified knowledge base 50 | and ready for future loading. 51 | 52 | Yes, the GRB file size is inflated like 10 times compared to the GRL one, 53 | But it, most of the time, like that when you compile some script into its 54 | compiled binary form. (.java vs .class, .c vs .exe, go vs executable) 55 | 56 | ## Loading GRB into KnowledgeLibrary 57 | 58 | Loading GRB is much simpler. No need a builder. 59 | 60 | ```go 61 | lib := ast.NewKnowledgeLibrary() 62 | 63 | // Open the existing safe file 64 | f, err := os.Open("HugeRuleSet.grb") 65 | assert.Nil(t, err) 66 | 67 | // Load the file directly into the library and close the file 68 | // btw, you should not use the blueprint_kb in your engine execution. 69 | bluerint_kb, err := lib.LoadKnowledgeBaseFromReader(f2, true) 70 | assert.Nil(t, err) 71 | _ = f.Close() 72 | ``` 73 | 74 | There you go !!!, the GRB is loaded into `KnowledgeLibrary` 75 | You can obtain the knowledge base normally. 76 | 77 | ```go 78 | kb := lib.NewKnowledgeBaseInstance("HugeRuleSet", "0.0.1") 79 | ``` 80 | 81 | One thing, if in your `KnowledgeLibrary` already contains the same `KnowledgeBase` name and version 82 | to the one in the GRB, that `KnowledgeBase` in the library will be overwritten. 83 | -------------------------------------------------------------------------------- /docs/en/GRL_Literals_en.md: -------------------------------------------------------------------------------- 1 | # Grule Rule Language (GRL) Literals 2 | 3 | [![GRL_Literals_cn](https://github.com/yammadev/flag-icons/blob/master/png/CN.png?raw=true)](../cn/GRL_Literals_cn.md) 4 | [![GRL_Literals_de](https://github.com/yammadev/flag-icons/blob/master/png/DE.png?raw=true)](../de/GRL_Literals_de.md) 5 | [![GRL_Literals_en](https://github.com/yammadev/flag-icons/blob/master/png/GB.png?raw=true)](../en/GRL_Literals_en.md) 6 | [![GRL_Literals_id](https://github.com/yammadev/flag-icons/blob/master/png/ID.png?raw=true)](../id/GRL_Literals_id.md) 7 | [![GRL_Literals_pl](https://github.com/yammadev/flag-icons/blob/master/png/PL.png?raw=true)](../pl/GRL_Literals_pl.md) 8 | 9 | [About](About_en.md) | [Tutorial](Tutorial_en.md) | [Rule Engine](RuleEngine_en.md) | [GRL](GRL_en.md) | [RETE Algorithm](RETE_en.md) | [Functions](Function_en.md) | [FAQ](FAQ_en.md) | [Benchmark](Benchmarking_en.md) 10 | 11 | --- 12 | 13 | ## String Literals 14 | 15 | In GRL, a string is any sequence of characters surrounded by either a single `'` or double `"` quotes. 16 | If the literal is started with single quote, then it must be terminated by single quote. The same for double quote. 17 | 18 | For example 19 | 20 | ```go 21 | "a quick brown fox jumps over a lazy dog" 22 | ``` 23 | 24 | or 25 | 26 | ```go 27 | 'a quick brown fox jumps over a lazy dog' 28 | ``` 29 | 30 | A string literal may contain white space characters such as `space`, `tab` or a 31 | `carriage-return` 32 | 33 | For example 34 | 35 | ```go 36 | "A quick brown fox 37 | Jumps 38 | Over a lazy dog" 39 | ``` 40 | 41 | To include special characters in string, you can *escape* them as is normal in Go 42 | 43 | For example 44 | 45 | ```go 46 | "This string contains \" Double Quote" 47 | ``` 48 | 49 | ## Number Literals 50 | 51 | GRL follows literal numbering as specified by the Golang specification as best 52 | as it can. It understands various numbers notation such as 53 | Base10 (Decimals), Base8 (Octal) and Base16 (Hex). Base2 (Binary) is not yet implemented. 54 | 55 | ### Integer Literal 56 | 57 | #### Decimals 58 | 59 | In Base 10 - For Example 60 | 61 | ```go 62 | 0 63 | 123 64 | 34592 65 | -1 66 | -47234 67 | ``` 68 | 69 | In Base 8 - For Example 70 | 71 | ```go 72 | 01 73 | 07 74 | 010 75 | 017 76 | -034 77 | -045 78 | 04328 (error : invalid octal number) 79 | ``` 80 | 81 | In Base 16 - For Example 82 | 83 | ```go 84 | 0x1 85 | 0xF 86 | 0x10 87 | 0x1F 88 | 0xFF00 89 | -0x12 90 | -0x00ABCD 91 | -0x890AbCdEf 92 | ``` 93 | 94 | ### Real Numbers / Float Literals 95 | 96 | In Base 10 - For Example 97 | 98 | ```go 99 | 0. 100 | 72.40 101 | 072.40 102 | 2.71828 103 | 1.e+0 104 | 6.67428e-11 105 | 1E6 106 | .25 107 | .12345E+5 108 | -072.40 109 | -2.71828 110 | -1.e+0 111 | ``` 112 | 113 | In Base 16 - For Example 114 | 115 | ```go 116 | 0x1p-2 117 | 0x2.p10 118 | 0x1.Fp+0 119 | 0X.8p-0 120 | 0X_1FFFP-16 121 | 0x15e-2 122 | ``` 123 | 124 | ## Boolean Literal 125 | 126 | ```go 127 | true 128 | TRUE 129 | True 130 | TrUe 131 | false 132 | False 133 | FALSE 134 | FaLsE 135 | ``` 136 | -------------------------------------------------------------------------------- /docs/en/MatchingRules_en.md: -------------------------------------------------------------------------------- 1 | ## Fetch Matching Rules (Order by Salience) 2 | 3 | [![MatchingRules_cn](https://github.com/yammadev/flag-icons/blob/master/png/CN.png?raw=true)](../cn/MatchingRules_cn.md) 4 | [![MatchingRules_de](https://github.com/yammadev/flag-icons/blob/master/png/DE.png?raw=true)](../de/MatchingRules_de.md) 5 | [![MatchingRules_en](https://github.com/yammadev/flag-icons/blob/master/png/GB.png?raw=true)](../en/MatchingRules_en.md) 6 | [![MatchingRules_id](https://github.com/yammadev/flag-icons/blob/master/png/ID.png?raw=true)](../id/MatchingRules_id.md) 7 | [![MatchingRules_pl](https://github.com/yammadev/flag-icons/blob/master/png/PL.png?raw=true)](../pl/MatchingRules_pl.md) 8 | 9 | `FetchMatchingRules` in `GruleEngine.go` fetches all the rules valid for a given fact and returns a list of `ast.RuleEntry` values ordered by salience property. 10 | 11 | ##### Rules: 12 | 13 | ```go 14 | rule DuplicateRule1 "Duplicate Rule 1" salience 5 { 15 | when 16 | (Fact.Distance > 5000 && Fact.Duration > 120) && (Fact.Result == false) 17 | Then 18 | Fact.NetAmount=143.320007; 19 | Fact.Result=true; 20 | } 21 | 22 | rule DuplicateRule2 "Duplicate Rule 2" salience 6 { 23 | when 24 | (Fact.Distance > 5000 && Fact.Duration > 120) && (Fact.Result == false) 25 | Then 26 | Fact.NetAmount=143.320007; 27 | Fact.Result=true; 28 | } 29 | 30 | rule DuplicateRule3 "Duplicate Rule 3" salience 7 { 31 | when 32 | (Fact.Distance > 5000 && Fact.Duration > 120) && (Fact.Result == false) 33 | Then 34 | Fact.NetAmount=143.320007; 35 | Fact.Result=true; 36 | } 37 | 38 | rule DuplicateRule4 "Duplicate Rule 4" salience 8 { 39 | when 40 | (Fact.Distance > 5000 && Fact.Duration > 120) && (Fact.Result == false) 41 | Then 42 | Fact.NetAmount=143.320007; 43 | Fact.Result=true; 44 | } 45 | 46 | rule UniqueRule5 "Unique Rule 5" salience 0 { 47 | when 48 | (Fact.Distance > 5000 && Fact.Duration == 120) && (Fact.Result == false) 49 | Then 50 | Output.NetAmount=143.320007; 51 | Fact.Result=true; 52 | } 53 | ``` 54 | 55 | All the above rules are duplicate ones except for a differing salience value, except `UniqueRule5`. As we all know, a rule with higher salience has a higher priority and will get executed before a rule with lower salience if there is a conflict. 56 | 57 | ```go 58 | fact := &Fact{ 59 | Distance: 6000, 60 | Duration: 121, 61 | } 62 | ``` 63 | 64 | ##### FetchMatchingRules: 65 | 66 | ```go 67 | engine := engine.NewGruleEngine() 68 | ruleEntries, err := engine.FetchMatchingRules(dctx, kb) 69 | if err != nil { 70 | panic(err) 71 | } 72 | ``` 73 | 74 | #### result: 75 | 76 | ```go 77 | Returns []*ast.RuleEntry (All Matching Rule Entries sorted by Salience) 78 | 79 | rule DuplicateRule4 "Duplicate Rule 4" salience 8 { 80 | when 81 | (Fact.Distance > 5000 && Fact.Duration > 120) && (Fact.Result == false) 82 | Then 83 | Fact.NetAmount=143.320007; 84 | Fact.Result=true; 85 | } 86 | 87 | rule DuplicateRule3 "Duplicate Rule 3" salience 7 { 88 | when 89 | (Fact.Distance > 5000 && Fact.Duration > 120) && (Fact.Result == false) 90 | Then 91 | Fact.NetAmount=143.320007; 92 | Fact.Result=true; 93 | } 94 | 95 | rule DuplicateRule2 "Duplicate Rule 2" salience 6 { 96 | when 97 | (Fact.Distance > 5000 && Fact.Duration > 120) && (Fact.Result == false) 98 | Then 99 | Fact.NetAmount=143.320007; 100 | Fact.Result=true; 101 | } 102 | 103 | rule DuplicateRule1 "Duplicate Rule 1" salience 5 { 104 | when 105 | (Fact.Distance > 5000 && Fact.Duration > 120) && (Fact.Result == false) 106 | Then 107 | Fact.NetAmount=143.320007; 108 | Fact.Result=true; 109 | } 110 | ``` 111 | -------------------------------------------------------------------------------- /docs/id/GRL_Literals_id.md: -------------------------------------------------------------------------------- 1 | # Literal-literal Dalam Grule Rule Language (GRL) 2 | 3 | --- 4 | 5 | :construction: 6 | __THIS PAGE IS BEING TRANSLATED__ 7 | :construction: 8 | 9 | :construction_worker: Contributors are invited. Please read [CONTRIBUTING](../../CONTRIBUTING.md) and [CONTRIBUTING TRANSLATION](../CONTRIBUTING_TRANSLATION.md) guidelines. 10 | 11 | :vulcan_salute: Please remove this note once you're done translating. 12 | 13 | --- 14 | 15 | 16 | [![GRL_Literals_cn](https://github.com/yammadev/flag-icons/blob/master/png/CN.png?raw=true)](../cn/GRL_Literals_cn.md) 17 | [![GRL_Literals_de](https://github.com/yammadev/flag-icons/blob/master/png/DE.png?raw=true)](../de/GRL_Literals_de.md) 18 | [![GRL_Literals_en](https://github.com/yammadev/flag-icons/blob/master/png/GB.png?raw=true)](../en/GRL_Literals_en.md) 19 | [![GRL_Literals_id](https://github.com/yammadev/flag-icons/blob/master/png/ID.png?raw=true)](../id/GRL_Literals_id.md) 20 | [![GRL_Literals_pl](https://github.com/yammadev/flag-icons/blob/master/png/PL.png?raw=true)](../pl/GRL_Literals_pl.md) 21 | 22 | [Tentang Grule](About_id.md) | [Tutorial](Tutorial_id.md) | [Rule Engine](RuleEngine_id.md) | [GRL](GRL_id.md) | [GRL JSON](GRL_JSON_id.md) | [Algoritma RETE](RETE_id.md) | [Fungsi-fungsi](Function_id.md) | [FAQ](FAQ_id.md) | [Benchmark](Benchmarking_id.md) 23 | 24 | --- 25 | 26 | ## String Literals 27 | 28 | In GRL, a string is any sequence of characters surrounded by either a single `'` or double `"` quotes. 29 | If the literal is started with single quote, then it must be terminated by single quote. The same for double quote. 30 | 31 | For example 32 | 33 | ```go 34 | "a quick brown fox jumps over a lazy dog" 35 | ``` 36 | 37 | or 38 | 39 | ```go 40 | 'a quick brown fox jumps over a lazy dog' 41 | ``` 42 | 43 | A string literal may contain white space characters such as `space`, `tab` or a 44 | `carriage-return` 45 | 46 | For example 47 | 48 | ```go 49 | "A quick brown fox 50 | Jumps 51 | Over a lazy dog" 52 | ``` 53 | 54 | To include special characters in string, you can *escape* them as is normal in Go 55 | 56 | For example 57 | 58 | ```go 59 | "This string contains \" Double Quote" 60 | ``` 61 | 62 | ## Number Literals 63 | 64 | GRL follows literal numbering as specified by the Golang specification as best 65 | as it can. It understands various numbers notation such as 66 | Base10 (Decimals), Base8 (Octal) and Base16 (Hex). Base2 (Binary) is not yet implemented. 67 | 68 | ### Integer Literal 69 | 70 | #### Decimals 71 | 72 | In Base 10 - For Example 73 | 74 | ```go 75 | 0 76 | 123 77 | 34592 78 | -1 79 | -47234 80 | ``` 81 | 82 | In Base 8 - For Example 83 | 84 | ```go 85 | 01 86 | 07 87 | 010 88 | 017 89 | -034 90 | -045 91 | 04328 (error : invalid octal number) 92 | ``` 93 | 94 | In Base 16 - For Example 95 | 96 | ```go 97 | 0x1 98 | 0xF 99 | 0x10 100 | 0x1F 101 | 0xFF00 102 | -0x12 103 | -0x00ABCD 104 | -0x890AbCdEf 105 | ``` 106 | 107 | ### Real Numbers / Float Literals 108 | 109 | In Base 10 - For Example 110 | 111 | ```go 112 | 0. 113 | 72.40 114 | 072.40 115 | 2.71828 116 | 1.e+0 117 | 6.67428e-11 118 | 1E6 119 | .25 120 | .12345E+5 121 | -072.40 122 | -2.71828 123 | -1.e+0 124 | ``` 125 | 126 | In Base 16 - For Example 127 | 128 | ```go 129 | 0x1p-2 130 | 0x2.p10 131 | 0x1.Fp+0 132 | 0X.8p-0 133 | 0X_1FFFP-16 134 | 0x15e-2 135 | ``` 136 | 137 | ## Boolean Literal 138 | 139 | ```go 140 | true 141 | TRUE 142 | True 143 | TrUe 144 | false 145 | False 146 | FALSE 147 | FaLsE 148 | ``` 149 | -------------------------------------------------------------------------------- /docs/id/MatchingRules_id.md: -------------------------------------------------------------------------------- 1 | ## Fetch Matching Rules (Order by Salience) 2 | 3 | --- 4 | 5 | :construction: 6 | __THIS PAGE IS BEING TRANSLATED__ 7 | :construction: 8 | 9 | :construction_worker: Contributors are invited. Please read [CONTRIBUTING](../../CONTRIBUTING.md) and [CONTRIBUTING TRANSLATION](../CONTRIBUTING_TRANSLATION.md) guidelines. 10 | 11 | :vulcan_salute: Please remove this note once you're done translating. 12 | 13 | --- 14 | 15 | 16 | [![MatchingRules_cn](https://github.com/yammadev/flag-icons/blob/master/png/CN.png?raw=true)](../cn/MatchingRules_cn.md) 17 | [![MatchingRules_de](https://github.com/yammadev/flag-icons/blob/master/png/DE.png?raw=true)](../de/MatchingRules_de.md) 18 | [![MatchingRules_en](https://github.com/yammadev/flag-icons/blob/master/png/GB.png?raw=true)](../en/MatchingRules_en.md) 19 | [![MatchingRules_id](https://github.com/yammadev/flag-icons/blob/master/png/ID.png?raw=true)](../id/MatchingRules_id.md) 20 | [![MatchingRules_pl](https://github.com/yammadev/flag-icons/blob/master/png/PL.png?raw=true)](../pl/MatchingRules_pl.md) 21 | 22 | `FetchMatchingRules` in `GruleEngine.go` fetches all the rules valid for a given fact and returns a list of `ast.RuleEntry` values ordered by salience property. 23 | 24 | ##### Rules: 25 | 26 | ```go 27 | rule DuplicateRule1 "Duplicate Rule 1" salience 5 { 28 | when 29 | (Fact.Distance > 5000 && Fact.Duration > 120) && (Fact.Result == false) 30 | Then 31 | Fact.NetAmount=143.320007; 32 | Fact.Result=true; 33 | } 34 | 35 | rule DuplicateRule2 "Duplicate Rule 2" salience 6 { 36 | when 37 | (Fact.Distance > 5000 && Fact.Duration > 120) && (Fact.Result == false) 38 | Then 39 | Fact.NetAmount=143.320007; 40 | Fact.Result=true; 41 | } 42 | 43 | rule DuplicateRule3 "Duplicate Rule 3" salience 7 { 44 | when 45 | (Fact.Distance > 5000 && Fact.Duration > 120) && (Fact.Result == false) 46 | Then 47 | Fact.NetAmount=143.320007; 48 | Fact.Result=true; 49 | } 50 | 51 | rule DuplicateRule4 "Duplicate Rule 4" salience 8 { 52 | when 53 | (Fact.Distance > 5000 && Fact.Duration > 120) && (Fact.Result == false) 54 | Then 55 | Fact.NetAmount=143.320007; 56 | Fact.Result=true; 57 | } 58 | 59 | rule UniqueRule5 "Unique Rule 5" salience 0 { 60 | when 61 | (Fact.Distance > 5000 && Fact.Duration == 120) && (Fact.Result == false) 62 | Then 63 | Output.NetAmount=143.320007; 64 | Fact.Result=true; 65 | } 66 | ``` 67 | 68 | All the above rules are duplicate ones except for a differing salience value, except `UniqueRule5`. As we all know, a rule with higher salience has a higher priority and will get executed before a rule with lower salience if there is a conflict. 69 | 70 | ```go 71 | fact := &Fact{ 72 | Distance: 6000, 73 | Duration: 121, 74 | } 75 | ``` 76 | 77 | ##### FetchMatchingRules: 78 | 79 | ```go 80 | engine := engine.NewGruleEngine() 81 | ruleEntries, err := engine.FetchMatchingRules(dctx, kb) 82 | if err != nil { 83 | panic(err) 84 | } 85 | ``` 86 | 87 | #### result: 88 | 89 | ```go 90 | Returns []*ast.RuleEntry (All Matching Rule Entries sorted by Salience) 91 | 92 | rule DuplicateRule4 "Duplicate Rule 4" salience 8 { 93 | when 94 | (Fact.Distance > 5000 && Fact.Duration > 120) && (Fact.Result == false) 95 | Then 96 | Fact.NetAmount=143.320007; 97 | Fact.Result=true; 98 | } 99 | 100 | rule DuplicateRule3 "Duplicate Rule 3" salience 7 { 101 | when 102 | (Fact.Distance > 5000 && Fact.Duration > 120) && (Fact.Result == false) 103 | Then 104 | Fact.NetAmount=143.320007; 105 | Fact.Result=true; 106 | } 107 | 108 | rule DuplicateRule2 "Duplicate Rule 2" salience 6 { 109 | when 110 | (Fact.Distance > 5000 && Fact.Duration > 120) && (Fact.Result == false) 111 | Then 112 | Fact.NetAmount=143.320007; 113 | Fact.Result=true; 114 | } 115 | 116 | rule DuplicateRule1 "Duplicate Rule 1" salience 5 { 117 | when 118 | (Fact.Distance > 5000 && Fact.Duration > 120) && (Fact.Result == false) 119 | Then 120 | Fact.NetAmount=143.320007; 121 | Fact.Result=true; 122 | } 123 | ``` 124 | -------------------------------------------------------------------------------- /docs/pl/Binary_Rule_File_pl.md: -------------------------------------------------------------------------------- 1 | # Przechowywanie i ładowanie pliku GRB. 2 | 3 | [![Binary_Rule_File_cn](https://github.com/yammadev/flag-icons/blob/master/png/CN.png?raw=true)](../cn/Binary_Rule_File_cn.md) 4 | [![Binary_Rule_File_de](https://github.com/yammadev/flag-icons/blob/master/png/DE.png?raw=true)](../de/Binary_Rule_File_de.md) 5 | [![Binary_Rule_File_en](https://github.com/yammadev/flag-icons/blob/master/png/GB.png?raw=true)](../en/Binary_Rule_File_en.md) 6 | [![Binary_Rule_File_id](https://github.com/yammadev/flag-icons/blob/master/png/ID.png?raw=true)](../id/Binary_Rule_File_id.md) 7 | [![Binary_Rule_File_pl](https://github.com/yammadev/flag-icons/blob/master/png/PL.png?raw=true)](../pl/Binary_Rule_File_pl.md) 8 | 9 | [About](About_pl.md) | [Tutorial](Tutorial_pl.md) | [Rule Engine](RuleEngine_pl.md) | [GRL](GRL_pl.md) | [GRL JSON](GRL_JSON_pl.md) | [RETE Algorithm](RETE_pl.md) | [Functions](Function_pl.md) | [FAQ](FAQ_pl.md) | [Benchmark](Benchmarking_pl.md) 10 | 11 | --- 12 | 13 | Podczas ładowania dużej ilości (setek) reguł w skrypcie GRL do `KnowledgeLibrary`, np. po uruchomieniu silnika, można zauważyć, że ładowanie może zająć trochę czasu, czasami nawet kilka minut. Wynika to z parsowania składni przez ANTLR4. Nie zrozum mnie źle, ANTLR jest świetnym narzędziem i bardzo dobrze wykonuje swoją pracę. Ale oczywiście dziesiątki tysięcy linii w pliku skryptowym to nie lada wyzwanie dla każdego narzędzia parsującego. 14 | 15 | Chodzi więc o to, aby wszystkie reguły przechowywać w pliku binarnym. Dzięki temu następnym razem załaduje się on szybciej. Zupełnie jak kompilator. Kompilujesz swój tekstowy skrypt GRL, a rezultatem jest plik binarny (GRB), który ładuje się 10 razy szybciej. 16 | 17 | Przebieg pracy jest następujący: Jako autor reguł nadal pracujesz w tekstowym skrypcie GRL. Gdy chcesz opublikować swój zbiór reguł, możesz go "skompilować" do pliku binarnego GRB. Następnie przesyłasz GRB na swój serwer, a serwer ładuje się z GRB. 18 | 19 | ## Przechowywanie bazy wiedzy w GRB 20 | 21 | Po pierwsze, powinieneś mieć `KnowledgeLibrary` zawierającą `KnowledgeBase`, którą chcesz przechowywać w GRB. 22 | Zwykle ładuje się GRL do biblioteki w następujący sposób : 23 | 24 | ```go 25 | lib := ast.NewKnowledgeLibrary() 26 | rb := builder.NewRuleBuilder(lib) 27 | err := rb.BuildRuleFromResource("HugeRuleSet", "0.0.1", pkg.NewFileResource("HugeRuleSet.grl")) 28 | assert.NoError(t, err) 29 | ``` 30 | 31 | Po drugie, można teraz zapisać bazę wiedzy w GRB w następujący sposób: 32 | 33 | ```go 34 | f, err := os.OpenFile("HugeRuleSet.grb", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) 35 | assert.Nil(t, err) 36 | 37 | // Save the knowledge base into the file and close it. 38 | err = lib.StoreKnowledgeBaseToWriter(f, "HugeRuleSet", "0.0.1") 39 | assert.Nil(t, err) 40 | _ = f.Close() 41 | ``` 42 | 43 | Plik GRB zawiera teraz wszystkie reguły z określonej bazy wiedzy i jest gotowy do załadowania w przyszłości. 44 | 45 | Tak, rozmiar pliku GRB jest 10-krotnie większy niż GRL, ale najczęściej dzieje się tak, gdy kompilujesz jakiś skrypt do jego postaci binarnej. (.java vs .class, .c vs .exe, go vs executable) 46 | 47 | ## Ładowanie GRB do Biblioteki Wiedzy 48 | 49 | Ładowanie GRB jest znacznie prostsze. Nie potrzeba konstruktora. 50 | 51 | ```go 52 | lib := ast.NewKnowledgeLibrary() 53 | 54 | // Open the existing safe file 55 | f, err := os.Open("HugeRuleSet.grb") 56 | assert.Nil(t, err) 57 | 58 | // Load the file directly into the library and close the file 59 | // btw, you should not use the blueprint_kb in your engine execution. 60 | bluerint_kb, err := lib.LoadKnowledgeBaseFromReader(f2, true) 61 | assert.Nil(t, err) 62 | _ = f.Close() 63 | ``` 64 | 65 | Proszę bardzo!!!, GRB jest załadowana do `KnowledgeLibrary` Możesz normalnie uzyskać bazę wiedzy. 66 | 67 | ```go 68 | kb := lib.NewKnowledgeBaseInstance("HugeRuleSet", "0.0.1") 69 | ``` 70 | 71 | Jedna rzecz, jeśli `KnowledgeLibrary` zawiera już taką samą bazę wiedzy jak ta w GRB, ta `KnowledgeBase` w bibliotece zostanie nadpisana. 72 | -------------------------------------------------------------------------------- /docs/pl/GRL_Literals_pl.md: -------------------------------------------------------------------------------- 1 | # Literały Grule Rule Language (GRL) 2 | 3 | [![GRL_Literals_cn](https://github.com/yammadev/flag-icons/blob/master/png/CN.png?raw=true)](../cn/GRL_Literals_cn.md) 4 | [![GRL_Literals_de](https://github.com/yammadev/flag-icons/blob/master/png/DE.png?raw=true)](../de/GRL_Literals_de.md) 5 | [![GRL_Literals_en](https://github.com/yammadev/flag-icons/blob/master/png/GB.png?raw=true)](../en/GRL_Literals_en.md) 6 | [![GRL_Literals_id](https://github.com/yammadev/flag-icons/blob/master/png/ID.png?raw=true)](../id/GRL_Literals_id.md) 7 | [![GRL_Literals_pl](https://github.com/yammadev/flag-icons/blob/master/png/PL.png?raw=true)](../pl/GRL_Literals_pl.md) 8 | 9 | [About](About_pl.md) | [Tutorial](Tutorial_pl.md) | [Rule Engine](RuleEngine_pl.md) | [GRL](GRL_pl.md) | [GRL JSON](GRL_JSON_pl.md) | [RETE Algorithm](RETE_pl.md) | [Functions](Function_pl.md) | [FAQ](FAQ_pl.md) | [Benchmark](Benchmarking_pl.md) 10 | 11 | --- 12 | 13 | ## Literały łańcuchowe 14 | 15 | W GRL ciąg znaków to dowolna sekwencja znaków otoczona pojedynczym `'` lub podwójnym `'` cudzysłowem. 16 | Jeśli literał zaczyna się od pojedynczego cudzysłowu, to musi być zakończony pojedynczym cudzysłowem. To samo dotyczy cudzysłowów podwójnych. 17 | 18 | Na przykład 19 | 20 | ```go 21 | "a quick brown fox jumps over a lazy dog" 22 | ``` 23 | 24 | lub 25 | 26 | ```go 27 | 'a quick brown fox jumps over a lazy dog' 28 | ``` 29 | 30 | Literał łańcuchowy może zawierać białe znaki spacji, takie jak `space`, `tab` lub `carriage-return` 31 | 32 | Na przykład 33 | 34 | ```go 35 | "A quick brown fox 36 | Jumps 37 | Over a lazy dog" 38 | ``` 39 | 40 | Aby zawrzeć znaki specjalne w łańcuchu, można je *escape*, tak jak to jest normalnie w Go 41 | 42 | Na przykład 43 | 44 | ```go 45 | "This string contains \" Double Quote" 46 | ``` 47 | 48 | ## Literały liczbowe 49 | 50 | GRL stosuje numerację literalną określoną przez specyfikację Golanga najlepiej jak potrafi. Rozumie różne notacje liczbowe, takie jak Base10 (dziesiętne), Base8 (ósemkowe) i Base16 (szesnastkowe). Base2 (Binary) nie jest jeszcze zaimplementowana. 51 | 52 | ### Literały Liczb Całkowitych 53 | 54 | #### Liczby dziesiętne 55 | 56 | W Base 10 - na przykład 57 | 58 | ```go 59 | 0 60 | 123 61 | 34592 62 | -1 63 | -47234 64 | ``` 65 | 66 | W Base 8 - na przykład 67 | 68 | ```go 69 | 01 70 | 07 71 | 010 72 | 017 73 | -034 74 | -045 75 | 04328 (error : invalid octal number) 76 | ``` 77 | 78 | W Base 16 - na przykład 79 | 80 | ```go 81 | 0x1 82 | 0xF 83 | 0x10 84 | 0x1F 85 | 0xFF00 86 | -0x12 87 | -0x00ABCD 88 | -0x890AbCdEf 89 | ``` 90 | 91 | ### Liczby rzeczywiste / Literały zmiennoprzecinkowe 92 | 93 | W Base 10 - na przykład 94 | 95 | ```go 96 | 0. 97 | 72.40 98 | 072.40 99 | 2.71828 100 | 1.e+0 101 | 6.67428e-11 102 | 1E6 103 | .25 104 | .12345E+5 105 | -072.40 106 | -2.71828 107 | -1.e+0 108 | ``` 109 | 110 | W Base 16 - na przykład 111 | 112 | ```go 113 | 0x1p-2 114 | 0x2.p10 115 | 0x1.Fp+0 116 | 0X.8p-0 117 | 0X_1FFFP-16 118 | 0x15e-2 119 | ``` 120 | 121 | ## Literały Boolean 122 | 123 | ```go 124 | true 125 | TRUE 126 | True 127 | TrUe 128 | false 129 | False 130 | FALSE 131 | FaLsE 132 | ``` 133 | -------------------------------------------------------------------------------- /docs/pl/MatchingRules_pl.md: -------------------------------------------------------------------------------- 1 | ## Wyszukaj reguły dopasowania (kolejność według atrakcyjności) 2 | 3 | [![MatchingRules_cn](https://github.com/yammadev/flag-icons/blob/master/png/CN.png?raw=true)](../cn/MatchingRules_cn.md) 4 | [![MatchingRules_de](https://github.com/yammadev/flag-icons/blob/master/png/DE.png?raw=true)](../de/MatchingRules_de.md) 5 | [![MatchingRules_en](https://github.com/yammadev/flag-icons/blob/master/png/GB.png?raw=true)](../en/MatchingRules_en.md) 6 | [![MatchingRules_id](https://github.com/yammadev/flag-icons/blob/master/png/ID.png?raw=true)](../id/MatchingRules_id.md) 7 | [![MatchingRules_pl](https://github.com/yammadev/flag-icons/blob/master/png/PL.png?raw=true)](../pl/MatchingRules_pl.md) 8 | 9 | `FetchMatchingRules` w `GruleEngine.go` pobiera wszystkie reguły ważne dla danego faktu i zwraca listę wartości `ast.RuleEntry` uporządkowanych według właściwości salience. 10 | 11 | ##### Zasady: 12 | 13 | ```go 14 | rule DuplicateRule1 "Duplicate Rule 1" salience 5 { 15 | when 16 | (Fact.Distance > 5000 && Fact.Duration > 120) && (Fact.Result == false) 17 | Then 18 | Fact.NetAmount=143.320007; 19 | Fact.Result=true; 20 | } 21 | 22 | rule DuplicateRule2 "Duplicate Rule 2" salience 6 { 23 | when 24 | (Fact.Distance > 5000 && Fact.Duration > 120) && (Fact.Result == false) 25 | Then 26 | Fact.NetAmount=143.320007; 27 | Fact.Result=true; 28 | } 29 | 30 | rule DuplicateRule3 "Duplicate Rule 3" salience 7 { 31 | when 32 | (Fact.Distance > 5000 && Fact.Duration > 120) && (Fact.Result == false) 33 | Then 34 | Fact.NetAmount=143.320007; 35 | Fact.Result=true; 36 | } 37 | 38 | rule DuplicateRule4 "Duplicate Rule 4" salience 8 { 39 | when 40 | (Fact.Distance > 5000 && Fact.Duration > 120) && (Fact.Result == false) 41 | Then 42 | Fact.NetAmount=143.320007; 43 | Fact.Result=true; 44 | } 45 | 46 | rule UniqueRule5 "Unique Rule 5" salience 0 { 47 | when 48 | (Fact.Distance > 5000 && Fact.Duration == 120) && (Fact.Result == false) 49 | Then 50 | Output.NetAmount=143.320007; 51 | Fact.Result=true; 52 | } 53 | ``` 54 | 55 | Wszystkie powyższe reguły są duplikatami z wyjątkiem reguły `UniqueRule5`, która ma inną wartość ważności. Jak wiadomo, reguła o wyższej ważności ma wyższy priorytet i zostanie wykonana przed regułą o niższej ważności, jeśli wystąpi konflikt. 56 | 57 | ```go 58 | fact := &Fact{ 59 | Distance: 6000, 60 | Duration: 121, 61 | } 62 | ``` 63 | 64 | ##### FetchMatchingRules: 65 | 66 | ```go 67 | engine := engine.NewGruleEngine() 68 | ruleEntries, err := engine.FetchMatchingRules(dctx, kb) 69 | if err != nil { 70 | panic(err) 71 | } 72 | ``` 73 | 74 | #### wynik: 75 | 76 | ```go 77 | Returns []*ast.RuleEntry (All Matching Rule Entries sorted by Salience) 78 | 79 | rule DuplicateRule4 "Duplicate Rule 4" salience 8 { 80 | when 81 | (Fact.Distance > 5000 && Fact.Duration > 120) && (Fact.Result == false) 82 | Then 83 | Fact.NetAmount=143.320007; 84 | Fact.Result=true; 85 | } 86 | 87 | rule DuplicateRule3 "Duplicate Rule 3" salience 7 { 88 | when 89 | (Fact.Distance > 5000 && Fact.Duration > 120) && (Fact.Result == false) 90 | Then 91 | Fact.NetAmount=143.320007; 92 | Fact.Result=true; 93 | } 94 | 95 | rule DuplicateRule2 "Duplicate Rule 2" salience 6 { 96 | when 97 | (Fact.Distance > 5000 && Fact.Duration > 120) && (Fact.Result == false) 98 | Then 99 | Fact.NetAmount=143.320007; 100 | Fact.Result=true; 101 | } 102 | 103 | rule DuplicateRule1 "Duplicate Rule 1" salience 5 { 104 | when 105 | (Fact.Distance > 5000 && Fact.Duration > 120) && (Fact.Result == false) 106 | Then 107 | Fact.NetAmount=143.320007; 108 | Fact.Result=true; 109 | } 110 | ``` 111 | -------------------------------------------------------------------------------- /editor/EvaluationRoute.go: -------------------------------------------------------------------------------- 1 | package editor 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/hyperjumptech/grule-rule-engine/ast" 8 | "github.com/hyperjumptech/grule-rule-engine/builder" 9 | "github.com/hyperjumptech/grule-rule-engine/engine" 10 | "github.com/hyperjumptech/grule-rule-engine/pkg" 11 | mux "github.com/hyperjumptech/hyper-mux" 12 | "io/ioutil" 13 | "net/http" 14 | ) 15 | 16 | type JSONData struct { 17 | Name string 18 | JSONData string `json:"jsonText"` 19 | } 20 | 21 | type EvaluateRequest struct { 22 | GrlText string `json:"grlText"` 23 | Input []*JSONData `json:"jsonInput"` 24 | } 25 | 26 | func InitializeEvaluationRoute(router *mux.HyperMux) { 27 | router.AddRoute("/evaluate", http.MethodPost, func(writer http.ResponseWriter, reader *http.Request) { 28 | bodyBytes, err := ioutil.ReadAll(reader.Body) 29 | if err != nil { 30 | writer.WriteHeader(http.StatusInternalServerError) 31 | _, _ = writer.Write([]byte(fmt.Sprintf("error while reading body stream. got %v", err))) 32 | 33 | return 34 | } 35 | evReq := &EvaluateRequest{} 36 | err = json.Unmarshal(bodyBytes, evReq) 37 | if err != nil { 38 | writer.WriteHeader(http.StatusBadRequest) 39 | _, _ = writer.Write([]byte(fmt.Sprintf("wrong json format. got %v \n\n Json : %s", err, string(bodyBytes)))) 40 | 41 | return 42 | } 43 | 44 | dataContext := ast.NewDataContext() 45 | 46 | for _, JSONDat := range evReq.Input { 47 | jsonByte, err := base64.StdEncoding.DecodeString(JSONDat.JSONData) 48 | if err != nil { 49 | writer.WriteHeader(http.StatusBadRequest) 50 | _, _ = writer.Write([]byte(fmt.Sprintf("json data named %s should be sent using base64. got %v", JSONDat.Name, err))) 51 | 52 | return 53 | } 54 | err = dataContext.AddJSON(JSONDat.Name, jsonByte) 55 | if err != nil { 56 | writer.WriteHeader(http.StatusBadRequest) 57 | _, _ = writer.Write([]byte(fmt.Sprintf("invalid JSON data named %s when add json to context got %v", JSONDat.Name, err))) 58 | 59 | return 60 | } 61 | } 62 | 63 | knowledgeLibrary := ast.NewKnowledgeLibrary() 64 | ruleBuilder := builder.NewRuleBuilder(knowledgeLibrary) 65 | 66 | grlByte, err := base64.StdEncoding.DecodeString(evReq.GrlText) 67 | if err != nil { 68 | writer.WriteHeader(http.StatusBadRequest) 69 | _, _ = writer.Write([]byte(fmt.Sprintf("GRL data should be sent using base64. got %v", err))) 70 | 71 | return 72 | } 73 | 74 | err = ruleBuilder.BuildRuleFromResource("Evaluator", "0.0.1", pkg.NewBytesResource(grlByte)) 75 | if err != nil { 76 | writer.WriteHeader(http.StatusBadRequest) 77 | _, _ = writer.Write([]byte(fmt.Sprintf("invalid GRL : %s", err.Error()))) 78 | 79 | return 80 | } 81 | 82 | eng1 := &engine.GruleEngine{MaxCycle: 5} 83 | kb, err := knowledgeLibrary.NewKnowledgeBaseInstance("Evaluator", "0.0.1") 84 | if err != nil { 85 | writer.WriteHeader(http.StatusBadRequest) 86 | _, _ = writer.Write([]byte(fmt.Sprintf("Grule Error : %s", err.Error()))) 87 | 88 | return 89 | } 90 | err = eng1.Execute(dataContext, kb) 91 | if err != nil { 92 | writer.WriteHeader(http.StatusBadRequest) 93 | _, _ = writer.Write([]byte(fmt.Sprintf("Grule Error : %s", err.Error()))) 94 | 95 | return 96 | } 97 | 98 | respData := make(map[string]interface{}) 99 | for _, keyName := range dataContext.GetKeys() { 100 | respData[keyName] = dataContext.Get(keyName) 101 | } 102 | 103 | resultBytes, err := json.Marshal(respData) 104 | if err != nil { 105 | writer.WriteHeader(http.StatusInternalServerError) 106 | _, _ = writer.Write([]byte(fmt.Sprintf("Error marshaling result : %s", err.Error()))) 107 | 108 | return 109 | } 110 | 111 | writer.Header().Add("Content-Type", "application/json") 112 | writer.WriteHeader(http.StatusOK) 113 | _, _ = writer.Write(resultBytes) 114 | }) 115 | } 116 | -------------------------------------------------------------------------------- /editor/Server.go: -------------------------------------------------------------------------------- 1 | package editor 2 | 3 | import ( 4 | "context" 5 | mux "github.com/hyperjumptech/hyper-mux" 6 | "github.com/sirupsen/logrus" 7 | "log" 8 | "net/http" 9 | "os" 10 | "os/signal" 11 | "time" 12 | ) 13 | 14 | var ( 15 | hmux = mux.NewHyperMux() 16 | ) 17 | 18 | func Start() { 19 | InitializeStaticRoute(hmux) 20 | InitializeEvaluationRoute(hmux) 21 | 22 | var wait time.Duration 23 | address := "0.0.0.0:32123" 24 | 25 | logrus.Infof("Starting Editor at http://%s", address) 26 | 27 | srv := &http.Server{ 28 | Addr: address, 29 | WriteTimeout: 10 * time.Second, 30 | ReadTimeout: 10 * time.Second, 31 | IdleTimeout: 10 * time.Second, 32 | Handler: hmux, // Pass our instance of gorilla/mux in. 33 | } 34 | 35 | startTime := time.Now() 36 | 37 | // Run our server in a goroutine so that it doesn't block. 38 | go func() { 39 | if err := srv.ListenAndServe(); err != nil { 40 | log.Println(err) 41 | } 42 | }() 43 | 44 | channel := make(chan os.Signal, 1) 45 | // We'll accept graceful shutdowns when quit via SIGINT (Ctrl+C) 46 | // SIGKILL, SIGQUIT or SIGTERM (Ctrl+/) will not be caught. 47 | signal.Notify(channel, os.Interrupt) 48 | 49 | // Block until we receive our signal. 50 | <-channel 51 | 52 | // Create a deadline to wait for. 53 | ctx, cancel := context.WithTimeout(context.Background(), wait) 54 | defer cancel() 55 | // Doesn't block if no connections, but will otherwise wait 56 | // until the timeout deadline. 57 | srv.Shutdown(ctx) 58 | // Optionally, you could run srv.Shutdown in a goroutine and block on 59 | // <-ctx.Done() if your application should wait for other services 60 | // to finalize based on context cancellation. 61 | dur := time.Now().Sub(startTime) 62 | logrus.Infof("Shutting down. This Satpam been protecting the world for %s", dur.String()) 63 | os.Exit(0) 64 | 65 | } 66 | -------------------------------------------------------------------------------- /editor/StaticRoute.go: -------------------------------------------------------------------------------- 1 | package editor 2 | 3 | import ( 4 | "embed" 5 | "github.com/hyperjumptech/grule-rule-engine/editor/mime" 6 | mux "github.com/hyperjumptech/hyper-mux" 7 | "github.com/sirupsen/logrus" 8 | "net/http" 9 | "os" 10 | "strings" 11 | ) 12 | 13 | //go:embed statics 14 | var fileSyst embed.FS 15 | 16 | type FileData struct { 17 | Bytes []byte 18 | ContentType string 19 | } 20 | 21 | func IsDir(path string) bool { 22 | for _, s := range GetPathTree("static") { 23 | if s == "[DIR]"+path { 24 | return true 25 | } 26 | } 27 | 28 | return false 29 | } 30 | 31 | func GetPathTree(path string) []string { 32 | logrus.Debugf("Into %s", path) 33 | var entries []os.DirEntry 34 | var err error 35 | if strings.HasPrefix(path, "./") { 36 | entries, err = fileSyst.ReadDir(path[2:]) 37 | } else { 38 | entries, err = fileSyst.ReadDir(path) 39 | } 40 | ret := make([]string, 0) 41 | if err != nil { 42 | return ret 43 | } 44 | logrus.Debugf("Path %s %d etries", path, len(entries)) 45 | for _, e := range entries { 46 | if e.IsDir() { 47 | ret = append(ret, "[DIR]"+path+"/"+e.Name()) 48 | ret = append(ret, GetPathTree(path+"/"+e.Name())...) 49 | } else { 50 | ret = append(ret, path+"/"+e.Name()) 51 | } 52 | } 53 | 54 | return ret 55 | } 56 | 57 | func GetFile(path string) (*FileData, error) { 58 | bytes, err := fileSyst.ReadFile(path) 59 | if err != nil { 60 | return nil, err 61 | } 62 | mimeType, err := mime.GetMimeForFileName(path) 63 | if err != nil { 64 | 65 | return &FileData{ 66 | Bytes: bytes, 67 | ContentType: http.DetectContentType(bytes), 68 | }, nil 69 | } 70 | 71 | return &FileData{ 72 | Bytes: bytes, 73 | ContentType: mimeType, 74 | }, nil 75 | } 76 | 77 | func InitializeStaticRoute(router *mux.HyperMux) { 78 | for _, p := range GetPathTree("statics") { 79 | if !strings.HasPrefix(p, "[DIR]") { 80 | path := p[len("statics"):] 81 | router.AddRoute(path, http.MethodGet, StaticHandler(p)) 82 | } 83 | } 84 | 85 | router.AddRoute("/", http.MethodGet, func(writer http.ResponseWriter, request *http.Request) { 86 | writer.Header().Add("Location", "/index.html") 87 | writer.WriteHeader(http.StatusMovedPermanently) 88 | }) 89 | } 90 | 91 | func StaticHandler(path string) func(writer http.ResponseWriter, request *http.Request) { 92 | fData, err := GetFile(path) 93 | if err != nil { 94 | 95 | return func(writer http.ResponseWriter, request *http.Request) { 96 | _, _ = writer.Write([]byte(err.Error())) 97 | writer.WriteHeader(http.StatusInternalServerError) 98 | } 99 | } 100 | 101 | return func(writer http.ResponseWriter, request *http.Request) { 102 | writer.Header().Add("Content-Type", fData.ContentType) 103 | writer.WriteHeader(http.StatusOK) 104 | _, _ = writer.Write(fData.Bytes) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /editor/cmd/Main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/hyperjumptech/grule-rule-engine/editor" 5 | "github.com/sirupsen/logrus" 6 | ) 7 | 8 | func main() { 9 | logrus.SetLevel(logrus.TraceLevel) 10 | editor.Start() 11 | } 12 | -------------------------------------------------------------------------------- /editor/statics/css/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 5rem; 3 | } 4 | .starter-template { 5 | padding: 3rem 1.5rem; 6 | text-align: center; 7 | } 8 | 9 | .flex-box { 10 | display: flex; 11 | justify-content: center; 12 | } 13 | 14 | .rule { 15 | font-weight: bold; 16 | } -------------------------------------------------------------------------------- /editor/statics/js/index.js: -------------------------------------------------------------------------------- 1 | function ShowGRL() { 2 | $("#navGRL").addClass("active"); 3 | $("#navContext").removeClass("active"); 4 | $("#navResult").removeClass("active"); 5 | 6 | $("#panelGRL").css("display", "block"); 7 | $("#panelContext").css("display", "none"); 8 | $("#panelResult").css("display", "none"); 9 | } 10 | 11 | function ShowContext() { 12 | $("#navContext").addClass("active"); 13 | $("#navGRL").removeClass("active"); 14 | $("#navResult").removeClass("active"); 15 | 16 | $("#panelContext").css("display", "block"); 17 | $("#panelGRL").css("display", "none"); 18 | $("#panelResult").css("display", "none"); 19 | } 20 | 21 | function ShowResult() { 22 | $("#navResult").addClass("active"); 23 | $("#navContext").removeClass("active"); 24 | $("#navGRL").removeClass("active"); 25 | 26 | $("#panelResult").css("display", "block"); 27 | $("#panelContext").css("display", "none"); 28 | $("#panelGRL").css("display", "none"); 29 | } 30 | 31 | function executeRule() { 32 | let grl = $("#grlText").val(); 33 | let json = $("#jsonText").val() 34 | let grlB64 = btoa(grl); 35 | let jsonB64 = btoa(json); 36 | 37 | $.post( "/evaluate", JSON.stringify({"grlText": grlB64, "jsonText": jsonB64}) , function( data, status ) { 38 | $("#response").val(status + " : " + JSON.stringify(data) ); 39 | }, "json") .fail(function(data) { 40 | $("#response").val( "Status " + data.status + " : " + data.statusText + ". ResponseText : " + data.responseText); 41 | }); 42 | } 43 | 44 | function MarkUp(src) { 45 | let re = /rule/gi; 46 | let poses = src.match(re); 47 | let marks = [] 48 | if (poses) { 49 | while ((match = re.exec(src)) != null) { 50 | marks.push( 51 | { 52 | start: match.index, 53 | end: match.index+4, 54 | text: src.substring(match.index,match.index+4), 55 | kind: "rule" 56 | } 57 | ); 58 | } 59 | } 60 | return marks; 61 | } 62 | 63 | function MarkDown(src) { 64 | return src; 65 | } 66 | 67 | function Mark() { 68 | let markers = MarkUp($("#grleditor").text()); 69 | // do nothing 70 | } 71 | 72 | function JsonEditorTab(e) { 73 | if (e.keyCode === 9) { // tab key 74 | e.preventDefault(); // this will prevent us from tabbing out of the editor 75 | 76 | // now insert four non-breaking spaces for the tab key 77 | var editor = document.getElementById("jsoneditor"); 78 | var doc = editor.ownerDocument.defaultView; 79 | var sel = doc.getSelection(); 80 | var range = sel.getRangeAt(0); 81 | 82 | var tabNode = document.createTextNode("\u00a0\u00a0\u00a0\u00a0"); 83 | range.insertNode(tabNode); 84 | 85 | range.setStartAfter(tabNode); 86 | range.setEndAfter(tabNode); 87 | sel.removeAllRanges(); 88 | sel.addRange(range); 89 | } 90 | } 91 | 92 | function GrlEditorTab(e) { 93 | if (e.keyCode === 9) { // tab key 94 | e.preventDefault(); // this will prevent us from tabbing out of the editor 95 | 96 | // now insert four non-breaking spaces for the tab key 97 | var editor = document.getElementById("grleditor"); 98 | var doc = editor.ownerDocument.defaultView; 99 | var sel = doc.getSelection(); 100 | var range = sel.getRangeAt(0); 101 | 102 | var tabNode = document.createTextNode("\u00a0\u00a0\u00a0\u00a0"); 103 | range.insertNode(tabNode); 104 | 105 | range.setStartAfter(tabNode); 106 | range.setEndAfter(tabNode); 107 | sel.removeAllRanges(); 108 | sel.addRange(range); 109 | } 110 | } 111 | 112 | -------------------------------------------------------------------------------- /engine/GruleEngineListener.go: -------------------------------------------------------------------------------- 1 | package engine 2 | 3 | import "github.com/hyperjumptech/grule-rule-engine/ast" 4 | 5 | // GruleEngineListener is an interface to be implemented by those who want to listen the Engine execution. 6 | type GruleEngineListener interface { 7 | // EvaluateRuleEntry will be called by the engine if it evaluate a rule entry 8 | EvaluateRuleEntry(cycle uint64, entry *ast.RuleEntry, candidate bool) 9 | // ExecuteRuleEntry will be called by the engine if it execute a rule entry in a cycle 10 | ExecuteRuleEntry(cycle uint64, entry *ast.RuleEntry) 11 | // BeginCycle will be called by the engine every time it start a new evaluation cycle 12 | BeginCycle(cycle uint64) 13 | } 14 | -------------------------------------------------------------------------------- /examples/AgeCheckSample_test.go: -------------------------------------------------------------------------------- 1 | // Copyright hyperjumptech/grule-rule-engine Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package examples 16 | 17 | import ( 18 | "fmt" 19 | "github.com/hyperjumptech/grule-rule-engine/ast" 20 | "github.com/hyperjumptech/grule-rule-engine/builder" 21 | "github.com/hyperjumptech/grule-rule-engine/engine" 22 | "github.com/hyperjumptech/grule-rule-engine/pkg" 23 | "github.com/stretchr/testify/assert" 24 | "testing" 25 | ) 26 | 27 | const ( 28 | rule2 = ` 29 | rule AgeNameCheck "test" { 30 | when 31 | Pogo.GetStringLength("9999") > 0 && 32 | Pogo.Result == "" 33 | then 34 | Pogo.Result = "String len above 0"; 35 | } 36 | ` 37 | 38 | rule3 = ` 39 | rule AgeNameCheck "test" salience 10{ 40 | when 41 | Pogo.Compare(User.Name, "Calo") 42 | then 43 | User.Name = "Success"; 44 | Log(User.Name); 45 | Retract("AgeNameCheck"); 46 | } 47 | ` 48 | ) 49 | 50 | // MyPoGo serve as example plain Plai Old Go Object. 51 | type MyPoGo struct { 52 | Result string 53 | } 54 | 55 | // GetStringLength will return the length of provided string argument 56 | func (p *MyPoGo) GetStringLength(sarg string) int { 57 | return len(sarg) 58 | } 59 | 60 | // Compare will compare the equality between the two string. 61 | func (p *MyPoGo) Compare(t1, t2 string) bool { 62 | fmt.Println(t1, t2) 63 | return t1 == t2 64 | } 65 | 66 | // User is an example user struct. 67 | type User struct { 68 | Name string 69 | Age int 70 | Male bool 71 | } 72 | 73 | func TestMyPoGo_GetStringLength(t *testing.T) { 74 | dataContext := ast.NewDataContext() 75 | pogo := &MyPoGo{} 76 | err := dataContext.Add("Pogo", pogo) 77 | if err != nil { 78 | t.Fatal(err) 79 | } 80 | 81 | lib := ast.NewKnowledgeLibrary() 82 | ruleBuilder := builder.NewRuleBuilder(lib) 83 | err = ruleBuilder.BuildRuleFromResource("Test", "0.1.1", pkg.NewBytesResource([]byte(rule2))) 84 | assert.NoError(t, err) 85 | kb, err := lib.NewKnowledgeBaseInstance("Test", "0.1.1") 86 | assert.NoError(t, err) 87 | eng1 := &engine.GruleEngine{MaxCycle: 1} 88 | err = eng1.Execute(dataContext, kb) 89 | assert.NoError(t, err) 90 | assert.Equal(t, "String len above 0", pogo.Result) 91 | } 92 | 93 | func TestMyPoGo_Compare(t *testing.T) { 94 | user := &User{ 95 | Name: "Calo", 96 | Age: 0, 97 | Male: true, 98 | } 99 | 100 | dataContext := ast.NewDataContext() 101 | err := dataContext.Add("User", user) 102 | if err != nil { 103 | t.Fatal(err) 104 | } 105 | err = dataContext.Add("Pogo", &MyPoGo{}) 106 | if err != nil { 107 | t.Fatal(err) 108 | } 109 | 110 | lib := ast.NewKnowledgeLibrary() 111 | ruleBuilder := builder.NewRuleBuilder(lib) 112 | 113 | err = ruleBuilder.BuildRuleFromResource("Test", "0.1.1", pkg.NewBytesResource([]byte(rule3))) 114 | assert.NoError(t, err) 115 | kb, err := lib.NewKnowledgeBaseInstance("Test", "0.1.1") 116 | assert.NoError(t, err) 117 | eng1 := &engine.GruleEngine{MaxCycle: 100} 118 | err = eng1.Execute(dataContext, kb) 119 | assert.NoError(t, err) 120 | t.Log(user) 121 | assert.Equal(t, "Success", user.Name, "User should have changed name by rule to Success, but %s", user.Name) 122 | } 123 | -------------------------------------------------------------------------------- /examples/ArraySliceMap_test.go: -------------------------------------------------------------------------------- 1 | // Copyright hyperjumptech/grule-rule-engine Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package examples 16 | 17 | import ( 18 | "fmt" 19 | "github.com/hyperjumptech/grule-rule-engine/ast" 20 | "github.com/hyperjumptech/grule-rule-engine/builder" 21 | "github.com/hyperjumptech/grule-rule-engine/engine" 22 | "github.com/hyperjumptech/grule-rule-engine/pkg" 23 | "github.com/stretchr/testify/assert" 24 | "testing" 25 | ) 26 | 27 | type ArrayNode struct { 28 | Name string 29 | StringArray []string 30 | NumberArray []int 31 | ChildArray []*ArrayNode 32 | } 33 | 34 | func (node *ArrayNode) CallMyName() string { 35 | fmt.Println("You have call my name", node.Name) 36 | return node.Name 37 | } 38 | 39 | func (node *ArrayNode) GetChild(idx int64) *ArrayNode { 40 | return node.ChildArray[idx] 41 | } 42 | 43 | func TestArraySlice(t *testing.T) { 44 | //logrus.SetLevel(logrus.TraceLevel) 45 | Tree := &ArrayNode{ 46 | Name: "Node", 47 | StringArray: []string{"NodeString1", "NodeString2"}, 48 | NumberArray: []int{235, 633}, 49 | ChildArray: []*ArrayNode{ 50 | &ArrayNode{ 51 | Name: "NodeChild1", 52 | StringArray: []string{"NodeChildString11", "NodeChildString12"}, 53 | NumberArray: []int{578, 296}, 54 | ChildArray: []*ArrayNode{ 55 | &ArrayNode{ 56 | Name: "NodeChild11", 57 | StringArray: []string{"NodeChildString111", "NodeChildString112"}, 58 | NumberArray: []int{578, 296}, 59 | ChildArray: nil, 60 | }, &ArrayNode{ 61 | Name: "NodeChild12", 62 | StringArray: []string{"NodeChildString121", "NodeChildString122"}, 63 | NumberArray: []int{744, 895}, 64 | ChildArray: nil, 65 | }, 66 | }, 67 | }, &ArrayNode{ 68 | Name: "NodeChild2", 69 | StringArray: []string{"NodeChildString21", "NodeChildString22"}, 70 | NumberArray: []int{744, 895}, 71 | ChildArray: nil, 72 | }, 73 | }, 74 | } 75 | 76 | rule := ` 77 | rule SetTreeName "Set the top most tree name" { 78 | when 79 | Tree.Name.ToUpper() == "NODE" && 80 | Tree.StringArray[0].ToUpper() == "NODESTRING1" && 81 | Tree.StringArray[1].ToLower() == "nodestring2" && 82 | Tree.NumberArray[0] == 235 && 83 | Tree.NumberArray[1] == 633 && 84 | Tree.ChildArray[0].Name == "NodeChild1" && 85 | Tree.ChildArray[0].CallMyName() == "NodeChild1" && 86 | Tree.GetChild(0).ChildArray[0].Name == "NodeChild11" && 87 | Tree.GetChild(0).ChildArray[0].CallMyName() == "NodeChild11" && 88 | Tree.ChildArray[0].StringArray[1] == "NodeChildString12" 89 | then 90 | Tree.Name = "VERIFIED".ToLower(); 91 | Tree.ChildArray[0].StringArray[0] = "SetSuccessful"; 92 | Tree.NumberArray[1] = 1000; 93 | Tree.ChildArray[0].CallMyName(); 94 | Retract("SetTreeName"); 95 | } 96 | ` 97 | 98 | dataContext := ast.NewDataContext() 99 | err := dataContext.Add("Tree", Tree) 100 | assert.NoError(t, err) 101 | 102 | lib := ast.NewKnowledgeLibrary() 103 | ruleBuilder := builder.NewRuleBuilder(lib) 104 | err = ruleBuilder.BuildRuleFromResource("TestFuncChaining", "0.0.1", pkg.NewBytesResource([]byte(rule))) 105 | assert.NoError(t, err) 106 | kb, err := lib.NewKnowledgeBaseInstance("TestFuncChaining", "0.0.1") 107 | assert.NoError(t, err) 108 | eng1 := &engine.GruleEngine{MaxCycle: 1} 109 | err = eng1.Execute(dataContext, kb) 110 | assert.NoError(t, err) 111 | assert.Equal(t, "verified", Tree.Name) 112 | assert.Equal(t, "SetSuccessful", Tree.ChildArray[0].StringArray[0]) 113 | assert.Equal(t, 1000, Tree.NumberArray[1]) 114 | } 115 | -------------------------------------------------------------------------------- /examples/CallingFactFunctionExample_test.go: -------------------------------------------------------------------------------- 1 | // Copyright hyperjumptech/grule-rule-engine Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package examples 16 | 17 | import ( 18 | "fmt" 19 | "github.com/hyperjumptech/grule-rule-engine/ast" 20 | "github.com/hyperjumptech/grule-rule-engine/builder" 21 | "github.com/hyperjumptech/grule-rule-engine/engine" 22 | "github.com/hyperjumptech/grule-rule-engine/pkg" 23 | "github.com/stretchr/testify/assert" 24 | "testing" 25 | ) 26 | 27 | const ( 28 | CallFactFuncGRL = ` 29 | rule RaiseHandToClap "Raise the hand to be able to clap" { 30 | when 31 | Clapper.ClapCount < 10 && 32 | Clapper.HandsUp == false 33 | then 34 | Clapper.HandsUp = true; 35 | Log("RaiseHandToClap : Now hands up"); 36 | } 37 | 38 | rule CheckIfCanClap "If hands is up we can clap" { 39 | when 40 | Clapper.HandsUp && 41 | Clapper.CanClap == false 42 | then 43 | Clapper.CanClap = true; 44 | Log("CheckIfCanClap : Now can clap"); 45 | } 46 | 47 | rule LetsClap "If hands are up and can clap, lets clap" { 48 | when 49 | Clapper.HandsUp && Clapper.CanClap 50 | then 51 | Log("LetsClap : Now clapping. Hands Down thus Can't clap'"); 52 | Clapper.Clap(); 53 | 54 | // instruct the engine to forget about variables and functions. Otherwise the engine will use the last remembered value. 55 | Forget("Clapper.CanClap"); 56 | Forget("Clapper.HandsUp"); 57 | Forget("Clapper.ClapCount"); 58 | Forget("Clapper.Clap()"); 59 | } 60 | ` 61 | ) 62 | 63 | type Clapper struct { 64 | CanClap bool 65 | HandsUp bool 66 | ClapCount int64 67 | } 68 | 69 | func (c *Clapper) Clap() { 70 | c.ClapCount++ 71 | c.HandsUp = false 72 | c.CanClap = false 73 | } 74 | 75 | func TestCallingFactFunction(t *testing.T) { 76 | c := &Clapper{ 77 | CanClap: false, 78 | HandsUp: false, 79 | ClapCount: 0, 80 | } 81 | 82 | dataContext := ast.NewDataContext() 83 | err := dataContext.Add("Clapper", c) 84 | assert.NoError(t, err) 85 | 86 | // Prepare knowledgebase library and load it with our rule. 87 | lib := ast.NewKnowledgeLibrary() 88 | ruleBuilder := builder.NewRuleBuilder(lib) 89 | err = ruleBuilder.BuildRuleFromResource("CallingFactFunction", "0.1.1", pkg.NewBytesResource([]byte(CallFactFuncGRL))) 90 | assert.NoError(t, err) 91 | 92 | knowledgeBase, err := lib.NewKnowledgeBaseInstance("CallingFactFunction", "0.1.1") 93 | assert.NoError(t, err) 94 | eng1 := &engine.GruleEngine{MaxCycle: 500} 95 | err = eng1.Execute(dataContext, knowledgeBase) 96 | assert.NoError(t, err) 97 | fmt.Printf("%v", c) 98 | assert.Equal(t, 10, int(c.ClapCount)) 99 | } 100 | -------------------------------------------------------------------------------- /examples/CallingLogExample_test.go: -------------------------------------------------------------------------------- 1 | // Copyright hyperjumptech/grule-rule-engine Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package examples 16 | 17 | import ( 18 | "github.com/hyperjumptech/grule-rule-engine/ast" 19 | "github.com/hyperjumptech/grule-rule-engine/builder" 20 | "github.com/hyperjumptech/grule-rule-engine/engine" 21 | "github.com/hyperjumptech/grule-rule-engine/pkg" 22 | "github.com/stretchr/testify/assert" 23 | "testing" 24 | ) 25 | 26 | const ( 27 | GRL = ` 28 | rule CallingLog "Calling a log" { 29 | when 30 | true 31 | then 32 | Log("Hello Grule"); 33 | Retract("CallingLog"); 34 | } 35 | ` 36 | ) 37 | 38 | func TestCallingLog(t *testing.T) { 39 | dataContext := ast.NewDataContext() 40 | 41 | lib := ast.NewKnowledgeLibrary() 42 | ruleBuilder := builder.NewRuleBuilder(lib) 43 | err := ruleBuilder.BuildRuleFromResource("CallingLog", "0.1.1", pkg.NewBytesResource([]byte(GRL))) 44 | assert.NoError(t, err) 45 | 46 | knowledgeBase, err := lib.NewKnowledgeBaseInstance("CallingLog", "0.1.1") 47 | assert.NoError(t, err) 48 | 49 | eng1 := &engine.GruleEngine{MaxCycle: 1} 50 | err = eng1.Execute(dataContext, knowledgeBase) 51 | assert.NoError(t, err) 52 | } 53 | -------------------------------------------------------------------------------- /examples/CashFlowRule.grl: -------------------------------------------------------------------------------- 1 | rule TaxingLuxuryItems "When its a luxury Item, you tax them 15 percent." salience 10 { 2 | when 3 | Purchase.IgnoredPurchase == false && Purchase.Tax == 0 && Purchase.ItemType == "LUXURY" 4 | then 5 | Purchase.Tax = Purchase.Price + (Purchase.Price * 0.15); 6 | } 7 | 8 | rule TaxingNormalItems "When its a Normal Item, you tax them 10 percent." salience 8 { 9 | when 10 | Purchase.IgnoredPurchase == false && Purchase.Tax == 0 && Purchase.ItemType == "NORMAL" 11 | then 12 | Purchase.Tax = Purchase.Price + (Purchase.Price * 0.1); 13 | } 14 | 15 | rule TaxingOtherTypeItems "When its not Normal or Luxury Item, you tax them 20 percent." salience 7 { 16 | when 17 | Purchase.IgnoredPurchase == false && Purchase.Tax == 0 && Purchase.ItemType != "NORMAL" && Purchase.ItemType != "LUXURY" 18 | then 19 | Purchase.Tax = Purchase.Price + (Purchase.Price * 0.2); 20 | } 21 | 22 | rule CalculatePriceAfterTax "When tax is calculated, time to calculate price after tax" { 23 | when 24 | Purchase.Tax > 0 && Purchase.PriceAfterTax == 0 25 | then 26 | Purchase.PriceAfterTax = Purchase.Price + Purchase.Tax; 27 | } 28 | 29 | rule SumUpPurchase "When price after tax calculated, sum it up" { 30 | when 31 | Purchase.PriceAfterTax > 0 32 | then 33 | CashFlow.PurchaseCount = CashFlow.PurchaseCount + 1; 34 | Log("Count = " + CashFlow.PurchaseCount); 35 | CashFlow.TotalPurchases = CashFlow.TotalPurchases + Purchase.Price; 36 | CashFlow.TotalTax = CashFlow.TotalTax + Purchase.Tax; 37 | CashFlow.PurchasesAfterTax = CashFlow.PurchasesAfterTax + Purchase.PriceAfterTax; 38 | Retract("SumUpPurchase"); 39 | } 40 | 41 | rule OnlyCalculatePurchaseInYear2019 "All other purchase dated not in 2019, we should ignore" salience 100 { 42 | when 43 | Purchase.IgnoredPurchase == false && GetTimeYear(Purchase.PurchaseDate) != 2019 && Purchase.PriceAfterTax == 0 44 | then 45 | Purchase.IgnoredPurchase = true; 46 | Retract("OnlyCalculatePurchaseInYear2019"); 47 | } 48 | 49 | -------------------------------------------------------------------------------- /examples/CloneTableIssue_test.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import ( 4 | "github.com/hyperjumptech/grule-rule-engine/ast" 5 | "github.com/hyperjumptech/grule-rule-engine/builder" 6 | "github.com/hyperjumptech/grule-rule-engine/engine" 7 | "github.com/hyperjumptech/grule-rule-engine/pkg" 8 | "github.com/stretchr/testify/assert" 9 | "testing" 10 | ) 11 | 12 | type StructStringsData struct { 13 | Strings []string 14 | } 15 | 16 | func (f *StructStringsData) GetStrings() []string { 17 | return f.Strings 18 | } 19 | 20 | const panickingRule = ` rule test { 21 | when 22 | Fact.GetStrings()[0] == Fact.GetStrings()[1] 23 | then 24 | Complete(); 25 | }` 26 | 27 | func TestSliceFunctionPanicTest(t *testing.T) { 28 | fact := &StructStringsData{ 29 | Strings: []string{"0", "0"}, 30 | } 31 | 32 | dataContext := ast.NewDataContext() 33 | err := dataContext.Add("Fact", fact) 34 | assert.NoError(t, err) 35 | knowledgeLibrary := ast.NewKnowledgeLibrary() 36 | ruleBuilder := builder.NewRuleBuilder(knowledgeLibrary) 37 | err = ruleBuilder.BuildRuleFromResource("test", "0.0.1", pkg.NewBytesResource([]byte(panickingRule))) 38 | assert.NoError(t, err) 39 | knowledgeBase, err := knowledgeLibrary.NewKnowledgeBaseInstance("test", "0.0.1") 40 | assert.NoError(t, err) 41 | engine := engine.NewGruleEngine() 42 | 43 | err = engine.Execute(dataContext, knowledgeBase) 44 | assert.NoError(t, err) 45 | } 46 | -------------------------------------------------------------------------------- /examples/EvaluateMissingDataContext_test.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import ( 4 | "fmt" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | 8 | "github.com/hyperjumptech/grule-rule-engine/ast" 9 | "github.com/hyperjumptech/grule-rule-engine/builder" 10 | "github.com/hyperjumptech/grule-rule-engine/engine" 11 | "github.com/hyperjumptech/grule-rule-engine/pkg" 12 | ) 13 | 14 | const ( 15 | inputRule = ` 16 | rule TestRule "" { 17 | when 18 | R.Result == 'NoResult' && 19 | inputs.i_am_missing == 'abc' && 20 | inputs.name.first == 'john' 21 | then 22 | R.Result = "ok"; 23 | } 24 | ` 25 | ) 26 | 27 | func TestDataContextMissingFact(t *testing.T) { 28 | 29 | oresult := &ObjectResult{ 30 | Result: "NoResult", 31 | } 32 | 33 | // build rules 34 | lib := ast.NewKnowledgeLibrary() 35 | rb := builder.NewRuleBuilder(lib) 36 | err := rb.BuildRuleFromResource("Test", "0.0.1", pkg.NewBytesResource([]byte(inputRule))) 37 | 38 | // add JSON fact 39 | json := []byte(`{"blabla":"bla","name":{"first":"john","last":"doe"}}`) 40 | kb, err := lib.NewKnowledgeBaseInstance("Test", "0.0.1") 41 | assert.NoError(t, err) 42 | dcx := ast.NewDataContext() 43 | 44 | err = dcx.Add("R", oresult) 45 | err = dcx.AddJSON("inputs", json) 46 | if err != nil { 47 | fmt.Println(err.Error()) 48 | } 49 | 50 | // results in panic 51 | engine.NewGruleEngine().Execute(dcx, kb) 52 | 53 | } 54 | -------------------------------------------------------------------------------- /examples/FunctionCallChaining_test.go: -------------------------------------------------------------------------------- 1 | // Copyright hyperjumptech/grule-rule-engine Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package examples 16 | 17 | import ( 18 | "fmt" 19 | "github.com/hyperjumptech/grule-rule-engine/ast" 20 | "github.com/hyperjumptech/grule-rule-engine/builder" 21 | "github.com/hyperjumptech/grule-rule-engine/engine" 22 | "github.com/hyperjumptech/grule-rule-engine/pkg" 23 | "github.com/stretchr/testify/assert" 24 | "testing" 25 | ) 26 | 27 | type TestTree struct { 28 | Name string 29 | Child *TestTree 30 | } 31 | 32 | func (tt *TestTree) GetChild() *TestTree { 33 | return tt.Child 34 | } 35 | 36 | func (tt *TestTree) CallMyName() { 37 | fmt.Println("Calling your name", tt.Name) 38 | } 39 | 40 | func TestFunctionChain(t *testing.T) { 41 | Tree := &TestTree{ 42 | Name: "Test", 43 | Child: &TestTree{ 44 | Name: "TestTest", 45 | Child: &TestTree{ 46 | Name: "TestTestTest", 47 | Child: &TestTree{ 48 | Name: "TestTestTestTest", 49 | Child: nil, 50 | }, 51 | }, 52 | }, 53 | } 54 | 55 | rule := ` 56 | rule SetTreeName "Set the top most tree name" { 57 | when 58 | Tree.Name.ToUpper() == "TEST" && 59 | Tree.GetChild().Name.ToUpper() == "TESTTEST" && 60 | Tree.GetChild().GetChild().Name.ToUpper() == "TESTTESTTEST" && 61 | Tree.GetChild().GetChild().GetChild().Name.ToLower() == " testtesttesttest ".Trim() 62 | then 63 | Tree.Name = "VERIFIED".ToLower(); 64 | Tree.GetChild().GetChild().CallMyName(); 65 | Retract("SetTreeName"); 66 | } 67 | ` 68 | 69 | dataContext := ast.NewDataContext() 70 | err := dataContext.Add("Tree", Tree) 71 | assert.NoError(t, err) 72 | 73 | lib := ast.NewKnowledgeLibrary() 74 | ruleBuilder := builder.NewRuleBuilder(lib) 75 | err = ruleBuilder.BuildRuleFromResource("TestFuncChaining", "0.0.1", pkg.NewBytesResource([]byte(rule))) 76 | assert.NoError(t, err) 77 | kb, err := lib.NewKnowledgeBaseInstance("TestFuncChaining", "0.0.1") 78 | assert.NoError(t, err) 79 | eng1 := &engine.GruleEngine{MaxCycle: 1} 80 | err = eng1.Execute(dataContext, kb) 81 | assert.NoError(t, err) 82 | assert.Equal(t, "verified", Tree.Name) 83 | 84 | } 85 | 86 | func TestVariableChain(t *testing.T) { 87 | Tree := &TestTree{ 88 | Name: "Test", 89 | Child: &TestTree{ 90 | Name: "TestTest", 91 | Child: &TestTree{ 92 | Name: "TestTestTest", 93 | Child: &TestTree{ 94 | Name: "TestTestTestTest", 95 | Child: nil, 96 | }, 97 | }, 98 | }, 99 | } 100 | 101 | rule := ` 102 | rule SetTreeName "Set the top most tree name" { 103 | when 104 | Tree.Name.ToUpper() == "TEST" && 105 | Tree.Child.Name.ToUpper() == "TESTTEST" && 106 | Tree.Child.Child.Name.ToUpper() == "TESTTESTTEST" && 107 | Tree.Child.Child.Child.Name.ToLower() == " testtesttesttest ".Trim() 108 | then 109 | Tree.Name = "VERIFIED".ToLower(); 110 | Tree.Child.Child.Child.Name = "SUCCESS"; 111 | Retract("SetTreeName"); 112 | } 113 | ` 114 | 115 | dataContext := ast.NewDataContext() 116 | err := dataContext.Add("Tree", Tree) 117 | assert.NoError(t, err) 118 | 119 | lib := ast.NewKnowledgeLibrary() 120 | ruleBuilder := builder.NewRuleBuilder(lib) 121 | err = ruleBuilder.BuildRuleFromResource("TestFuncChaining", "0.0.1", pkg.NewBytesResource([]byte(rule))) 122 | assert.NoError(t, err) 123 | kb, err := lib.NewKnowledgeBaseInstance("TestFuncChaining", "0.0.1") 124 | assert.NoError(t, err) 125 | eng1 := &engine.GruleEngine{MaxCycle: 1} 126 | err = eng1.Execute(dataContext, kb) 127 | assert.NoError(t, err) 128 | assert.Equal(t, "verified", Tree.Name) 129 | assert.Equal(t, "SUCCESS", Tree.Child.Child.Child.Name) 130 | } 131 | -------------------------------------------------------------------------------- /examples/GrlParseErrorDetection_test.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import ( 4 | "github.com/hyperjumptech/grule-rule-engine/ast" 5 | "github.com/hyperjumptech/grule-rule-engine/builder" 6 | "github.com/hyperjumptech/grule-rule-engine/pkg" 7 | "testing" 8 | ) 9 | 10 | const ( 11 | RuleWithError = ` 12 | rule ErrorRule1 "Rule with error" salience 10{ 13 | when 14 | Pogo.Compare(User.Name, "Calo") 15 | then 16 | User.Name = "Success"; 17 | Log(User.Name) 18 | Retract("AgeNameCheck"); 19 | } 20 | ` 21 | ) 22 | 23 | // TestParsingErrorDetection demonstrate how to make your own GRL script/syntax validator using the RuleBuilder. 24 | func TestParsingErrorDetection(t *testing.T) { 25 | 26 | // Normal stuff, creating a library and builder for it. 27 | lib := ast.NewKnowledgeLibrary() 28 | ruleBuilder := builder.NewRuleBuilder(lib) 29 | 30 | // Build normally 31 | err := ruleBuilder.BuildRuleFromResource("Test", "0.1.1", pkg.NewBytesResource([]byte(RuleWithError))) 32 | 33 | // If the err != nil something is wrong. 34 | if err != nil { 35 | // Cast the error into pkg.GruleErrorReporter with type checking. 36 | // Type checking is necessary because the err might not only parsing error. 37 | if reporter, ok := err.(*pkg.GruleErrorReporter); ok { 38 | // Lets iterate all the error we get during parsing. 39 | for i, er := range reporter.Errors { 40 | t.Logf("detected error #%d : %s", i, er.Error()) 41 | } 42 | } else { 43 | // Well, its an error but not GruleErrorReporter instance. could be IO error. 44 | t.Error("There should be GruleErrorReporter") 45 | t.FailNow() 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /examples/InterfaceDataContext_test.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import ( 4 | "github.com/hyperjumptech/grule-rule-engine/ast" 5 | "github.com/hyperjumptech/grule-rule-engine/builder" 6 | "github.com/hyperjumptech/grule-rule-engine/engine" 7 | "github.com/hyperjumptech/grule-rule-engine/pkg" 8 | "github.com/stretchr/testify/assert" 9 | "testing" 10 | ) 11 | 12 | type Vehicle interface { 13 | GetPrice() int64 14 | GetName() string 15 | } 16 | 17 | type Car struct { 18 | price int64 19 | name string 20 | } 21 | 22 | func (c *Car) GetPrice() int64 { 23 | return c.price 24 | } 25 | 26 | func (c *Car) GetName() string { 27 | return c.name 28 | } 29 | 30 | type Motorcycle struct { 31 | price int64 32 | name string 33 | } 34 | 35 | func (m *Motorcycle) GetPrice() int64 { 36 | return m.price 37 | } 38 | 39 | func (m *Motorcycle) GetName() string { 40 | return m.name 41 | } 42 | 43 | func TestInterfaceDataContext(t *testing.T) { 44 | rule := `rule VehiclePriceCheck "Checking vehicle price" salience 100 { 45 | when 46 | v[1].GetPrice() > 60000 47 | then 48 | Log("Too expensive"); 49 | Retract("VehiclePriceCheck"); 50 | }` 51 | vehicles := []Vehicle{ 52 | &Car{price: 50000, name: "Car A"}, 53 | &Car{price: 90000, name: "Car B"}, 54 | &Motorcycle{price: 50000, name: "Motor A"}, 55 | &Motorcycle{price: 90000, name: "Motor A"}, 56 | } 57 | 58 | lib := ast.NewKnowledgeLibrary() 59 | rb := builder.NewRuleBuilder(lib) 60 | err := rb.BuildRuleFromResource("CarPriceTest", "0.1.1", pkg.NewBytesResource([]byte(rule))) 61 | assert.NoError(t, err) 62 | kb, err := lib.NewKnowledgeBaseInstance("CarPriceTest", "0.1.1") 63 | assert.NoError(t, err) 64 | eng := &engine.GruleEngine{MaxCycle: 3} 65 | 66 | dataContext := ast.NewDataContext() 67 | err = dataContext.Add("v", vehicles) 68 | assert.NoError(t, err) 69 | 70 | err = eng.Execute(dataContext, kb) 71 | assert.NoError(t, err) 72 | } 73 | -------------------------------------------------------------------------------- /examples/Issue108_test.go: -------------------------------------------------------------------------------- 1 | // Copyright hyperjumptech/grule-rule-engine Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package examples 16 | 17 | import ( 18 | "github.com/hyperjumptech/grule-rule-engine/ast" 19 | "github.com/hyperjumptech/grule-rule-engine/builder" 20 | "github.com/hyperjumptech/grule-rule-engine/engine" 21 | "github.com/hyperjumptech/grule-rule-engine/pkg" 22 | "github.com/stretchr/testify/assert" 23 | "testing" 24 | ) 25 | 26 | type Struct108 struct { 27 | Rule1Done bool 28 | Rule2Done bool 29 | Rule3Done bool 30 | Sequence []string 31 | } 32 | 33 | var Rule108 = ` 34 | rule Conflicting1 "First conflicting rule" salience 1 { 35 | when 36 | F.Rule1Done == false 37 | then 38 | F.Rule1Done = true; 39 | F.Sequence.Append("1"); 40 | } 41 | 42 | rule Conflicting2 "Second conflicting rule" salience 3 { 43 | when 44 | F.Rule2Done == false 45 | then 46 | F.Rule2Done = true; 47 | F.Sequence.Append("2"); 48 | } 49 | 50 | rule Conflicting3 "Third conflicting rule" salience 2 { 51 | when 52 | F.Rule3Done == false 53 | then 54 | F.Rule3Done = true; 55 | F.Sequence.Append("3"); 56 | } 57 | ` 58 | 59 | func TestIssue108(t *testing.T) { 60 | Obj := &Struct108{ 61 | Rule1Done: false, 62 | Rule2Done: false, 63 | Rule3Done: false, 64 | Sequence: make([]string, 0), 65 | } 66 | 67 | dataContext := ast.NewDataContext() 68 | err := dataContext.Add("F", Obj) 69 | assert.NoError(t, err) 70 | 71 | // Prepare knowledgebase library and load it with our rule. 72 | lib := ast.NewKnowledgeLibrary() 73 | rb := builder.NewRuleBuilder(lib) 74 | err = rb.BuildRuleFromResource("Test108", "0.0.1", pkg.NewBytesResource([]byte(Rule108))) 75 | assert.NoError(t, err) 76 | eng1 := &engine.GruleEngine{MaxCycle: 5} 77 | kb, err := lib.NewKnowledgeBaseInstance("Test108", "0.0.1") 78 | assert.NoError(t, err) 79 | err = eng1.Execute(dataContext, kb) 80 | assert.NoError(t, err) 81 | assert.Equal(t, 3, len(Obj.Sequence)) 82 | assert.Equal(t, "2", Obj.Sequence[0]) 83 | assert.Equal(t, "3", Obj.Sequence[1]) 84 | assert.Equal(t, "1", Obj.Sequence[2]) 85 | } 86 | -------------------------------------------------------------------------------- /examples/Issue328_test.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/hyperjumptech/grule-rule-engine/ast" 7 | "github.com/hyperjumptech/grule-rule-engine/builder" 8 | "github.com/hyperjumptech/grule-rule-engine/engine" 9 | "github.com/hyperjumptech/grule-rule-engine/pkg" 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | const ( 14 | SliceOORRule = ` 15 | rule SliceOORRule { 16 | when 17 | PriceSlice.Prices[4] > 10 // will cause panic 18 | then 19 | Log("Price number 4 is greater than 10"); 20 | Retract("SliceOORRule"); 21 | }` 22 | ) 23 | 24 | type AUserSliceIssue struct { 25 | Prices []int 26 | } 27 | 28 | func TestMethodCall_SliceOOR(t *testing.T) { 29 | ps := &AUserSliceIssue{ 30 | Prices: []int{1, 2, 3}, 31 | } 32 | 33 | dataContext := ast.NewDataContext() 34 | err := dataContext.Add("PriceSlice", ps) 35 | assert.NoError(t, err) 36 | 37 | // Prepare knowledgebase library and load it with our rule. 38 | lib := ast.NewKnowledgeLibrary() 39 | rb := builder.NewRuleBuilder(lib) 40 | err = rb.BuildRuleFromResource("Test", "0.1.1", pkg.NewBytesResource([]byte(SliceOORRule))) 41 | assert.NoError(t, err) 42 | 43 | eng1 := &engine.GruleEngine{MaxCycle: 5} 44 | kb, err := lib.NewKnowledgeBaseInstance("Test", "0.1.1") 45 | assert.NoError(t, err) 46 | err = eng1.Execute(dataContext, kb) 47 | assert.NoError(t, err) 48 | 49 | eng1 = &engine.GruleEngine{MaxCycle: 5, ReturnErrOnFailedRuleEvaluation: true} 50 | kb, err = lib.NewKnowledgeBaseInstance("Test", "0.1.1") 51 | assert.NoError(t, err) 52 | err = eng1.Execute(dataContext, kb) 53 | assert.Error(t, err) 54 | } 55 | -------------------------------------------------------------------------------- /examples/Issue4_test.go: -------------------------------------------------------------------------------- 1 | // Copyright hyperjumptech/grule-rule-engine Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package examples 16 | 17 | import ( 18 | "github.com/hyperjumptech/grule-rule-engine/ast" 19 | "github.com/hyperjumptech/grule-rule-engine/builder" 20 | "github.com/hyperjumptech/grule-rule-engine/engine" 21 | "github.com/hyperjumptech/grule-rule-engine/pkg" 22 | "github.com/stretchr/testify/assert" 23 | "testing" 24 | ) 25 | 26 | const ( 27 | Rule4 = ` 28 | rule UserTestRule4 "test 3" salience 10{ 29 | when 30 | User.Auth.GetEmail() == "watson@test.com" 31 | then 32 | User.Name = "FromRuleScope4"; 33 | Retract("UserTestRule4"); 34 | } 35 | ` 36 | ) 37 | 38 | type UserWithAuth struct { 39 | Auth *UserAuth 40 | Name string 41 | } 42 | 43 | func (user *UserWithAuth) GetName() string { 44 | return user.Name 45 | } 46 | 47 | type UserAuth struct { 48 | Email string 49 | } 50 | 51 | func (auth *UserAuth) GetEmail() string { 52 | return auth.Email 53 | } 54 | 55 | func TestMethodCall_Issue4(t *testing.T) { 56 | user := &UserWithAuth{ 57 | Auth: &UserAuth{Email: "watson@test.com"}, 58 | } 59 | 60 | if user.GetName() != "" { 61 | t.Fatal("User name not empty") 62 | } 63 | 64 | dataContext := ast.NewDataContext() 65 | err := dataContext.Add("User", user) 66 | assert.NoError(t, err) 67 | 68 | // Prepare knowledgebase library and load it with our rule. 69 | lib := ast.NewKnowledgeLibrary() 70 | rb := builder.NewRuleBuilder(lib) 71 | err = rb.BuildRuleFromResource("Test", "0.1.1", pkg.NewBytesResource([]byte(Rule4))) 72 | assert.NoError(t, err) 73 | kb, err := lib.NewKnowledgeBaseInstance("Test", "0.1.1") 74 | assert.NoError(t, err) 75 | eng1 := &engine.GruleEngine{MaxCycle: 3} 76 | err = eng1.Execute(dataContext, kb) 77 | assert.NoError(t, err) 78 | assert.Equal(t, "FromRuleScope4", user.GetName()) 79 | } 80 | -------------------------------------------------------------------------------- /examples/Issue5_test.go: -------------------------------------------------------------------------------- 1 | // Copyright hyperjumptech/grule-rule-engine Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package examples 16 | 17 | import ( 18 | "github.com/hyperjumptech/grule-rule-engine/ast" 19 | "github.com/hyperjumptech/grule-rule-engine/builder" 20 | "github.com/hyperjumptech/grule-rule-engine/engine" 21 | "github.com/hyperjumptech/grule-rule-engine/pkg" 22 | "github.com/stretchr/testify/assert" 23 | "testing" 24 | ) 25 | 26 | const ( 27 | Rule = ` 28 | rule UserTestRule3 "test 3" salience 10{ 29 | when 30 | User.GetName() == "Watson" 31 | then 32 | User.SetName("FromRuleScope3"); 33 | Retract("UserTestRule3"); 34 | } 35 | ` 36 | ) 37 | 38 | type AUser struct { 39 | Name string 40 | } 41 | 42 | func (u *AUser) GetName() string { 43 | return u.Name 44 | } 45 | 46 | func (u *AUser) SetName(name string) { 47 | u.Name = name 48 | } 49 | 50 | func TestMethodCall_Issue5(t *testing.T) { 51 | user := &AUser{ 52 | Name: "Watson", 53 | } 54 | 55 | dataContext := ast.NewDataContext() 56 | err := dataContext.Add("User", user) 57 | assert.NoError(t, err) 58 | 59 | // Prepare knowledgebase library and load it with our rule. 60 | lib := ast.NewKnowledgeLibrary() 61 | rb := builder.NewRuleBuilder(lib) 62 | err = rb.BuildRuleFromResource("Test", "0.1.1", pkg.NewBytesResource([]byte(Rule))) 63 | assert.NoError(t, err) 64 | eng1 := &engine.GruleEngine{MaxCycle: 5} 65 | kb, err := lib.NewKnowledgeBaseInstance("Test", "0.1.1") 66 | assert.NoError(t, err) 67 | err = eng1.Execute(dataContext, kb) 68 | assert.NoError(t, err) 69 | assert.Equal(t, "FromRuleScope3", user.GetName()) 70 | } 71 | -------------------------------------------------------------------------------- /examples/Issue7_test.go: -------------------------------------------------------------------------------- 1 | // Copyright hyperjumptech/grule-rule-engine Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package examples 16 | 17 | import ( 18 | "github.com/hyperjumptech/grule-rule-engine/ast" 19 | "github.com/hyperjumptech/grule-rule-engine/builder" 20 | "github.com/hyperjumptech/grule-rule-engine/engine" 21 | "github.com/hyperjumptech/grule-rule-engine/pkg" 22 | "github.com/stretchr/testify/assert" 23 | "testing" 24 | ) 25 | 26 | const ( 27 | Rule7 = ` 28 | rule UserTestRule7 "test 7" salience 10{ 29 | when 30 | User.Age > 1 31 | then 32 | User.SetName("FromRule"); 33 | Retract("UserTestRule7"); 34 | } 35 | ` 36 | ) 37 | 38 | type AUserIssue7 struct { 39 | Name string 40 | Age int 41 | } 42 | 43 | func (u *AUserIssue7) GetName() string { 44 | return u.Name 45 | } 46 | 47 | func (u *AUserIssue7) SetName(name interface{}) { 48 | u.Name = name.(string) 49 | } 50 | 51 | func TestMethodCall_Issue7(t *testing.T) { 52 | user := &AUserIssue7{ 53 | Name: "Watson", 54 | Age: 7, 55 | } 56 | 57 | dataContext := ast.NewDataContext() 58 | err := dataContext.Add("User", user) 59 | assert.NoError(t, err) 60 | 61 | // Prepare knowledgebase library and load it with our rule. 62 | lib := ast.NewKnowledgeLibrary() 63 | rb := builder.NewRuleBuilder(lib) 64 | err = rb.BuildRuleFromResource("Test", "0.1.1", pkg.NewBytesResource([]byte(Rule7))) 65 | assert.NoError(t, err) 66 | eng1 := &engine.GruleEngine{MaxCycle: 5} 67 | kb, err := lib.NewKnowledgeBaseInstance("Test", "0.1.1") 68 | assert.NoError(t, err) 69 | err = eng1.Execute(dataContext, kb) 70 | assert.NoError(t, err) 71 | assert.Equal(t, "FromRule", user.GetName()) 72 | } 73 | -------------------------------------------------------------------------------- /examples/KnowledgeBaseInstancePanic_test.go: -------------------------------------------------------------------------------- 1 | // Copyright hyperjumptech/grule-rule-engine Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package examples 16 | 17 | import ( 18 | "github.com/hyperjumptech/grule-rule-engine/ast" 19 | "github.com/hyperjumptech/grule-rule-engine/builder" 20 | "github.com/hyperjumptech/grule-rule-engine/pkg" 21 | "github.com/stretchr/testify/assert" 22 | "testing" 23 | ) 24 | 25 | func TestNoPanicForNoDescription(t *testing.T) { 26 | GRL := `rule TestNoDesc { when true then Ok(); }` 27 | lib := ast.NewKnowledgeLibrary() 28 | ruleBuilder := builder.NewRuleBuilder(lib) 29 | err := ruleBuilder.BuildRuleFromResource("CallingLog", "0.1.1", pkg.NewBytesResource([]byte(GRL))) 30 | assert.NoError(t, err) 31 | _, err = lib.NewKnowledgeBaseInstance("CallingLog", "0.1.1") 32 | assert.NoError(t, err) 33 | } 34 | -------------------------------------------------------------------------------- /examples/MatchingRules_test.go: -------------------------------------------------------------------------------- 1 | // Copyright hyperjumptech/grule-rule-engine Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package examples 16 | 17 | import ( 18 | "github.com/hyperjumptech/grule-rule-engine/ast" 19 | "github.com/hyperjumptech/grule-rule-engine/builder" 20 | "github.com/hyperjumptech/grule-rule-engine/engine" 21 | "github.com/hyperjumptech/grule-rule-engine/pkg" 22 | "github.com/stretchr/testify/assert" 23 | "testing" 24 | ) 25 | 26 | type Fact struct { 27 | NetAmount float32 28 | Distance int32 29 | Duration int32 30 | Result bool 31 | } 32 | 33 | const duplicateRulesWithDiffSalience = ` 34 | rule DuplicateRule1 "Duplicate Rule 1" salience 5 { 35 | when 36 | (Fact.Distance > 5000 && Fact.Duration > 120) && (Fact.Result == false) 37 | Then 38 | Fact.NetAmount=143.320007; 39 | Fact.Result=true; 40 | } 41 | 42 | rule DuplicateRule2 "Duplicate Rule 2" salience 6 { 43 | when 44 | (Fact.Distance > 5000 && Fact.Duration > 120) && (Fact.Result == false) 45 | Then 46 | Fact.NetAmount=143.320007; 47 | Fact.Result=true; 48 | } 49 | 50 | 51 | rule DuplicateRule3 "Duplicate Rule 3" salience 7 { 52 | when 53 | (Fact.Distance > 5000 && Fact.Duration > 120) && (Fact.Result == false) 54 | Then 55 | Fact.NetAmount=143.320007; 56 | Fact.Result=true; 57 | } 58 | 59 | 60 | rule DuplicateRule4 "Duplicate Rule 4" salience 8 { 61 | when 62 | (Fact.Distance > 5000 && Fact.Duration > 120) && (Fact.Result == false) 63 | Then 64 | Fact.NetAmount=143.320007; 65 | Fact.Result=true; 66 | } 67 | 68 | 69 | rule DuplicateRule5 "Duplicate Rule 5" salience 9 { 70 | when 71 | (Fact.Distance > 5000 && Fact.Duration == 120) && (Fact.Result == false) 72 | Then 73 | Output.NetAmount=143.320007; 74 | Fact.Result=true; 75 | }` 76 | 77 | func TestGruleEngine_FetchMatchingRules_Having_Diff_Salience(t *testing.T) { 78 | //Given 79 | fact := &Fact{ 80 | Distance: 6000, 81 | Duration: 121, 82 | } 83 | dctx := ast.NewDataContext() 84 | err := dctx.Add("Fact", fact) 85 | assert.NoError(t, err) 86 | lib := ast.NewKnowledgeLibrary() 87 | rb := builder.NewRuleBuilder(lib) 88 | err = rb.BuildRuleFromResource("conflict_rules_test", "0.1.1", pkg.NewBytesResource([]byte(duplicateRulesWithDiffSalience))) 89 | assert.NoError(t, err) 90 | kb, err := lib.NewKnowledgeBaseInstance("conflict_rules_test", "0.1.1") 91 | assert.NoError(t, err) 92 | 93 | //When 94 | e := engine.NewGruleEngine() 95 | ruleEntries, err := e.FetchMatchingRules(dctx, kb) 96 | assert.NoError(t, err) 97 | 98 | //Then 99 | assert.Equal(t, 4, len(ruleEntries)) 100 | assert.Equal(t, 8, ruleEntries[0].Salience) 101 | assert.Equal(t, 7, ruleEntries[1].Salience) 102 | assert.Equal(t, 6, ruleEntries[2].Salience) 103 | assert.Equal(t, 5, ruleEntries[3].Salience) 104 | } 105 | -------------------------------------------------------------------------------- /examples/MemoizeSliceFunction_test.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import ( 4 | "github.com/hyperjumptech/grule-rule-engine/ast" 5 | "github.com/hyperjumptech/grule-rule-engine/builder" 6 | "github.com/hyperjumptech/grule-rule-engine/engine" 7 | "github.com/hyperjumptech/grule-rule-engine/pkg" 8 | "github.com/stretchr/testify/assert" 9 | "testing" 10 | ) 11 | 12 | type TestData struct { 13 | Index int 14 | Strings []string 15 | Concatenation string 16 | } 17 | 18 | func (f *TestData) GetStrings() []string { 19 | return f.Strings 20 | } 21 | 22 | const rule = ` rule test { 23 | when 24 | Fact.Index < Fact.Strings.Len() 25 | then 26 | Fact.Concatenation = Fact.Concatenation + Fact.GetStrings()[Fact.Index]; 27 | Fact.Index = Fact.Index + 1; 28 | }` 29 | 30 | func TestSliceFunctionTest(t *testing.T) { 31 | fact := &TestData{ 32 | Index: 0, 33 | Strings: []string{"1", "2", "3"}, 34 | } 35 | 36 | dataContext := ast.NewDataContext() 37 | err := dataContext.Add("Fact", fact) 38 | assert.NoError(t, err) 39 | knowledgeLibrary := ast.NewKnowledgeLibrary() 40 | ruleBuilder := builder.NewRuleBuilder(knowledgeLibrary) 41 | err = ruleBuilder.BuildRuleFromResource("test", "0.0.1", pkg.NewBytesResource([]byte(rule))) 42 | assert.NoError(t, err) 43 | knowledgeBase, err := knowledgeLibrary.NewKnowledgeBaseInstance("test", "0.0.1") 44 | assert.NoError(t, err) 45 | engine := engine.NewGruleEngine() 46 | 47 | err = engine.Execute(dataContext, knowledgeBase) 48 | assert.NoError(t, err) 49 | assert.Equal(t, "123", fact.Concatenation) 50 | } 51 | -------------------------------------------------------------------------------- /examples/Multipe_knowledgebases_test.go: -------------------------------------------------------------------------------- 1 | // Copyright hyperjumptech/grule-rule-engine Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package examples 16 | 17 | import ( 18 | "github.com/hyperjumptech/grule-rule-engine/ast" 19 | "github.com/hyperjumptech/grule-rule-engine/builder" 20 | "github.com/hyperjumptech/grule-rule-engine/engine" 21 | "github.com/hyperjumptech/grule-rule-engine/pkg" 22 | "github.com/stretchr/testify/assert" 23 | "testing" 24 | ) 25 | 26 | // Tests for to check whether Grules support multiple KnowledgeBases 27 | const RideRule = ` 28 | rule RideRule "Ride Related Rule" salience 10 { 29 | when 30 | (RideFact.Distance > 5000 || RideFact.Duration > 120) || (RideFact.RideType == "On-Demand" && RideFact.IsFrequentCustomer == true) 31 | Then 32 | RideFact.NetAmount=143.320007; 33 | RideFact.Result=true; 34 | Retract("RideRule"); 35 | } 36 | ` 37 | 38 | const UserRule = ` 39 | rule UserRule "User Related Rule" salience 10 { 40 | when User.IsPalindrome(User.Name) == true 41 | Then 42 | User.Message = "Palindrome"; 43 | Retract("UserRule"); 44 | } 45 | ` 46 | 47 | type RideFact struct { 48 | Distance int32 49 | Duration int32 50 | RideType string 51 | IsFrequentCustomer bool 52 | Result bool 53 | NetAmount float32 54 | } 55 | 56 | type UserFact struct { 57 | Name string 58 | Message string 59 | } 60 | 61 | func (u *UserFact) IsPalindrome(name string) bool { 62 | for i := 0; i < len(name)/2; i++ { 63 | if name[i] != name[len(name)-i-1] { 64 | return false 65 | } 66 | } 67 | return true 68 | } 69 | 70 | func TestGruleEngine_Support_Multiple_KnowledgeBases(t *testing.T) { 71 | //Given 72 | user := &UserFact{ 73 | Name: "madam", 74 | Message: "Not a Palindrome", // Default 75 | } 76 | rideFact := &RideFact{ 77 | Distance: 6000, 78 | Duration: 121, 79 | } 80 | userDataContext := ast.NewDataContext() 81 | err := userDataContext.Add("User", user) 82 | if err != nil { 83 | t.Fatal(err) 84 | } 85 | rideDataContext := ast.NewDataContext() 86 | err = rideDataContext.Add("RideFact", rideFact) 87 | if err != nil { 88 | t.Fatal(err) 89 | } 90 | lib := ast.NewKnowledgeLibrary() 91 | ruleBuilder := builder.NewRuleBuilder(lib) 92 | 93 | //When 94 | err = ruleBuilder.BuildRuleFromResource("UserRules", "0.1.1", pkg.NewBytesResource([]byte(UserRule))) 95 | if err != nil { 96 | t.Fatal(err) 97 | } 98 | err = ruleBuilder.BuildRuleFromResource("RideRules", "0.1.1", pkg.NewBytesResource([]byte(RideRule))) 99 | if err != nil { 100 | t.Fatal(err) 101 | } 102 | userKnowledgeBase, err := lib.NewKnowledgeBaseInstance("UserRules", "0.1.1") 103 | assert.NoError(t, err) 104 | rideKnowledgeBase, err := lib.NewKnowledgeBaseInstance("RideRules", "0.1.1") 105 | assert.NoError(t, err) 106 | eng1 := engine.NewGruleEngine() 107 | err = eng1.Execute(rideDataContext, rideKnowledgeBase) 108 | if err != nil { 109 | t.Fatalf("Got error %v", err) 110 | } 111 | err = eng1.Execute(userDataContext, userKnowledgeBase) 112 | if err != nil { 113 | t.Fatalf("Got error %v", err) 114 | } 115 | 116 | //Then 117 | if user.Message != "Palindrome" { 118 | t.Fatalf("Expecting Palindrome") 119 | } 120 | 121 | if rideFact.NetAmount != float32(143.32) { 122 | t.Fatalf("NetAmount is not as expected") 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /examples/NegationSymbolExample_test.go: -------------------------------------------------------------------------------- 1 | // Copyright hyperjumptech/grule-rule-engine Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package examples 16 | 17 | import ( 18 | "github.com/hyperjumptech/grule-rule-engine/ast" 19 | "github.com/hyperjumptech/grule-rule-engine/builder" 20 | "github.com/hyperjumptech/grule-rule-engine/engine" 21 | "github.com/hyperjumptech/grule-rule-engine/pkg" 22 | "github.com/stretchr/testify/assert" 23 | "testing" 24 | ) 25 | 26 | type StructTest struct { 27 | StringValue string 28 | BoolValue bool 29 | } 30 | 31 | const ( 32 | NegationRule1 = ` 33 | rule NegationCheck "User Related Rule" salience 10 { 34 | when 35 | !(StructTest.StringValue != "ABC") 36 | Then 37 | StructTest.StringValue="ITS ABC"; 38 | Retract("NegationCheck"); 39 | } 40 | ` 41 | 42 | NegationRule2 = ` 43 | rule NegationCheck "User Related Rule" salience 10 { 44 | when 45 | !StructTest.BoolValue 46 | Then 47 | StructTest.StringValue="YES ITS NOT"; 48 | Retract("NegationCheck"); 49 | } 50 | ` 51 | ) 52 | 53 | func TestNegationSymbolExpressionAtom(t *testing.T) { 54 | structTest := &StructTest{ 55 | BoolValue: false, 56 | } 57 | 58 | dataContext := ast.NewDataContext() 59 | err := dataContext.Add("StructTest", structTest) 60 | assert.NoError(t, err) 61 | 62 | // Prepare knowledgebase library and load it with our rule. 63 | lib := ast.NewKnowledgeLibrary() 64 | rb := builder.NewRuleBuilder(lib) 65 | err = rb.BuildRuleFromResource("TestNegation", "1.0.0", pkg.NewBytesResource([]byte(NegationRule2))) 66 | assert.NoError(t, err) 67 | eng1 := &engine.GruleEngine{MaxCycle: 5} 68 | kb, err := lib.NewKnowledgeBaseInstance("TestNegation", "1.0.0") 69 | assert.NoError(t, err) 70 | err = eng1.Execute(dataContext, kb) 71 | assert.NoError(t, err) 72 | assert.Equal(t, "YES ITS NOT", structTest.StringValue) 73 | } 74 | 75 | func TestNegationSymbolExpression(t *testing.T) { 76 | structTest := &StructTest{ 77 | StringValue: "ABC", 78 | } 79 | 80 | dataContext := ast.NewDataContext() 81 | err := dataContext.Add("StructTest", structTest) 82 | assert.NoError(t, err) 83 | 84 | // Prepare knowledgebase library and load it with our rule. 85 | lib := ast.NewKnowledgeLibrary() 86 | rb := builder.NewRuleBuilder(lib) 87 | err = rb.BuildRuleFromResource("TestNegation", "1.0.0", pkg.NewBytesResource([]byte(NegationRule1))) 88 | assert.NoError(t, err) 89 | eng1 := &engine.GruleEngine{MaxCycle: 5} 90 | kb, err := lib.NewKnowledgeBaseInstance("TestNegation", "1.0.0") 91 | assert.NoError(t, err) 92 | err = eng1.Execute(dataContext, kb) 93 | assert.NoError(t, err) 94 | assert.Equal(t, "ITS ABC", structTest.StringValue) 95 | } 96 | -------------------------------------------------------------------------------- /examples/NilKnowledgeBasePanic_test.go: -------------------------------------------------------------------------------- 1 | // Copyright hyperjumptech/grule-rule-engine Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package examples 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/hyperjumptech/grule-rule-engine/ast" 21 | "github.com/hyperjumptech/grule-rule-engine/engine" 22 | 23 | "github.com/stretchr/testify/assert" 24 | ) 25 | 26 | func Test_NoPanicOnEmptyKnowledgeBase(t *testing.T) { 27 | // create a new fact for user 28 | user := &User{ 29 | Name: "Calo", 30 | Age: 0, 31 | Male: true, 32 | } 33 | // create an empty data context 34 | dataContext := ast.NewDataContext() 35 | // add the fact struct to the data context 36 | err := dataContext.Add("User", user) 37 | if err != nil { 38 | t.Fatal(err) 39 | } 40 | 41 | t.Run("with nil knowledge base in execute", func(t *testing.T) { 42 | eng := &engine.GruleEngine{MaxCycle: 10} 43 | err = eng.Execute(dataContext, nil) 44 | 45 | assert.NotNil(t, err) 46 | }) 47 | 48 | t.Run("with nil knowledge base in FetchMatchingRules", func(t *testing.T) { 49 | eng := &engine.GruleEngine{MaxCycle: 10} 50 | _, err = eng.FetchMatchingRules(dataContext, nil) 51 | 52 | assert.NotNil(t, err) 53 | }) 54 | } 55 | -------------------------------------------------------------------------------- /examples/NumberExponentExample_test.go: -------------------------------------------------------------------------------- 1 | // Copyright hyperjumptech/grule-rule-engine Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package examples 16 | 17 | import ( 18 | "github.com/hyperjumptech/grule-rule-engine/ast" 19 | "github.com/hyperjumptech/grule-rule-engine/builder" 20 | "github.com/hyperjumptech/grule-rule-engine/engine" 21 | "github.com/hyperjumptech/grule-rule-engine/pkg" 22 | "github.com/stretchr/testify/assert" 23 | "testing" 24 | ) 25 | 26 | type ExponentData struct { 27 | Check float64 28 | Set float64 29 | } 30 | 31 | const ExponentRule = ` 32 | rule ExponentCheck "User Related Rule" salience 10 { 33 | when 34 | ExponentData.Check == 6.67428e-11 35 | Then 36 | ExponentData.Set = .12345E+5; 37 | Retract("ExponentCheck"); 38 | } 39 | ` 40 | 41 | func TestEvaluateAndAssignExponentNumber(t *testing.T) { 42 | exponent := &ExponentData{ 43 | Check: 6.67428e-11, 44 | Set: 0, 45 | } 46 | 47 | dataContext := ast.NewDataContext() 48 | err := dataContext.Add("ExponentData", exponent) 49 | assert.NoError(t, err) 50 | 51 | // Prepare knowledgebase library and load it with our rule. 52 | lib := ast.NewKnowledgeLibrary() 53 | rb := builder.NewRuleBuilder(lib) 54 | err = rb.BuildRuleFromResource("TestExponent", "1.0.0", pkg.NewBytesResource([]byte(ExponentRule))) 55 | assert.NoError(t, err) 56 | eng1 := &engine.GruleEngine{MaxCycle: 5} 57 | kb, err := lib.NewKnowledgeBaseInstance("TestExponent", "1.0.0") 58 | assert.NoError(t, err) 59 | err = eng1.Execute(dataContext, kb) 60 | assert.NoError(t, err) 61 | assert.Equal(t, .12345e+5, exponent.Set) 62 | 63 | } 64 | -------------------------------------------------------------------------------- /examples/RemoveRuleEntry_test.go: -------------------------------------------------------------------------------- 1 | // Copyright hyperjumptech/grule-rule-engine Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package examples 16 | 17 | import ( 18 | "github.com/hyperjumptech/grule-rule-engine/ast" 19 | "github.com/hyperjumptech/grule-rule-engine/builder" 20 | "github.com/hyperjumptech/grule-rule-engine/engine" 21 | "github.com/hyperjumptech/grule-rule-engine/pkg" 22 | "github.com/stretchr/testify/assert" 23 | "testing" 24 | ) 25 | 26 | const ( 27 | RuleA = ` 28 | rule ColorCheck1 "test 1" salience 100 { 29 | when 30 | Color.Name.In("Grey", "Black") 31 | then 32 | Color.Message = "Its Grey!!!"; 33 | Retract("ColorCheck1"); 34 | } 35 | ` 36 | RuleB = ` 37 | rule ColorCheck1 "test 2" { 38 | when 39 | Color.Name.In("Black") 40 | then 41 | Color.Message = "Its Black!!!"; 42 | Retract("ColorCheck1"); 43 | } 44 | ` 45 | ) 46 | 47 | type ColorCheck struct { 48 | Name string 49 | Message string 50 | } 51 | 52 | func TestRemoveRuleEntry(t *testing.T) { 53 | color := &ColorCheck{ 54 | Name: "Black", 55 | Message: "", 56 | } 57 | 58 | dataContext := ast.NewDataContext() 59 | err := dataContext.Add("Color", color) 60 | assert.NoError(t, err) 61 | 62 | //Load RuleA into knowledgeBase 63 | lib := ast.NewKnowledgeLibrary() 64 | rb := builder.NewRuleBuilder(lib) 65 | err = rb.BuildRuleFromResource("Test", "0.1.1", pkg.NewBytesResource([]byte(RuleA))) 66 | assert.NoError(t, err) 67 | kb, err := lib.NewKnowledgeBaseInstance("Test", "0.1.1") 68 | assert.NoError(t, err) 69 | eng := &engine.GruleEngine{MaxCycle: 1} 70 | err = eng.Execute(dataContext, kb) 71 | assert.NoError(t, err) 72 | assert.Equal(t, "Its Grey!!!", color.Message) 73 | 74 | //Remove RuleEntry A 75 | kb.RemoveRuleEntry("ColorCheck1") 76 | lib.RemoveRuleEntry("ColorCheck1", "Test", "0.1.1") 77 | 78 | //Add RuleB again, which is similar to RuleA except its output 79 | err = rb.BuildRuleFromResource("Test", "0.1.1", pkg.NewBytesResource([]byte(RuleB))) 80 | assert.NoError(t, err) 81 | kb, err = lib.NewKnowledgeBaseInstance("Test", "0.1.1") 82 | assert.NoError(t, err) 83 | eng = &engine.GruleEngine{MaxCycle: 1} 84 | err = eng.Execute(dataContext, kb) 85 | assert.NoError(t, err) 86 | assert.Equal(t, "Its Black!!!", color.Message) 87 | } 88 | -------------------------------------------------------------------------------- /examples/SalienceExample_test.go: -------------------------------------------------------------------------------- 1 | // Copyright hyperjumptech/grule-rule-engine Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package examples 16 | 17 | import ( 18 | "github.com/hyperjumptech/grule-rule-engine/ast" 19 | "github.com/hyperjumptech/grule-rule-engine/builder" 20 | "github.com/hyperjumptech/grule-rule-engine/engine" 21 | "github.com/hyperjumptech/grule-rule-engine/pkg" 22 | "github.com/stretchr/testify/assert" 23 | "testing" 24 | ) 25 | 26 | type ValueData struct { 27 | IntValue int 28 | Rating string 29 | Expect string 30 | } 31 | 32 | const ( 33 | SalienceGRL = ` 34 | 35 | // Highest salience, if IntValue is bellow 33, all rule may match but this one take precedence 36 | rule LowRule "If its on the low range, rating is low" salience 30 { 37 | When 38 | V.Rating == "" && 39 | V.IntValue < 33 40 | Then 41 | V.Rating = "Low"; 42 | } 43 | 44 | // Lower salience, if IntValue is bellow 66, some rule may match but this one take precedence, unless there rule with highest salience are met (LowRule). 45 | rule MediumRule "If its on the medium range, rating is medium" salience 20 { 46 | When 47 | V.Rating == "" && 48 | V.IntValue < 66 49 | Then 50 | V.Rating = "Medium"; 51 | } 52 | 53 | // Even lower salience 54 | rule HighRule "If its on the high range, rating is high" salience 10 { 55 | When 56 | V.Rating == "" && 57 | V.IntValue < 300 58 | Then 59 | V.Rating = "High"; 60 | } 61 | 62 | 63 | // Lowest and negative salience, will win the last and ensure other higher salience take precedence 64 | rule ImpossibleRule "If its on the very very very high range, rating is high" salience -100 { 65 | When 66 | V.Rating == "" 67 | Then 68 | V.Rating = "This is not right"; 69 | } 70 | ` 71 | ) 72 | 73 | func TestSalience(t *testing.T) { 74 | testData := []*ValueData{ 75 | &ValueData{ 76 | IntValue: 10, 77 | Expect: "Low", 78 | }, 79 | &ValueData{ 80 | IntValue: 20, 81 | Expect: "Low", 82 | }, 83 | &ValueData{ 84 | IntValue: 30, 85 | Expect: "Low", 86 | }, 87 | &ValueData{ 88 | IntValue: 40, 89 | Expect: "Medium", 90 | }, 91 | &ValueData{ 92 | IntValue: 50, 93 | Expect: "Medium", 94 | }, 95 | &ValueData{ 96 | IntValue: 60, 97 | Expect: "Medium", 98 | }, 99 | &ValueData{ 100 | IntValue: 70, 101 | Expect: "High", 102 | }, 103 | &ValueData{ 104 | IntValue: 80, 105 | Expect: "High", 106 | }, 107 | &ValueData{ 108 | IntValue: 90, 109 | Expect: "High", 110 | }, 111 | &ValueData{ 112 | IntValue: 1000000, 113 | Expect: "This is not right", 114 | }, 115 | } 116 | 117 | // Prepare knowledgebase library and load it with our rule. 118 | lib := ast.NewKnowledgeLibrary() 119 | rb := builder.NewRuleBuilder(lib) 120 | byteArr := pkg.NewBytesResource([]byte(SalienceGRL)) 121 | err := rb.BuildRuleFromResource("Tutorial", "0.0.1", byteArr) 122 | assert.NoError(t, err) 123 | 124 | engine := engine.NewGruleEngine() 125 | 126 | knowledgeBase, err := lib.NewKnowledgeBaseInstance("Tutorial", "0.0.1") 127 | assert.NoError(t, err) 128 | 129 | for _, td := range testData { 130 | dataCtx := ast.NewDataContext() 131 | err := dataCtx.Add("V", td) 132 | assert.NoError(t, err) 133 | 134 | err = engine.Execute(dataCtx, knowledgeBase) 135 | assert.NoError(t, err) 136 | assert.Equal(t, td.Expect, td.Rating) 137 | } 138 | 139 | } 140 | -------------------------------------------------------------------------------- /examples/Serialization_test.go: -------------------------------------------------------------------------------- 1 | // Copyright hyperjumptech/grule-rule-engine Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package examples 16 | 17 | import ( 18 | "bytes" 19 | "github.com/hyperjumptech/grule-rule-engine/ast" 20 | "github.com/hyperjumptech/grule-rule-engine/builder" 21 | "github.com/hyperjumptech/grule-rule-engine/pkg" 22 | "github.com/stretchr/testify/assert" 23 | "io" 24 | "os" 25 | "testing" 26 | ) 27 | 28 | func TestSerialization(t *testing.T) { 29 | lib := ast.NewKnowledgeLibrary() 30 | rb := builder.NewRuleBuilder(lib) 31 | err := rb.BuildRuleFromResource("Purchase Calculator", "0.0.1", pkg.NewFileResource("CashFlowRule.grl")) 32 | assert.NoError(t, err) 33 | 34 | kb := lib.GetKnowledgeBase("Purchase Calculator", "0.0.1") 35 | cat := kb.MakeCatalog() 36 | 37 | buff1 := &bytes.Buffer{} 38 | err = cat.WriteCatalogToWriter(buff1) 39 | assert.Nil(t, err) 40 | 41 | buff2 := bytes.NewBuffer(buff1.Bytes()) 42 | cat2 := &ast.Catalog{} 43 | err = cat2.ReadCatalogFromReader(buff2) 44 | if err != nil && err != io.EOF { 45 | assert.Nil(t, err) 46 | } 47 | 48 | kb2, err := cat2.BuildKnowledgeBase() 49 | assert.NoError(t, err) 50 | assert.True(t, kb.IsIdentical(kb2)) 51 | } 52 | 53 | func TestSerializationOnFile(t *testing.T) { 54 | testFile := "Test.GRB" 55 | 56 | // SAFING INTO FILE 57 | // First prepare our library for loading the orignal GRL 58 | lib := ast.NewKnowledgeLibrary() 59 | rb := builder.NewRuleBuilder(lib) 60 | err := rb.BuildRuleFromResource("Purchase Calculator", "0.0.1", pkg.NewFileResource("CashFlowRule.grl")) 61 | assert.NoError(t, err) 62 | 63 | // Lets create the file to write into 64 | f, err := os.OpenFile(testFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) 65 | assert.Nil(t, err) 66 | 67 | // Save the knowledge base into the file and close it. 68 | err = lib.StoreKnowledgeBaseToWriter(f, "Purchase Calculator", "0.0.1") 69 | assert.Nil(t, err) 70 | _ = f.Close() 71 | 72 | // LOADING FROM FILE 73 | // Lets assume we are using different library, so create a new one 74 | lib2 := ast.NewKnowledgeLibrary() 75 | 76 | // Open the existing safe file 77 | f2, err := os.Open(testFile) 78 | assert.Nil(t, err) 79 | 80 | // Load the file directly into the library and close the file 81 | kb2, err := lib2.LoadKnowledgeBaseFromReader(f2, true) 82 | assert.Nil(t, err) 83 | _ = f2.Close() 84 | 85 | // Delete the test file 86 | err = os.Remove(testFile) 87 | assert.Nil(t, err) 88 | 89 | // compare that the original knowledgebase is exacly the same to the loaded one. 90 | assert.True(t, lib.GetKnowledgeBase("Purchase Calculator", "0.0.1").IsIdentical(kb2)) 91 | } 92 | -------------------------------------------------------------------------------- /examples/StringFunctions_test.go: -------------------------------------------------------------------------------- 1 | // Copyright hyperjumptech/grule-rule-engine Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package examples 16 | 17 | import ( 18 | "github.com/hyperjumptech/grule-rule-engine/ast" 19 | "github.com/hyperjumptech/grule-rule-engine/builder" 20 | "github.com/hyperjumptech/grule-rule-engine/engine" 21 | "github.com/hyperjumptech/grule-rule-engine/pkg" 22 | "github.com/stretchr/testify/assert" 23 | "testing" 24 | ) 25 | 26 | const ( 27 | strInConditionRule = ` 28 | rule StrInConditionCheck "test 1" { 29 | when 30 | Color.Name.In("Black", "Yellow") 31 | then 32 | Color.Message = "Its either black or yellow!!!"; 33 | Retract("StrInConditionCheck"); 34 | } 35 | ` 36 | strMatchStringConditionRule = ` 37 | rule strMatchStringConditionCheck "test 2" { 38 | when 39 | Color.Name.MatchString("B([a-z]+)ck") 40 | then 41 | Color.Message = "yes its Black!!!"; 42 | Retract("strMatchStringConditionCheck"); 43 | } 44 | ` 45 | ) 46 | 47 | type Color struct { 48 | Name string 49 | Message string 50 | } 51 | 52 | func TestStringInExample(t *testing.T) { 53 | color := &Color{ 54 | Name: "Black", 55 | Message: "", 56 | } 57 | dataContext := ast.NewDataContext() 58 | err := dataContext.Add("Color", color) 59 | assert.NoError(t, err) 60 | 61 | // Prepare knowledgebase library and load it with our rule. 62 | lib := ast.NewKnowledgeLibrary() 63 | rb := builder.NewRuleBuilder(lib) 64 | err = rb.BuildRuleFromResource("Test", "0.1.1", pkg.NewBytesResource([]byte(strInConditionRule))) 65 | assert.NoError(t, err) 66 | kb, err := lib.NewKnowledgeBaseInstance("Test", "0.1.1") 67 | assert.NoError(t, err) 68 | eng1 := &engine.GruleEngine{MaxCycle: 1} 69 | err = eng1.Execute(dataContext, kb) 70 | assert.NoError(t, err) 71 | assert.Equal(t, "Its either black or yellow!!!", color.Message) 72 | } 73 | 74 | func TestStringMatchStringExample(t *testing.T) { 75 | color := &Color{ 76 | Name: "Black", 77 | Message: "", 78 | } 79 | 80 | dataContext := ast.NewDataContext() 81 | err := dataContext.Add("Color", color) 82 | assert.NoError(t, err) 83 | 84 | // Prepare knowledgebase library and load it with our rule. 85 | lib := ast.NewKnowledgeLibrary() 86 | rb := builder.NewRuleBuilder(lib) 87 | err = rb.BuildRuleFromResource("Test", "0.1.1", pkg.NewBytesResource([]byte(strMatchStringConditionRule))) 88 | assert.NoError(t, err) 89 | kb, err := lib.NewKnowledgeBaseInstance("Test", "0.1.1") 90 | assert.NoError(t, err) 91 | eng1 := &engine.GruleEngine{MaxCycle: 1} 92 | err = eng1.Execute(dataContext, kb) 93 | assert.NoError(t, err) 94 | assert.Equal(t, "yes its Black!!!", color.Message) 95 | } 96 | -------------------------------------------------------------------------------- /examples/TutorialExample_test.go: -------------------------------------------------------------------------------- 1 | // Copyright hyperjumptech/grule-rule-engine Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package examples 16 | 17 | import ( 18 | "fmt" 19 | "github.com/hyperjumptech/grule-rule-engine/ast" 20 | "github.com/hyperjumptech/grule-rule-engine/builder" 21 | "github.com/hyperjumptech/grule-rule-engine/engine" 22 | "github.com/hyperjumptech/grule-rule-engine/pkg" 23 | "github.com/stretchr/testify/assert" 24 | "testing" 25 | "time" 26 | ) 27 | 28 | type MyFact struct { 29 | IntAttribute int64 30 | StringAttribute string 31 | BooleanAttribute bool 32 | FloatAttribute float64 33 | TimeAttribute time.Time 34 | WhatToSay string 35 | } 36 | 37 | func (mf *MyFact) GetWhatToSay(sentence string) string { 38 | return fmt.Sprintf("Let say \"%s\"", sentence) 39 | } 40 | 41 | func TestTutorial(t *testing.T) { 42 | myFact := &MyFact{ 43 | IntAttribute: 123, 44 | StringAttribute: "Some string value", 45 | BooleanAttribute: true, 46 | FloatAttribute: 1.234, 47 | TimeAttribute: time.Now(), 48 | } 49 | dataCtx := ast.NewDataContext() 50 | err := dataCtx.Add("MF", myFact) 51 | assert.NoError(t, err) 52 | 53 | // Prepare knowledgebase library and load it with our rule. 54 | knowledgeLibrary := ast.NewKnowledgeLibrary() 55 | ruleBuilder := builder.NewRuleBuilder(knowledgeLibrary) 56 | 57 | drls := ` 58 | rule CheckValues "Check the default values" salience 10 { 59 | when 60 | MF.IntAttribute == 123 && MF.StringAttribute == "Some string value" 61 | then 62 | MF.WhatToSay = MF.GetWhatToSay("Hello Grule"); 63 | Retract("CheckValues"); 64 | } 65 | ` 66 | byteArr := pkg.NewBytesResource([]byte(drls)) 67 | err = ruleBuilder.BuildRuleFromResource("Tutorial", "0.0.1", byteArr) 68 | assert.NoError(t, err) 69 | 70 | knowledgeBase, err := knowledgeLibrary.NewKnowledgeBaseInstance("Tutorial", "0.0.1") 71 | assert.NoError(t, err) 72 | 73 | engine := engine.NewGruleEngine() 74 | err = engine.Execute(dataCtx, knowledgeBase) 75 | assert.NoError(t, err) 76 | assert.Equal(t, "Let say \"Hello Grule\"", myFact.WhatToSay) 77 | println(myFact.WhatToSay) 78 | } 79 | -------------------------------------------------------------------------------- /examples/UnicodeRule_test.go: -------------------------------------------------------------------------------- 1 | // Copyright hyperjumptech/grule-rule-engine Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package examples 16 | 17 | import ( 18 | "fmt" 19 | "github.com/hyperjumptech/grule-rule-engine/ast" 20 | "github.com/hyperjumptech/grule-rule-engine/builder" 21 | "github.com/hyperjumptech/grule-rule-engine/engine" 22 | "github.com/hyperjumptech/grule-rule-engine/pkg" 23 | "github.com/stretchr/testify/assert" 24 | "testing" 25 | "time" 26 | ) 27 | 28 | type MyḞact struct { 29 | IntAttribute int64 30 | StringǍttribute string 31 | BooleanAttribute bool 32 | ḞloatAttribute float64 33 | TimeAttribute time.Time 34 | WhatToSay string 35 | } 36 | 37 | func (mf *MyḞact) GetẀhatToSay(sentence string) string { 38 | return fmt.Sprintf("Let say \"%s\"", sentence) 39 | } 40 | 41 | func TestUnicodeTutorial(t *testing.T) { 42 | myFact := &MyḞact{ 43 | IntAttribute: 123, 44 | StringǍttribute: "Some string vǍlue", 45 | BooleanAttribute: true, 46 | ḞloatAttribute: 1.234, 47 | TimeAttribute: time.Now(), 48 | } 49 | dataCtx := ast.NewDataContext() 50 | err := dataCtx.Add("MḞ", myFact) 51 | assert.NoError(t, err) 52 | 53 | // Prepare knowledgebase library and load it with our rule. 54 | knowledgeLibrary := ast.NewKnowledgeLibrary() 55 | ruleBuilder := builder.NewRuleBuilder(knowledgeLibrary) 56 | 57 | drls := ` 58 | rule ChĕckValuĕs "Check the default values" salience 10 { 59 | when 60 | MḞ.IntAttribute == 123 && MḞ.StringǍttribute == "Some string vǍlue" 61 | then 62 | MḞ.WhatToSay = MḞ.GetẀhatToSay("HellǑ Grule"); 63 | Retract("ChĕckValuĕs"); 64 | } 65 | ` 66 | byteArr := pkg.NewBytesResource([]byte(drls)) 67 | err = ruleBuilder.BuildRuleFromResource("Tutorial", "0.0.1", byteArr) 68 | assert.NoError(t, err) 69 | 70 | knowledgeBase, err := knowledgeLibrary.NewKnowledgeBaseInstance("Tutorial", "0.0.1") 71 | assert.NoError(t, err) 72 | 73 | engine := engine.NewGruleEngine() 74 | err = engine.Execute(dataCtx, knowledgeBase) 75 | assert.NoError(t, err) 76 | assert.Equal(t, "Let say \"HellǑ Grule\"", myFact.WhatToSay) 77 | println(myFact.WhatToSay) 78 | } 79 | -------------------------------------------------------------------------------- /examples/benchmark/DummyRuleMaker.go: -------------------------------------------------------------------------------- 1 | package benchmark 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "math/rand" 7 | "os" 8 | "strconv" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | var ( 14 | words []string 15 | dupCheck map[string]bool 16 | ) 17 | 18 | func init() { 19 | f, err := os.Open("words.txt") 20 | if err != nil { 21 | panic(err.Error()) 22 | } 23 | defer f.Close() 24 | buf := bufio.NewReader(f) 25 | words = make([]string, 0) 26 | for true { 27 | str, err := buf.ReadString('\n') 28 | if err != nil { 29 | break 30 | } 31 | words = append(words, strings.TrimSpace(str)) 32 | } 33 | rand.Seed(time.Now().Unix()) 34 | dupCheck = make(map[string]bool) 35 | } 36 | 37 | // GetWord get a random english word 38 | func GetWord(t bool) string { 39 | w := words[rand.Intn(len(words))] 40 | if t { 41 | return strings.Title(w) 42 | } 43 | return w 44 | } 45 | 46 | // MakeRule make a single dummy rule 47 | func MakeRule(seq int) string { 48 | var rname string 49 | for true { 50 | rname = GetWord(true) + GetWord(true) + GetWord(true) 51 | if _, ok := dupCheck[rname]; !ok { 52 | break 53 | } 54 | } 55 | buff := &bytes.Buffer{} 56 | buff.WriteString("rule ") 57 | buff.WriteString(rname) 58 | buff.WriteString(" \"") 59 | buff.WriteString(strconv.Itoa(seq)) 60 | buff.WriteString(" ") 61 | buff.WriteString(GetWord(true)) 62 | buff.WriteString(" ") 63 | buff.WriteString(GetWord(true)) 64 | buff.WriteString("\"") 65 | buff.WriteString(" salience ") 66 | buff.WriteString(strconv.Itoa(rand.Intn(100) + 10)) 67 | buff.WriteString(" {\n\t") 68 | buff.WriteString("when\n\t\t") 69 | buff.WriteString(GetWord(false)) 70 | buff.WriteString(".") 71 | buff.WriteString(GetWord(true)) 72 | buff.WriteString(" == ") 73 | buff.WriteString(GetWord(false)) 74 | buff.WriteString(".") 75 | buff.WriteString(GetWord(true)) 76 | buff.WriteString("() && \n\t\t") 77 | buff.WriteString(GetWord(false)) 78 | buff.WriteString(".") 79 | buff.WriteString(GetWord(true)) 80 | buff.WriteString(" <= ") 81 | buff.WriteString(GetWord(false)) 82 | buff.WriteString(".") 83 | buff.WriteString(GetWord(true)) 84 | buff.WriteString("()\n\tthen\n\t\t") 85 | buff.WriteString(GetWord(false)) 86 | buff.WriteString(".") 87 | buff.WriteString(GetWord(true)) 88 | buff.WriteString("();\n\t\t") 89 | buff.WriteString(GetWord(false)) 90 | buff.WriteString(".") 91 | buff.WriteString(GetWord(true)) 92 | buff.WriteString(" = ") 93 | buff.WriteString(GetWord(false)) 94 | buff.WriteString(".") 95 | buff.WriteString(GetWord(true)) 96 | buff.WriteString("();") 97 | buff.WriteString("\n}\n\n") 98 | 99 | return buff.String() 100 | } 101 | 102 | // GenRandomRule simply generate count number of simple parse-able rule into a file 103 | func GenRandomRule(fileName string, count int) error { 104 | f, err := os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY, 0666) 105 | if err != nil { 106 | return err 107 | } 108 | defer f.Close() 109 | for i := 1; i <= count; i++ { 110 | _, err := f.WriteString(MakeRule(i)) 111 | if err != nil { 112 | return err 113 | } 114 | } 115 | return nil 116 | } 117 | -------------------------------------------------------------------------------- /examples/benchmark/ExecRules_benchmark_test.go: -------------------------------------------------------------------------------- 1 | // Copyright hyperjumptech/grule-rule-engine Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package benchmark 16 | 17 | import ( 18 | "fmt" 19 | "github.com/hyperjumptech/grule-rule-engine/ast" 20 | "github.com/hyperjumptech/grule-rule-engine/builder" 21 | "github.com/hyperjumptech/grule-rule-engine/engine" 22 | "github.com/hyperjumptech/grule-rule-engine/pkg" 23 | "io/ioutil" 24 | "testing" 25 | ) 26 | 27 | /** 28 | Benchmarking `engine.Execute` function by running 100 and 1000 rules with different N values 29 | Please refer docs/benchmarking_en.md for more info 30 | */ 31 | 32 | var knowledgeBase *ast.KnowledgeBase 33 | 34 | func Benchmark_Grule_Execution_Engine(b *testing.B) { 35 | rules := []struct { 36 | name string 37 | fun func() 38 | }{ 39 | {"100 rules", load100RulesIntoKnowledgebase}, 40 | {"1000 rules", load1000RulesIntoKnowledgebase}, 41 | } 42 | for _, rule := range rules { 43 | for k := 0; k < 10; k++ { 44 | b.Run(fmt.Sprintf("%s", rule.name), func(b *testing.B) { 45 | rule.fun() 46 | for i := 0; i < b.N; i++ { 47 | f1 := RideFact{ 48 | Distance: 6000, 49 | Duration: 121, 50 | } 51 | e := engine.NewGruleEngine() 52 | //Fact1 53 | dataCtx := ast.NewDataContext() 54 | err := dataCtx.Add("Fact", &f1) 55 | if err != nil { 56 | b.Fail() 57 | } 58 | err = e.Execute(dataCtx, knowledgeBase) 59 | if err != nil { 60 | fmt.Print(err) 61 | } 62 | } 63 | }) 64 | } 65 | } 66 | } 67 | 68 | func load100RulesIntoKnowledgebase() { 69 | input, _ := ioutil.ReadFile("100_rules.grl") 70 | rules := string(input) 71 | fact := &RideFact{ 72 | Distance: 6000, 73 | Duration: 121, 74 | } 75 | dctx := ast.NewDataContext() 76 | _ = dctx.Add("Fact", fact) 77 | 78 | lib := ast.NewKnowledgeLibrary() 79 | rb := builder.NewRuleBuilder(lib) 80 | _ = rb.BuildRuleFromResource("exec_rules_test", "0.1.1", pkg.NewBytesResource([]byte(rules))) 81 | knowledgeBase, _ = lib.NewKnowledgeBaseInstance("exec_rules_test", "0.1.1") 82 | 83 | } 84 | 85 | func load1000RulesIntoKnowledgebase() { 86 | input, _ := ioutil.ReadFile("1000_rules.grl") 87 | rules := string(input) 88 | fact := &RideFact{ 89 | Distance: 6000, 90 | Duration: 121, 91 | } 92 | dctx := ast.NewDataContext() 93 | _ = dctx.Add("Fact", fact) 94 | 95 | lib := ast.NewKnowledgeLibrary() 96 | rb := builder.NewRuleBuilder(lib) 97 | _ = rb.BuildRuleFromResource("exec_rules_test", "0.1.1", pkg.NewBytesResource([]byte(rules))) 98 | knowledgeBase, _ = lib.NewKnowledgeBaseInstance("exec_rules_test", "0.1.1") 99 | } 100 | -------------------------------------------------------------------------------- /examples/benchmark/GRBPerformance_test.go: -------------------------------------------------------------------------------- 1 | package benchmark 2 | 3 | import ( 4 | "github.com/hyperjumptech/grule-rule-engine/ast" 5 | "github.com/hyperjumptech/grule-rule-engine/builder" 6 | "github.com/hyperjumptech/grule-rule-engine/pkg" 7 | "github.com/stretchr/testify/assert" 8 | "os" 9 | "testing" 10 | "time" 11 | ) 12 | 13 | var ( 14 | ruleCount = 5000 15 | 16 | grlFile = "5000_dummy_rules.grl" 17 | grbFile = "5000_dummy_rules.grb" 18 | 19 | knowledgeName = "Dummy Knowledge" 20 | knowledgeVersion = "v1.0.0" 21 | ) 22 | 23 | func TestSerializationPerformanceOnFile(t *testing.T) { 24 | if testing.Short() { 25 | return 26 | } 27 | 28 | t.Log("GENERATING DUMMY RULES") 29 | err := GenRandomRule(grlFile, ruleCount) 30 | assert.Nil(t, err) 31 | 32 | t.Log("LOADING GRL") 33 | timer := time.Now() 34 | 35 | // SAFING INTO FILE 36 | // First prepare our library for loading the orignal GRL 37 | lib := ast.NewKnowledgeLibrary() 38 | rb := builder.NewRuleBuilder(lib) 39 | err = rb.BuildRuleFromResource(knowledgeName, knowledgeVersion, pkg.NewFileResource(grlFile)) 40 | assert.NoError(t, err) 41 | 42 | durationA := time.Since(timer) 43 | t.Log("SAVING BINARY INTO GRB") 44 | timer = time.Now() 45 | 46 | // Lets create the file to write into 47 | f, err := os.OpenFile(grbFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) 48 | assert.Nil(t, err) 49 | 50 | // Save the knowledge base into the file and close it. 51 | err = lib.StoreKnowledgeBaseToWriter(f, knowledgeName, knowledgeVersion) 52 | assert.Nil(t, err) 53 | _ = f.Close() 54 | 55 | durationB := time.Since(timer) 56 | t.Log("LOADING BINARY FROM GRB") 57 | timer = time.Now() 58 | 59 | // LOADING FROM FILE 60 | // Lets assume we are using different library, so create a new one 61 | lib2 := ast.NewKnowledgeLibrary() 62 | 63 | // Open the existing safe file 64 | f2, err := os.Open(grbFile) 65 | assert.Nil(t, err) 66 | 67 | // Load the file directly into the library and close the file 68 | kb2, err := lib2.LoadKnowledgeBaseFromReader(f2, true) 69 | assert.Nil(t, err) 70 | _ = f2.Close() 71 | 72 | durationC := time.Since(timer) 73 | 74 | t.Log("-------------------------------------------") 75 | t.Logf("Load GRL duration : %d ms", durationA.Milliseconds()) 76 | t.Logf("Saving GRB duration : %d ms", durationB.Milliseconds()) 77 | t.Logf("Load GRB duration : %d ms", durationC.Milliseconds()) 78 | 79 | // Delete the test file 80 | err = os.Remove(grlFile) 81 | assert.Nil(t, err) 82 | err = os.Remove(grbFile) 83 | assert.Nil(t, err) 84 | 85 | // compare that the original knowledgebase is exacly the same to the loaded one. 86 | assert.True(t, lib.GetKnowledgeBase(knowledgeName, knowledgeVersion).IsIdentical(kb2)) 87 | } 88 | -------------------------------------------------------------------------------- /examples/benchmark/LoadRules_benchmark_test.go: -------------------------------------------------------------------------------- 1 | // Copyright hyperjumptech/grule-rule-engine Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package benchmark 16 | 17 | import ( 18 | "fmt" 19 | "github.com/hyperjumptech/grule-rule-engine/ast" 20 | "github.com/hyperjumptech/grule-rule-engine/builder" 21 | "github.com/hyperjumptech/grule-rule-engine/pkg" 22 | "io/ioutil" 23 | "testing" 24 | ) 25 | 26 | /* 27 | * 28 | 29 | Benchmarking `ast.KnowledgeBase` by loading 100 and 1000 rules into knowledgeBase 30 | Please refer docs/benchmarking_en.md for more info 31 | */ 32 | type RideFact struct { 33 | Distance int32 34 | Duration int32 35 | RideType string 36 | IsFrequentCustomer bool 37 | Result bool 38 | NetAmount float32 39 | } 40 | 41 | func Benchmark_Grule_Load_Rules(b *testing.B) { 42 | rules := []struct { 43 | name string 44 | fun func() 45 | }{ 46 | {"100 rules", load100RulesIntoKnowledgeBase}, 47 | {"1000 rules", load1000RulesIntoKnowledgeBase}, 48 | } 49 | for _, rule := range rules { 50 | for k := 0; k < 10; k++ { 51 | b.Run(fmt.Sprintf("%s", rule.name), func(b *testing.B) { 52 | for i := 0; i < b.N; i++ { 53 | rule.fun() 54 | } 55 | }) 56 | } 57 | } 58 | } 59 | 60 | func load100RulesIntoKnowledgeBase() { 61 | input, _ := ioutil.ReadFile("100_rules.grl") 62 | rules := string(input) 63 | fact := &RideFact{ 64 | Distance: 6000, 65 | Duration: 121, 66 | } 67 | dctx := ast.NewDataContext() 68 | _ = dctx.Add("Fact", fact) 69 | 70 | lib := ast.NewKnowledgeLibrary() 71 | rb := builder.NewRuleBuilder(lib) 72 | _ = rb.BuildRuleFromResource("load_rules_test", "0.1.1", pkg.NewBytesResource([]byte(rules))) 73 | _, _ = lib.NewKnowledgeBaseInstance("load_rules_test", "0.1.1") 74 | } 75 | 76 | func load1000RulesIntoKnowledgeBase() { 77 | input, _ := ioutil.ReadFile("1000_rules.grl") 78 | rules := string(input) 79 | fact := &RideFact{ 80 | Distance: 6000, 81 | Duration: 121, 82 | } 83 | dctx := ast.NewDataContext() 84 | _ = dctx.Add("Fact", fact) 85 | 86 | lib := ast.NewKnowledgeLibrary() 87 | rb := builder.NewRuleBuilder(lib) 88 | _ = rb.BuildRuleFromResource("load_rules_test", "0.1.1", pkg.NewBytesResource([]byte(rules))) 89 | _, _ = lib.NewKnowledgeBaseInstance("load_rules_test", "0.1.1") 90 | } 91 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hyperjumptech/grule-rule-engine 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 7 | github.com/bmatcuk/doublestar v1.3.4 8 | github.com/go-git/go-billy/v5 v5.5.0 9 | github.com/go-git/go-git/v5 v5.11.0 10 | github.com/google/uuid v1.3.0 11 | github.com/hyperjumptech/hyper-mux v1.1.0 12 | github.com/sirupsen/logrus v1.9.3 13 | github.com/stretchr/testify v1.8.4 14 | go.uber.org/zap v1.25.0 15 | gopkg.in/src-d/go-billy.v4 v4.3.2 16 | ) 17 | 18 | require ( 19 | dario.cat/mergo v1.0.0 // indirect 20 | github.com/Microsoft/go-winio v0.6.1 // indirect 21 | github.com/NYTimes/gziphandler v1.1.1 // indirect 22 | github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect 23 | github.com/cloudflare/circl v1.3.3 // indirect 24 | github.com/cyphar/filepath-securejoin v0.2.4 // indirect 25 | github.com/davecgh/go-spew v1.1.1 // indirect 26 | github.com/emirpasic/gods v1.18.1 // indirect 27 | github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect 28 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 29 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect 30 | github.com/kevinburke/ssh_config v1.2.0 // indirect 31 | github.com/pjbgf/sha1cd v0.3.0 // indirect 32 | github.com/pmezard/go-difflib v1.0.0 // indirect 33 | github.com/rs/cors v1.8.0 // indirect 34 | github.com/sergi/go-diff v1.1.0 // indirect 35 | github.com/skeema/knownhosts v1.2.1 // indirect 36 | github.com/xanzy/ssh-agent v0.3.3 // indirect 37 | go.uber.org/multierr v1.10.0 // indirect 38 | golang.org/x/crypto v0.18.0 // indirect 39 | golang.org/x/mod v0.12.0 // indirect 40 | golang.org/x/net v0.19.0 // indirect 41 | golang.org/x/sys v0.16.0 // indirect 42 | golang.org/x/tools v0.13.0 // indirect 43 | gopkg.in/warnings.v0 v0.1.2 // indirect 44 | gopkg.in/yaml.v3 v3.0.1 // indirect 45 | ) 46 | -------------------------------------------------------------------------------- /gopher-grule.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperjumptech/grule-rule-engine/e4e90fe744ffffc1010edb7d12c5693a0b9ce477/gopher-grule.png -------------------------------------------------------------------------------- /logger/Logger.go: -------------------------------------------------------------------------------- 1 | // Copyright hyperjumptech/grule-rule-engine Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package logger 16 | 17 | import ( 18 | "github.com/sirupsen/logrus" 19 | "go.uber.org/zap" 20 | ) 21 | 22 | // Level type 23 | type Level uint32 24 | 25 | const ( 26 | // PanicLevel level, highest level of severity. Logs and then calls panic with the 27 | // message passed to Debug, Info, ... 28 | PanicLevel Level = iota 29 | // FatalLevel level. Logs and then calls `logger.Exit(1)`. It will exit even if the 30 | // logging level is set to Panic. 31 | FatalLevel 32 | // ErrorLevel level. Logs. Used for errors that should definitely be noted. 33 | // Commonly used for hooks to send errors to an error tracking service. 34 | ErrorLevel 35 | // WarnLevel level. Non-critical entries that deserve eyes. 36 | WarnLevel 37 | // InfoLevel level. General operational entries about what's going on inside the 38 | // application. 39 | InfoLevel 40 | // DebugLevel level. Usually only enabled when debugging. Very verbose logging. 41 | DebugLevel 42 | // TraceLevel level. Designates finer-grained informational events than the Debug. 43 | TraceLevel 44 | ) 45 | 46 | type LogEntry struct { 47 | Logger 48 | Level Level 49 | } 50 | 51 | type Fields map[string]interface{} 52 | 53 | type Logger interface { 54 | Debug(args ...interface{}) 55 | Info(args ...interface{}) 56 | Warn(args ...interface{}) 57 | Error(args ...interface{}) 58 | Panic(args ...interface{}) 59 | Fatal(args ...interface{}) 60 | 61 | Debugf(template string, args ...interface{}) 62 | Infof(template string, args ...interface{}) 63 | Warnf(template string, args ...interface{}) 64 | Errorf(template string, args ...interface{}) 65 | Panicf(template string, args ...interface{}) 66 | Fatalf(template string, args ...interface{}) 67 | 68 | Trace(args ...interface{}) 69 | Tracef(format string, args ...interface{}) 70 | 71 | Print(args ...interface{}) 72 | Println(args ...interface{}) 73 | Printf(format string, args ...interface{}) 74 | 75 | WithFields(keyValues Fields) LogEntry 76 | } 77 | 78 | var ( 79 | Log LogEntry 80 | ) 81 | 82 | func init() { 83 | logger := logrus.New() 84 | logger.Level = logrus.InfoLevel 85 | 86 | Log = LogEntry{ 87 | Logger: NewLogrus(logger).WithFields(Fields{"lib": "grule-rule-engine"}), 88 | Level: DebugLevel, 89 | } 90 | } 91 | 92 | // SetLogger changes default logger on external 93 | func SetLogger(externalLog interface{}) { 94 | switch externalLog.(type) { 95 | case *zap.Logger: 96 | log, ok := externalLog.(*zap.Logger) 97 | if !ok { 98 | 99 | return 100 | } 101 | Log = NewZap(log) 102 | case *logrus.Logger: 103 | log, ok := externalLog.(*logrus.Logger) 104 | if !ok { 105 | 106 | return 107 | } 108 | Log = NewLogrus(log) 109 | default: 110 | 111 | return 112 | } 113 | } 114 | 115 | // SetLogLevel will set the logger log level 116 | func SetLogLevel(lvl Level) { 117 | Log.Level = lvl 118 | } 119 | -------------------------------------------------------------------------------- /model/TimeFormat_test.go: -------------------------------------------------------------------------------- 1 | // Copyright hyperjumptech/grule-rule-engine Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package model 16 | 17 | import ( 18 | "testing" 19 | "time" 20 | ) 21 | 22 | /* 23 | ANSIC = "Mon Jan _2 15:04:05 2006" 24 | UnixDate = "Mon Jan _2 15:04:05 MST 2006" 25 | RubyDate = "Mon Jan 02 15:04:05 -0700 2006" 26 | RFC822 = "02 Jan 06 15:04 MST" 27 | RFC822Z = "02 Jan 06 15:04 -0700" // RFC822 with numeric zone 28 | RFC850 = "Monday, 02-Jan-06 15:04:05 MST" 29 | RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST" 30 | RFC1123Z = "Mon, 02 Jan 2006 15:04:05 -0700" // RFC1123 with numeric zone 31 | RFC3339 = "2006-01-02T15:04:05Z07:00" 32 | RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00" 33 | Kitchen = "3:04PM" 34 | // Handy time stamps. 35 | Stamp = "Jan _2 15:04:05" 36 | StampMilli = "Jan _2 15:04:05.000" 37 | StampMicro = "Jan _2 15:04:05.000000" 38 | StampNano = "Jan _2 15:04:05.000000000" 39 | */ 40 | 41 | type TimeTestData struct { 42 | Layout string 43 | Date string 44 | Valid bool 45 | } 46 | 47 | func TestIsDateFormatValid(t *testing.T) { 48 | testData := []*TimeTestData{ 49 | {Layout: "notexist", Date: "02 Jan 06 15:04 MST", Valid: false}, 50 | {Layout: time.ANSIC, Date: "Mon Jan 2 15:04:05 2006", Valid: true}, 51 | {Layout: time.ANSIC, Date: "Mon Jan 02 15:04:05 2006", Valid: true}, 52 | {Layout: time.ANSIC, Date: "Mon Jan 22 15:04:05 2006", Valid: true}, 53 | {Layout: time.ANSIC, Date: "Mon Jan 22 15:04:05 06", Valid: false}, 54 | // todo add more format test here 55 | } 56 | for _, td := range testData { 57 | v := IsDateFormatValid(td.Layout, td.Date) 58 | if v != td.Valid { 59 | t.Logf("layout '%s' expect '%s' to %v but %v", td.Layout, td.Date, td.Valid, !td.Valid) 60 | t.Fail() 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /pkg/CloneTool.go: -------------------------------------------------------------------------------- 1 | // Copyright hyperjumptech/grule-rule-engine Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package pkg 16 | 17 | // CloneRecord contains information about all AST versions, instance, their cloned version and cloned instance. 18 | type CloneRecord struct { 19 | OriginAstID string 20 | CloneAstID string 21 | OriginInstance interface{} 22 | CloneInstance interface{} 23 | } 24 | 25 | // NewCloneTable create new instance of CloneTable 26 | func NewCloneTable() *CloneTable { 27 | 28 | return &CloneTable{Records: make(map[string]*CloneRecord)} 29 | } 30 | 31 | // CloneTable will stores all meta information about AST object being cloned under one KnowledgeBase. 32 | type CloneTable struct { 33 | Records map[string]*CloneRecord 34 | } 35 | 36 | // IsCloned will check if an AST object with identified astId has a clone. 37 | func (tab *CloneTable) IsCloned(astID string) bool { 38 | _, ok := tab.Records[astID] 39 | 40 | return ok 41 | } 42 | 43 | // MarkCloned will record that an Ast object are now been cloned, so all other cloned object should reference to the newly cloned Ast object 44 | func (tab *CloneTable) MarkCloned(originAst, cloneAst string, origin, clone interface{}) { 45 | tab.Records[originAst] = &CloneRecord{ 46 | OriginAstID: originAst, 47 | CloneAstID: cloneAst, 48 | OriginInstance: origin, 49 | CloneInstance: clone, 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /pkg/embeddedResource_test.go: -------------------------------------------------------------------------------- 1 | //go:build go1.16 2 | // +build go1.16 3 | 4 | // Copyright hyperjumptech/grule-rule-engine Authors 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | package pkg 19 | 20 | import ( 21 | "embed" 22 | "strings" 23 | "testing" 24 | ) 25 | 26 | //go:embed test 27 | var rules embed.FS 28 | 29 | func TestEmbeddedResourceBundle_Load(t *testing.T) { 30 | erb := NewEmbeddedResourceBundle(rules, ".", "/**/*.grl") 31 | resources := erb.MustLoad() 32 | if len(resources) != 6 { 33 | t.Errorf("Expected 6 but get %d", len(resources)) 34 | t.FailNow() 35 | } 36 | if !strings.HasSuffix(resources[0].String(), "GrlFile11.grl") { 37 | t.Errorf("Expect [0] to have suffix GrlFile11.grl. But %s", resources[0].String()) 38 | } 39 | if !strings.HasSuffix(resources[1].String(), "GrlFile12.grl") { 40 | t.Errorf("Expect [1] to have suffix GrlFile12.grl. But %s", resources[0].String()) 41 | } 42 | if !strings.HasSuffix(resources[2].String(), "GrlFile21.grl") { 43 | t.Errorf("Expect [2] to have suffix GrlFile11.grl. But %s", resources[0].String()) 44 | } 45 | if !strings.HasSuffix(resources[3].String(), "GrlFile22.grl") { 46 | t.Errorf("Expect [3] to have suffix GrlFile11.grl. But %s", resources[0].String()) 47 | } 48 | if !strings.HasSuffix(resources[4].String(), "GrlFile211.grl") { 49 | t.Errorf("Expect [4] to have suffix GrlFile11.grl. But %s", resources[0].String()) 50 | } 51 | if !strings.HasSuffix(resources[5].String(), "GrlFile212.grl") { 52 | t.Errorf("Expect [5] to have suffix GrlFile11.grl. But %s", resources[0].String()) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /pkg/errorReporter.go: -------------------------------------------------------------------------------- 1 | // Copyright hyperjumptech/grule-rule-engine Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package pkg 16 | 17 | import ( 18 | "fmt" 19 | "github.com/antlr/antlr4/runtime/Go/antlr" 20 | ) 21 | 22 | // GruleErrorReporter is an implementation of ErrorListener interface by antlr. The purpose is to capture errors during lexer tokenization and parsing. 23 | type GruleErrorReporter struct { 24 | *antlr.DefaultErrorListener // Embed default which ensures we fit the interface 25 | Errors []error 26 | } 27 | 28 | // AddError simply add an error into this reporter 29 | func (c *GruleErrorReporter) AddError(err error) { 30 | c.Errors = append(c.Errors, err) 31 | } 32 | 33 | // SyntaxError call back which will be called upon parsing error 34 | func (c *GruleErrorReporter) SyntaxError(recognizer antlr.Recognizer, offendingSymbol interface{}, line, column int, msg string, e antlr.RecognitionException) { 35 | c.Errors = append(c.Errors, fmt.Errorf("grl error on %d:%d %s", line, column, msg)) 36 | } 37 | 38 | // HasError check if this reporter has an error 39 | func (c *GruleErrorReporter) HasError() bool { 40 | 41 | return c.Errors != nil && len(c.Errors) > 0 42 | } 43 | 44 | // Error return an error text. This function is there for compatibility reason. 45 | func (c *GruleErrorReporter) Error() string { 46 | if c.HasError() { 47 | 48 | return fmt.Sprintf("got %d error(s) in grl the script", len(c.Errors)) 49 | } 50 | 51 | return fmt.Sprintf("no error in grl script") 52 | } 53 | -------------------------------------------------------------------------------- /pkg/gitresource.go: -------------------------------------------------------------------------------- 1 | //go:build go1.11 2 | // +build go1.11 3 | 4 | package pkg 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/go-git/go-billy/v5/memfs" 10 | "github.com/go-git/go-git/v5" 11 | "github.com/go-git/go-git/v5/plumbing" 12 | http2 "github.com/go-git/go-git/v5/plumbing/transport/http" 13 | "github.com/go-git/go-git/v5/storage/memory" 14 | ) 15 | 16 | // Load will load the file from your git repository 17 | func (bundle *GITResourceBundle) Load() ([]Resource, error) { 18 | fileSystem := memfs.New() 19 | CloneOpts := &git.CloneOptions{} 20 | if len(bundle.URL) == 0 { 21 | 22 | return nil, fmt.Errorf("GIT URL is not specified") 23 | } 24 | CloneOpts.URL = bundle.URL 25 | 26 | if len(bundle.RefName) == 0 { 27 | CloneOpts.ReferenceName = plumbing.ReferenceName("refs/heads/master") 28 | } else { 29 | CloneOpts.ReferenceName = plumbing.ReferenceName(bundle.RefName) 30 | } 31 | 32 | if len(bundle.Remote) == 0 { 33 | CloneOpts.RemoteName = "origin" 34 | } else { 35 | CloneOpts.RemoteName = bundle.Remote 36 | } 37 | 38 | if len(bundle.PathPattern) == 0 { 39 | return nil, fmt.Errorf("no path pattern specified") 40 | } 41 | 42 | if len(bundle.User) != 0 { 43 | CloneOpts.Auth = &http2.BasicAuth{ 44 | Username: bundle.User, 45 | Password: bundle.Password, 46 | } 47 | } 48 | 49 | _, err := git.Clone(memory.NewStorage(), fileSystem, CloneOpts) 50 | if err != nil { 51 | 52 | return nil, err 53 | } 54 | 55 | return bundle.loadPath(bundle.URL, "/", fileSystem) 56 | } 57 | -------------------------------------------------------------------------------- /pkg/gitresource_1.10.go: -------------------------------------------------------------------------------- 1 | //go:build !go1.11 2 | // +build !go1.11 3 | 4 | package pkg 5 | 6 | import ( 7 | "fmt" 8 | ) 9 | 10 | // Load will load the file from your git repository 11 | func (bundle *GITResourceBundle) Load() ([]Resource, error) { 12 | 13 | return nil, fmt.Errorf("GIT resources are not supported with Go 1.10 or below") 14 | } 15 | -------------------------------------------------------------------------------- /pkg/test/subfold1/GrlFile11.grl: -------------------------------------------------------------------------------- 1 | rule GrlFile11 "This is dummy rule" { 2 | when 3 | true 4 | then 5 | Log("Nice"); 6 | Complete(); 7 | } -------------------------------------------------------------------------------- /pkg/test/subfold1/GrlFile12.grl: -------------------------------------------------------------------------------- 1 | rule GrlFile12 "This is dummy rule" { 2 | when 3 | true 4 | then 5 | Log("Nice"); 6 | Complete(); 7 | } -------------------------------------------------------------------------------- /pkg/test/subfold2/GrlFile21.grl: -------------------------------------------------------------------------------- 1 | rule GrlFile21 "This is dummy rule" { 2 | when 3 | true 4 | then 5 | Log("Nice"); 6 | Complete(); 7 | } -------------------------------------------------------------------------------- /pkg/test/subfold2/GrlFile22.grl: -------------------------------------------------------------------------------- 1 | rule GrlFile22 "This is dummy rule" { 2 | when 3 | true 4 | then 5 | Log("Nice"); 6 | Complete(); 7 | } -------------------------------------------------------------------------------- /pkg/test/subfold2/subfold21/GrlFile211.grl: -------------------------------------------------------------------------------- 1 | rule GrlFile211 "This is dummy rule" { 2 | when 3 | true 4 | then 5 | Log("Nice"); 6 | Complete(); 7 | } -------------------------------------------------------------------------------- /pkg/test/subfold2/subfold21/GrlFile212.grl: -------------------------------------------------------------------------------- 1 | rule GrlFile212 "This is dummy rule" { 2 | when 3 | true 4 | then 5 | Log("Nice"); 6 | Complete(); 7 | } --------------------------------------------------------------------------------