Yasper is available from http://www.yasper.org/. I recommend it to everyone interested in process modelling.
The image below shows an example process I have tested with Yasper (click to enlarge):

-->
using System;
using System.Collections.Generic;
using NGinn.RippleBoo;
class TestMe
{
private RuleRepository _repos;
public TestMe()
{
_repos = new RuleRepository();
_repos.BaseDirectory = "c:\\rules";
_repos.ImportNamespaces.Add("System");
}
public void RunSomeRules()
{
Dictionary<string, object> variables = new Dictionary<string,object>();
variables["Email"] = "my@email.com";
variables["Timestamp"] = DateTime.Now;
Dictionary<string, object> context = new Dictionary<string,object>();
context["Output"] = Console.Output;
_repos.EvaluateRules("some_rules.boo", variables, context);
}
}
ruleset "SomeRules":
rule "R1":
when Variables.Email.EndsWith("mydomain.com")
action:
Context.Output.WriteLine("Email from my domain")
rule "SPAM":
label "Spam? - move to spam"
when IS_SPAM()
except_rule "Friendly_spam"
action:
MOVE_TO "Spam"
else_rule "WORK"
What we have here:
rule "VERY_IMPORTANT":
label "Important? - mark high priority"
when __msg.From == "customer_care@mybank.com"
side_effect:
__msg.Priority = "High"
#variable alias
__msg = Variables.Message
#helper function - check if message is spam
IS_SPAM = def() :
return __msg.Subject.IndexOf("[--spam--]") >= 0
#helper - move message to specified folder
MOVE_TO = def(folder):
Context.MessageDb.MoveMessage(__msg, folder)
ruleset "Email_default_rules":
rule "SPAM":
label "Spam? - move to spam"
when IS_SPAM()
except_rule "Friendly_spam"
action:
MOVE_TO "Spam"
else_rule "WORK"
rule "Friendly_spam":
label "Interesting subject? - read!"
when __msg.Subject.IndexOf("enlarge") >= 0
action:
MOVE_TO "Useful_spam"
rule "WORK":
label "Work? - move to WORK"
when __msg.From.EndsWith("mycompany.com")
action:
MOVE_TO "Work"
else_rule "VERY_IMPORTANT"
rule "VERY_IMPORTANT":
label "Important? - mark high priority"
when __msg.From == "customer_care@mybank.com"
side_effect:
__msg.Priority = "High"
Other features
goto_ruleset "another ruleset"
goto_file "another_rules.boo"
Today I have added a first working version of a rules engine to NGinn. The source code is in 'NGinn.RippleBoo' folder.
The RippleBoo engine implements algorighm called 'Ripple Down Rules' - basically it is a binary decision tree. Each rule has simple "if
When rule condition evals to true, its action is executed and next rule to evaluate will be the 'positive' successor rule. When condition evals to false, action will not be executed and next rule to evaluate will be the 'negative' successor. In effect, we get a binary decision tree, but we don't have to worry about its completeness because it is guaranteed that at least one rule will fire no matter what are the conditions (because the first rule is always true).
Rules were implemented in Boo language using the RhinoDSL library from the Rhino-tools package. Rhino DSL is a library for building DSLs (domain specific languages) in Boo. Here's a link to its author's blog: http://ayende.com/Blog/archive/2007/12/03/Implementing-a-DSL.aspx. The guy has done a great work and many interesting examples of DSLs can be found there.
Below is an example ruleset in my "rule definition language". BTW, it's also a valid Boo script:
Ruleset "MyRules"
rule "R1", "R2", null, V.Counter < 9:
log.Info("AAA");
rule "R2", "R3", null, V.Counter < 8:
log.Info ("R2")
rule "R3", "R4", null, V.Counter < 7:
log.Info ("R3")
rule "R4", null, "R5", V.Counter == 1:
log.Info ("R4")
rule "R5", "R6", null, 1 == 1:
log.Info ("R5: Counter is ${V.Counter}")
rule "R6", "X", null, 2 % 2 == 0:
log.Info ("Rule six: {0}", date.Now)
rule "X", null, null, date.Today > date.Parse('2008-10-11'):
log.Warn("The X Rule!!!")
So this entry:
rule "R6", "X", null, 2 % 2 == 0:means 'define rule R6 that will fire if expression "2 % 2 == 0" evals to true. If it is true, execute action that writes current date to log file. Next rule to evaluate will be "X", or none if the rule doesn't fire'
log.Info ("Rule six: {0}", date.Now)
Currently rules engine is a completely standalone project, but I plan to integrate it into NGinn process engine.It will be used in many places, certainly as a part of process logic, but also for message routing and preprocessing.
The main problem is that Boo is not yet used in NGinn, except for the RippleBoo project. Currently Script.Net language is the main script environment for NGinn processes and I wouldn't like to mix these two languages. So probably only one is here to stay, and chances are it will be Boo. Script.Net is more elastic and easier to use, but Boo is more mature, better tested and documented. Main issue with Boo is that it's a compiled language, so it will require more effort to integrate it with NGinn engine which is very 'dynamic' in nature.
Today I'd like to describe some examples of processes that are known to be working in current version of nginn. The focus is on control structures, not the actual task functionality (which is very incomplete as for now). I have selected rather complex and not very obvious examples because the basic ones such as parallelism (AND-split), sequences and decisions (XOR-splits), well, should just work or there wouln't be much to talk about.
Deferred choice with a timeout
This is a very common pattern - deferred choice with a timeout. It can be used for adding some time limits to manual or other tasks. When token is placed in 'start', both tasks are enabled - 'eval_candidate' and 'timeout'. When 'eval_candidate' completes first, timeout is cancelled. When timeout completes first (deadline is reached), eval_candidate is cancelled.
Deferred choice - complex situation
This proces is an example of more complex implicit choice. There are two places with implicit choice (p1 and p2), each having two possible tasks. However, they share the t2 task. Functionality here is that system enables all tasks: t1, t2 and t3 after tokens arrive at p1 and p2. This construction ensures that either t1 and t3 can complete, or t2 can complete. When t2 completes, t1 and t3 will be cancelled. When t1 completes, t2 will be cancelled and t3 will stay enabled. When t3 completes, t2 will be cancelled and t1 will stay enabled.
OR-join with 'escaping' tokens
This is rather a complex example, so I was very happy to see it working. What we have here. First of all, there's t1 task with an OR-split. The split can choose V1 or V2 path, or both of them. The eval_candidate4 task is a corresponding OR-join.
The catch here is that we have a deferred choice in place p1, and eval_candidate3 task can 'steal' token from p1, effectively moving it out of OR-join's scope. Situations where either V1 or V2 path is chosen are not very interesting. However, if both V1 and V2 are chosen, the eval_candidate4 OR-join should wait for two tokens to arrive before eval_candidate4 can be enabled. But if eval_candidate3 steals the token, eval_candidate4 should 'change its mind' and wait for one token only. Why? Because no more tokens can arrive in such situation, so all possible OR-join's input paths don't contain more tokens.
OR-join with tokens 'stolen' by a cancellation (cancel sets)
Here the situation is similar to the previous case - we have an OR-split and OR-join and two paths V1 and V2. However, there's this little red arrow from t3 to p2. This arrow is a cancellation (cancel set), meaning that when t3 completes all tokens should be removed from p2 (effectively cancelling the eval_candidate2 task).
Effect is that when both V1 and V2 are chosen, you need to complete eval_candidate and eval_candidate2 before eval_candidate4 will be enabled. Alternatively, you can complete t3, then you will not have to do eval_candidate2. After you complete eval_candidate2, completing t3 has no side-effects.