From d606e67ce489f2f739c48d819fe7c120c6ef6943 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Wed, 18 Feb 2015 15:28:04 -0800 Subject: [PATCH 1/2] Add custom constraint using function and resolve types in scope --- lib/constraint.js | 17 +++++++ lib/constraintMatcher.js | 3 ++ lib/rule.js | 107 +++++++++++++++++++++++---------------- test/rules.test.js | 45 ++++++++++++++++ 4 files changed, 128 insertions(+), 44 deletions(-) diff --git a/lib/constraint.js b/lib/constraint.js index 3bf3452..48da9c1 100644 --- a/lib/constraint.js +++ b/lib/constraint.js @@ -236,5 +236,22 @@ Constraint.extend({ } }).as(exports, "FromConstraint"); +Constraint.extend({ + instance: { + constructor: function(func, options) { + this.type = "custom"; + this.fn = func; + this.options = options; + }, + + equal: function(constraint) { + return instanceOf(constraint, this._static) && this.fn === constraint.constraint; + }, + + "assert": function(fact, fh) { + return this.fn(fact, fh); + } + } +}).as(exports, "CustomConstraint"); diff --git a/lib/constraintMatcher.js b/lib/constraintMatcher.js index a4f975c..c2aa60e 100644 --- a/lib/constraintMatcher.js +++ b/lib/constraintMatcher.js @@ -458,6 +458,9 @@ exports.getSourceMatcher = function (rule, options, equality) { }; exports.toConstraints = function (constraint, options) { + if(typeof constraint === 'function') { + return [new atoms.CustomConstraint(constraint, options)]; + } //constraint.split("&&") return lang.toConstraints(constraint, options); }; diff --git a/lib/rule.js b/lib/rule.js index fe44bc6..eae2042 100644 --- a/lib/rule.js +++ b/lib/rule.js @@ -16,6 +16,14 @@ var extd = require("./extended"), FromExistsPattern = pattern.FromExistsPattern, CompositePattern = pattern.CompositePattern; +var parseConstraint = function(constraint) { + if (typeof constraint === 'function') { + // No parsing is needed for constraint functions + return constraint; + } + return parser.parseConstraint(constraint); +}; + var parseExtra = extd .switcher() .isUndefinedOrNull(function () { @@ -66,55 +74,63 @@ var normailizeConstraint = extd }) .switcher(); - -var getParamTypeSwitch = extd +var getParamType = function getParamType(type, scope) { + scope = scope || {}; + var getParamTypeSwitch = extd .switcher() - .isEq("string", function () { - return String; + .isEq("string", function() { + return String; }) - .isEq("date", function () { - return Date; + .isEq("date", function() { + return Date; }) - .isEq("array", function () { - return Array; + .isEq("array", function() { + return Array; }) - .isEq("boolean", function () { - return Boolean; + .isEq("boolean", function() { + return Boolean; }) - .isEq("regexp", function () { - return RegExp; + .isEq("regexp", function() { + return RegExp; }) - .isEq("number", function () { - return Number; + .isEq("number", function() { + return Number; }) - .isEq("object", function () { - return Object; + .isEq("object", function() { + return Object; }) - .isEq("hash", function () { - return Object; + .isEq("hash", function() { + return Object; }) - .def(function (param) { - throw new TypeError("invalid param type " + param); + .def(function(param) { + throw new TypeError("invalid param type " + param); }) .switcher(); - -var getParamType = extd + var _getParamType = extd .switcher() - .isString(function (param) { + .isString(function(param) { + var t = scope[param]; + if(!t) { return getParamTypeSwitch(param.toLowerCase()); + } else { + return t; + } }) - .isFunction(function (func) { - return func; + .isFunction(function(func) { + return func; }) - .deepEqual([], function () { - return Array; + .deepEqual([], function() { + return Array; }) - .def(function (param) { - throw new Error("invalid param type " + param); + .def(function(param) { + throw new Error("invalid param type " + param); }) .switcher(); + return _getParamType(type); +}; + var parsePattern = extd .switcher() .containsAt("or", 0, function (condition) { @@ -130,20 +146,20 @@ var parsePattern = extd if (condition[4] && condition[4].from) { return [ new FromNotPattern( - getParamType(condition[0]), + getParamType(condition[0], condition.scope), condition[1] || "m", - parser.parseConstraint(condition[2] || "true"), + parseConstraint(condition[2] || "true"), condition[3] || {}, - parser.parseConstraint(condition[4].from), + parseConstraint(condition[4].from), {scope: condition.scope, pattern: condition[2]} ) ]; } else { return [ new NotPattern( - getParamType(condition[0]), + getParamType(condition[0], condition.scope), condition[1] || "m", - parser.parseConstraint(condition[2] || "true"), + parseConstraint(condition[2] || "true"), condition[3] || {}, {scope: condition.scope, pattern: condition[2]} ) @@ -156,20 +172,20 @@ var parsePattern = extd if (condition[4] && condition[4].from) { return [ new FromExistsPattern( - getParamType(condition[0]), + getParamType(condition[0], condition.scope), condition[1] || "m", - parser.parseConstraint(condition[2] || "true"), + parseConstraint(condition[2] || "true"), condition[3] || {}, - parser.parseConstraint(condition[4].from), + parseConstraint(condition[4].from), {scope: condition.scope, pattern: condition[2]} ) ]; } else { return [ new ExistsPattern( - getParamType(condition[0]), + getParamType(condition[0], condition.scope), condition[1] || "m", - parser.parseConstraint(condition[2] || "true"), + parseConstraint(condition[2] || "true"), condition[3] || {}, {scope: condition.scope, pattern: condition[2]} ) @@ -177,24 +193,27 @@ var parsePattern = extd } }) .def(function (condition) { + if (typeof condition === 'function') { + return [condition]; + } condition = normailizeConstraint(condition); if (condition[4] && condition[4].from) { return [ new FromPattern( - getParamType(condition[0]), + getParamType(condition[0], condition.scope), condition[1] || "m", - parser.parseConstraint(condition[2] || "true"), + parseConstraint(condition[2] || "true"), condition[3] || {}, - parser.parseConstraint(condition[4].from), + parseConstraint(condition[4].from), {scope: condition.scope, pattern: condition[2]} ) ]; } else { return [ new ObjectPattern( - getParamType(condition[0]), + getParamType(condition[0], condition.scope), condition[1] || "m", - parser.parseConstraint(condition[2] || "true"), + parseConstraint(condition[2] || "true"), condition[3] || {}, {scope: condition.scope, pattern: condition[2]} ) diff --git a/test/rules.test.js b/test/rules.test.js index c2be248..acdb6d8 100644 --- a/test/rules.test.js +++ b/test/rules.test.js @@ -428,6 +428,51 @@ it.describe("Rule", function (it) { }); }); + it.describe("custom function as constraints", function (it) { + + var MyConstraint = function(fact) { + return true; + }; + + it.should("create for String function with custom constraint", function() { + var rule = rules.createRule("My Rule", [String, "s", MyConstraint], cb); + assert.isNotNull(rule); + assert.lengthOf(rule, 1); + rule = rule[0]; + assert.equal(rule.name, "My Rule"); + assert.isNotNull(rule.pattern); + var pattern = rule.pattern; + assert.equal(pattern.alias, "s"); + assert.lengthOf(pattern.constraints, 2); + assert.instanceOf(pattern.constraints[0], constraints.ObjectConstraint); + assert.equal(pattern.constraints[0].constraint, String); + assert.instanceOf(pattern.constraints[1], constraints.CustomConstraint); + assert.strictEqual(rule.cb, cb); + }); + }); + + it.describe("custom type via scope", function (it) { + + var MyType = function(name) { + this.name = name; + }; + + it.should("create for String function with custom constraint", function() { + var rule = rules.createRule("My Rule", {scope: {MyType: MyType}}, ['MyType', "s", "s.name === 'X'"], cb); + assert.isNotNull(rule); + assert.lengthOf(rule, 1); + rule = rule[0]; + assert.equal(rule.name, "My Rule"); + assert.isNotNull(rule.pattern); + var pattern = rule.pattern; + assert.equal(pattern.alias, "s"); + assert.lengthOf(pattern.constraints, 2); + assert.instanceOf(pattern.constraints[0], constraints.ObjectConstraint); + assert.equal(pattern.constraints[0].constraint, MyType); + assert.strictEqual(rule.cb, cb); + }); + }); + it.should("create a composite rule", function () { var rule = rules.createRule("My Rule", [ ["string", "s"], From a5103031eb2b2d65cfc3fd186d596df57dcb9cd0 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Mon, 23 Feb 2015 14:13:37 -0800 Subject: [PATCH 2/2] Enable session.halt() --- lib/flow.js | 16 +++++++++++++++- package.json | 2 +- test/flow.test.js | 2 ++ test/flow/index.js | 1 + test/flow/match.halt.js | 36 ++++++++++++++++++++++++++++++++++++ 5 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 test/flow.test.js create mode 100644 test/flow/match.halt.js diff --git a/lib/flow.js b/lib/flow.js index 9c1ed49..dda92bf 100644 --- a/lib/flow.js +++ b/lib/flow.js @@ -27,7 +27,8 @@ module.exports = declare(EventEmitter, { this.agenda.on("fire", bind(this, "emit", "fire")); this.agenda.on("focused", bind(this, "emit", "focused")); this.rootNode = new nodes.RootNode(this.workingMemory, this.agenda); - extd.bindAll(this, "halt", "assert", "retract", "modify", "focus", "emit", "getFacts"); + extd.bindAll(this, "halt", "assert", "retract", "modify", "focus", + "emit", "getFacts", "getFact"); }, getFacts: function (Type) { @@ -40,6 +41,16 @@ module.exports = declare(EventEmitter, { return ret; }, + getFact: function (Type) { + var ret; + if (Type) { + ret = this.workingMemory.getFactsByType(Type); + } else { + ret = this.workingMemory.getFacts(); + } + return ret && ret[0]; + }, + focus: function (focused) { this.agenda.setFocus(focused); return this; @@ -47,9 +58,12 @@ module.exports = declare(EventEmitter, { halt: function () { var strategy = this.executionStrategy; + /* if (strategy.matchUntilHalt) { strategy.halt(); } + */ + strategy.halt(); return this; }, diff --git a/package.json b/package.json index 3c6ea6d..da8e023 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "author": "Doug Martin (http://c2fo.com)", "main": "index.js", "scripts": { - "test": "it -r dot", + "test": "it -r spec", "create-doc": "rm -rf docs/* && coddoc -d ./lib -f multi-html" }, "directories": { diff --git a/test/flow.test.js b/test/flow.test.js new file mode 100644 index 0000000..0c35672 --- /dev/null +++ b/test/flow.test.js @@ -0,0 +1,2 @@ +"use strict"; +require('./flow/index'); \ No newline at end of file diff --git a/test/flow/index.js b/test/flow/index.js index 12cde2b..b196e30 100644 --- a/test/flow/index.js +++ b/test/flow/index.js @@ -5,6 +5,7 @@ require("./events.test"); require("./exists.test"); require("./from.test"); require("./matchUntil.halt"); +require("./match.halt"); require("./not.test"); require("./or.test"); require("./rule.test"); diff --git a/test/flow/match.halt.js b/test/flow/match.halt.js new file mode 100644 index 0000000..e4d92dc --- /dev/null +++ b/test/flow/match.halt.js @@ -0,0 +1,36 @@ +"use strict"; +var it = require("it"), + nools = require("../../"), + assert = require("assert"); + +it.describe("#matchHalt", function (it) { + + function Count(c) { + this.count = c; + } + + var session, flow = nools.flow("Match with halt Flow", function (flow) { + + flow.rule("Stop", [Count, "c", "c.count == 6"], function () { + this.halt(); + }); + + flow.rule("Inc", [Count, "c"], function (facts) { + facts.c.count++; + this.modify(facts.c); + }); + + }); + + it.beforeEach(function () { + session = flow.getSession(new Count(0)); + }); + + it.should("stop match with halt", function () { + return session.match().then(function (err) { + assert.isUndefinedOrNull(err); + assert.equal(session.getFacts(Count)[0].count, 6); + }); + + }); +}); \ No newline at end of file