diff --git a/lib/context.js b/lib/context.js index cc876aa..28b8ac9 100644 --- a/lib/context.js +++ b/lib/context.js @@ -5,6 +5,8 @@ var extd = require("./extended"), indexOf = extd.indexOf, pPush = Array.prototype.push; +var Match = require('./match.js'); + function createContextHash(paths, hashCode) { var ret = "", i = -1, @@ -16,77 +18,28 @@ function createContextHash(paths, hashCode) { return ret; } -function merge(h1, h2, aliases) { - var i = -1, l = aliases.length, alias; - while (++i < l) { - alias = aliases[i]; - h1[alias] = h2[alias]; - } -} - -function unionRecency(arr, arr1, arr2) { - pPush.apply(arr, arr1); - var i = -1, l = arr2.length, val, j = arr.length; +function createPathstHash(paths, hashCode) { + var ret = "", + i = -1, + l = paths.length; while (++i < l) { - val = arr2[i]; - if (indexOf(arr, val) === -1) { - arr[j++] = val; - } + ret += paths[i].id + ":"; } + return ret; } -var Match = declare({ - instance: { - isMatch: true, - hashCode: "", - facts: null, - factIds: null, - factHash: null, - recency: null, - aliases: null, - constructor: function () { - this.facts = []; - this.factIds = []; - this.factHash = {}; - this.recency = []; - this.aliases = []; - }, - - addFact: function (assertable) { - pPush.call(this.facts, assertable); - pPush.call(this.recency, assertable.recency); - pPush.call(this.factIds, assertable.id); - this.hashCode = this.factIds.join(":"); - return this; - }, - - merge: function (mr) { - var ret = new Match(); - ret.isMatch = mr.isMatch; - pPush.apply(ret.facts, this.facts); - pPush.apply(ret.facts, mr.facts); - pPush.apply(ret.aliases, this.aliases); - pPush.apply(ret.aliases, mr.aliases); - ret.hashCode = this.hashCode + ":" + mr.hashCode; - merge(ret.factHash, this.factHash, this.aliases); - merge(ret.factHash, mr.factHash, mr.aliases); - unionRecency(ret.recency, this.recency, mr.recency); - return ret; - } - } -}); var Context = declare({ instance: { - match: null, - factHash: null, - aliases: null, - fact: null, - hashCode: null, - paths: null, - pathsHash: null, + match: null, + factHash: null, + aliases: null, + fact: null, + hashCode: null, + paths: null, + pathsHash: null, constructor: function (fact, paths, mr) { this.fact = fact; diff --git a/lib/extended.js b/lib/extended.js index 3468567..9e800d9 100644 --- a/lib/extended.js +++ b/lib/extended.js @@ -1,4 +1,5 @@ var arr = require("array-extended"), + is = require("is-extended"), unique = arr.unique, indexOf = arr.indexOf, map = arr.map, @@ -126,6 +127,27 @@ function diffHash(h1, h2) { function union(arr1, arr2) { return unique(arr1.concat(arr2)); } + +function findIndex(coll, item) { + var target = -1; + if( 'function' === typeof item ) { + coll.some(function(e, i) { + if(item(e,i)) { + target = i; + return true; + }; + }); + } + else { + coll.some(function(e, i) { + if(is.deepEqual(e, item)) { + target = i; + return true; + }; + }); + } + return target; +} module.exports = require("extended")() .register(require("date-extended")) @@ -145,5 +167,6 @@ module.exports = require("extended")() .register("HashTable", require("ht")) .register("declare", require("declare.js")) .register(require("leafy")) + .register('findIndex', findIndex) .register("LinkedList", require("./linkedList")); diff --git a/lib/match.js b/lib/match.js new file mode 100644 index 0000000..c819cbb --- /dev/null +++ b/lib/match.js @@ -0,0 +1,73 @@ +"use strict"; +var extd = require("./extended"), + isBoolean = extd.isBoolean, + declare = extd.declare, + indexOf = extd.indexOf, + pPush = Array.prototype.push; + + +function merge(h1, h2, aliases) { + var i = -1, l = aliases.length, alias; + while (++i < l) { + alias = aliases[i]; + h1[alias] = h2[alias]; + } +} + +function unionRecency(arr, arr1, arr2) { + pPush.apply(arr, arr1); + var i = -1, l = arr2.length, val, j = arr.length; + while (++i < l) { + val = arr2[i]; + if (indexOf(arr, val) === -1) { + arr[j++] = val; + } + } +} + +var Match = declare({ + instance: { + + isMatch: true, + hashCode: "", + facts: null, + factIds: null, + factHash: null, + recency: null, + aliases: null, + + constructor: function () { + this.facts = []; + this.factIds = []; + this.factHash = {}; + this.recency = []; + this.aliases = []; + }, + + addFact: function (assertable) { + pPush.call(this.facts, assertable); + pPush.call(this.recency, assertable.recency); + pPush.call(this.factIds, assertable.id); + this.hashCode = this.factIds.join(":"); + return this; + }, + merge: function (mr) { + var ret = new Match(); + ret.isMatch = mr.isMatch; + pPush.apply(ret.facts, this.facts); + pPush.apply(ret.facts, mr.facts); + pPush.apply(ret.factIds, this.factIds); + pPush.apply(ret.factIds, mr.factIds); + pPush.apply(ret.aliases, this.aliases); + pPush.apply(ret.aliases, mr.aliases); + ret.hashCode = this.hashCode + ":" + mr.hashCode; + merge(ret.factHash, this.factHash, this.aliases); + merge(ret.factHash, mr.factHash, mr.aliases); + unionRecency(ret.recency, this.recency, mr.recency); + return ret; + } + } +}).as(module); + + + diff --git a/lib/parser/nools/nool.parser.js b/lib/parser/nools/nool.parser.js index 71d4012..3b6b571 100644 --- a/lib/parser/nools/nool.parser.js +++ b/lib/parser/nools/nool.parser.js @@ -1,6 +1,6 @@ "use strict"; -var tokens = require("./tokens.js"), +var tokens = require("./tokens.js").topLevelTokens, extd = require("../../extended"), keys = extd.hash.keys, utils = require("./util.js"); diff --git a/lib/parser/nools/tokens.js b/lib/parser/nools/tokens.js index 3ac7080..2ead85f 100644 --- a/lib/parser/nools/tokens.js +++ b/lib/parser/nools/tokens.js @@ -5,7 +5,7 @@ var utils = require("./util.js"), extd = require("../../extended"), filter = extd.filter, indexOf = extd.indexOf, - predicates = ["not", "or", "exists"], + predicates = ["not", "or", "exists"], predicateRegExp = new RegExp("^(" + predicates.join("|") + ") *\\((.*)\\)$", "m"), predicateBeginExp = new RegExp(" *(" + predicates.join("|") + ") *\\(", "g"); @@ -38,8 +38,82 @@ var splitRuleLineByPredicateExpressions = function (ruleLine) { return ret.join(";"); }; -var ruleTokens = { +/** +*/ +var ruleRegExp = /^(\$?\w+)\s*:\s*(\w+)(.*)/; +var collectRuleRegExp = /^(\$?\w+)\s*:\s*(\w+)\s*(.*)from\s*collect\s*\((.*)\)/; +var hashRegExp = /(\{ *(?:["']?\$?\w+["']?\s*:\s*["']?\$?\w+["']? *(?:, *["']?\$?\w+["']?\s*:\s*["']?\$?\w+["']?)*)+ *\})/; +var fromRegExp = /(\bfrom\s+.*)/; +var parseRules = function (str) { + var rules = []; + var ruleLines = str.split(";"), l = ruleLines.length, ruleLine, alias, constraints, parts, collectRule; + for (var i = 0; i < l && (ruleLine = ruleLines[i].replace(/^\s*|\s*$/g, "").replace(/\n/g, "")); i++) { + if (!isWhiteSpace(ruleLine)) { + var rule = []; + if (predicateRegExp.test(ruleLine)) { + var m = ruleLine.match(predicateRegExp); + var pred = m[1].replace(/^\s*|\s*$/g, ""); + rule.push(pred); + ruleLine = m[2].replace(/^\s*|\s*$/g, ""); + if (pred === "or") { + rule = rule.concat(parseRules(splitRuleLineByPredicateExpressions(ruleLine))); + rules.push(rule); + continue; + } + } + else if( collectRuleRegExp.test(ruleLine) ) { + parts = ruleLine.match(collectRuleRegExp); + if(parts && parts.length ) { + rule.push('collect'); + rule.push(parts[2], parts[1]); + constraints = parts[3].trim(); + rule.push( (constraints && !isWhiteSpace(constraints)) ? constraints : undefined ); + collectRule = parseRules(parts[4]); + rule.push(collectRule[0]); + } + rules.push(rule); + continue; + } + parts = ruleLine.match(ruleRegExp); + if (parts && parts.length) { + rule.push(parts[2], parts[1]); + constraints = parts[3].replace(/^\s*|\s*$/g, ""); + var hashParts = constraints.match(hashRegExp), from = null, fromMatch; + if (hashParts) { + var hash = hashParts[1], constraint = constraints.replace(hash, ""); + if (fromRegExp.test(constraint)) { + fromMatch = constraint.match(fromRegExp); + from = fromMatch[0]; + constraint = constraint.replace(fromMatch[0], ""); + } + if (constraint) { + rule.push(constraint.replace(/^\s*|\s*$/g, "")); + } + if (hash) { + rule.push(eval("(" + hash.replace(/(\$?\w+)\s*:\s*(\$?\w+)/g, '"$1" : "$2"') + ")")); + } + } else if (constraints && !isWhiteSpace(constraints)) { + if (fromRegExp.test(constraints)) { + fromMatch = constraints.match(fromRegExp); + from = fromMatch[0]; + constraints = constraints.replace(fromMatch[0], ""); + } + rule.push(constraints); + } + if (from) { + rule.push(from); + } + rules.push(rule); + } else { + throw new Error("Invalid constraint " + ruleLine); + } + } + } + return rules; +}; +exports.parseRules = parseRules; +var ruleTokens = { salience: (function () { var salienceRegexp = /^(salience|priority)\s*:\s*(-?\d+)\s*[,;]?/; return function (src, context) { @@ -93,7 +167,7 @@ var ruleTokens = { } }; })(), - + "agenda-group": function () { return this.agendaGroup.apply(this, arguments); }, @@ -109,66 +183,6 @@ var ruleTokens = { when: (function () { /*jshint evil:true*/ - var ruleRegExp = /^(\$?\w+) *: *(\w+)(.*)/; - - var constraintRegExp = /(\{ *(?:["']?\$?\w+["']?\s*:\s*["']?\$?\w+["']? *(?:, *["']?\$?\w+["']?\s*:\s*["']?\$?\w+["']?)*)+ *\})/; - var fromRegExp = /(\bfrom\s+.*)/; - var parseRules = function (str) { - var rules = []; - var ruleLines = str.split(";"), l = ruleLines.length, ruleLine; - for (var i = 0; i < l && (ruleLine = ruleLines[i].replace(/^\s*|\s*$/g, "").replace(/\n/g, "")); i++) { - if (!isWhiteSpace(ruleLine)) { - var rule = []; - if (predicateRegExp.test(ruleLine)) { - var m = ruleLine.match(predicateRegExp); - var pred = m[1].replace(/^\s*|\s*$/g, ""); - rule.push(pred); - ruleLine = m[2].replace(/^\s*|\s*$/g, ""); - if (pred === "or") { - rule = rule.concat(parseRules(splitRuleLineByPredicateExpressions(ruleLine))); - rules.push(rule); - continue; - } - - } - var parts = ruleLine.match(ruleRegExp); - if (parts && parts.length) { - rule.push(parts[2], parts[1]); - var constraints = parts[3].replace(/^\s*|\s*$/g, ""); - var hashParts = constraints.match(constraintRegExp), from = null, fromMatch; - if (hashParts) { - var hash = hashParts[1], constraint = constraints.replace(hash, ""); - if (fromRegExp.test(constraint)) { - fromMatch = constraint.match(fromRegExp); - from = fromMatch[0]; - constraint = constraint.replace(fromMatch[0], ""); - } - if (constraint) { - rule.push(constraint.replace(/^\s*|\s*$/g, "")); - } - if (hash) { - rule.push(eval("(" + hash.replace(/(\$?\w+)\s*:\s*(\$?\w+)/g, '"$1" : "$2"') + ")")); - } - } else if (constraints && !isWhiteSpace(constraints)) { - if (fromRegExp.test(constraints)) { - fromMatch = constraints.match(fromRegExp); - from = fromMatch[0]; - constraints = constraints.replace(fromMatch[0], ""); - } - rule.push(constraints); - } - if (from) { - rule.push(from); - } - rules.push(rule); - } else { - throw new Error("Invalid constraint " + ruleLine); - } - } - } - return rules; - }; - return function (orig, context) { var src = orig.replace(/^when\s*/, "").replace(/^\s*|\s*$/g, ""); if (utils.findNextToken(src) === "{") { @@ -324,7 +338,7 @@ var topLevelTokens = { } }, - "rule": function (orig, context, parse) { + "rule": function (orig, context, parse) { var src = orig.replace(/^rule\s*/, ""); var name = src.match(/^([a-zA-Z_$][0-9a-zA-Z_$]*|"[^"]*"|'[^']*')/); if (name) { @@ -343,8 +357,7 @@ var topLevelTokens = { } else { throw new Error("missing name"); } - } }; -module.exports = topLevelTokens; +exports.topLevelTokens = topLevelTokens; diff --git a/lib/rule.js b/lib/rule.js index 75a73d8..e69fe8f 100644 --- a/lib/rule.js +++ b/lib/rule.js @@ -130,7 +130,17 @@ var getParamType = function getParamType(type, scope) { return _getParamType(type); }; +// +function mergePatterns(patterns, merged, setAliases) { + var flattened = extd(patterns).flatten().value(); + flattened.forEach(function (thePattern) { + setAliases.push(thePattern.alias); + merged.push([thePattern]); + }); + return merged; +} +// var parsePattern = extd .switcher() .containsAt("or", 0, function (condition) { @@ -198,16 +208,16 @@ var parsePattern = extd } condition = normailizeConstraint(condition); if (condition[4] && condition[4].from) { - return [ - new FromPattern( - getParamType(condition[0], condition.scope), - condition[1] || "m", - parseConstraint(condition[2] || "true"), - condition[3] || {}, - parseConstraint(condition[4].from), - {scope: condition.scope, pattern: condition[2]} - ) - ]; + return [ + new FromPattern( + getParamType(condition[0], condition.scope), + condition[1] || "m", + parseConstraint(condition[2] || "true"), + condition[3] || {}, + parseConstraint(condition[4].from), + {scope: condition.scope, pattern: condition[2]} + ) + ]; } else { return [ new ObjectPattern( @@ -218,7 +228,7 @@ var parsePattern = extd {scope: condition.scope, pattern: condition[2]} ) ]; - } + } }).switcher(); var Rule = declare({ @@ -227,6 +237,7 @@ var Rule = declare({ this.name = name; this.pattern = pattern; this.cb = cb; + this.noLoop = options.noLoop; if (options.agendaGroup) { this.agendaGroup = options.agendaGroup; this.autoFocus = extd.isBoolean(options.autoFocus) ? options.autoFocus : false; @@ -236,7 +247,7 @@ var Rule = declare({ fire: function (flow, match) { var ret = new Promise(), cb = this.cb; - try { + try { if (cb.length === 3) { cb.call(flow, match.factHash, flow, ret.resolve); } else { @@ -249,67 +260,123 @@ var Rule = declare({ } } }); +exports.Rule = Rule; +// +function _mergePatterns (patterns) { + // + return function (patt, i) { + // [pattern], [pattern], ... in arrays of length 1 + // we wish to build a single array in order of lhs progression + if( isArray(patt) ) { + if( patt.length === 1 ) { + patt = patt[0]; + i = 0; + } + else { + throw new Error('invalid pattern structure'); + } + } + if (!patterns[i]) { + patterns[i] = i === 0 ? [] : patterns[i - 1].slice(); + //remove dup + if (i !== 0) { + patterns[i].pop(); + } + patterns[i].push(patt); + } else { + extd(patterns).forEach(function (p) { + p.push(patt); + }); + } + }; +} +// +// function createRule(name, options, conditions, cb) { - if (isArray(options)) { - cb = conditions; - conditions = options; - } else { - options = options || {}; - } - var isRules = extd.every(conditions, function (cond) { - return isArray(cond); - }); - if (isRules && conditions.length === 1) { - conditions = conditions[0]; - isRules = false; - } - var rules = []; - var scope = options.scope || {}; - conditions.scope = scope; - if (isRules) { - var _mergePatterns = function (patt, i) { - if (!patterns[i]) { - patterns[i] = i === 0 ? [] : patterns[i - 1].slice(); - //remove dup - if (i !== 0) { - patterns[i].pop(); - } - patterns[i].push(patt); - } else { - extd(patterns).forEach(function (p) { - p.push(patt); - }); - } - - }; - var l = conditions.length, patterns = [], condition; - for (var i = 0; i < l; i++) { - condition = conditions[i]; - condition.scope = scope; - extd.forEach(parsePattern(condition), _mergePatterns); - - } - rules = extd.map(patterns, function (patterns) { - var compPat = null; - for (var i = 0; i < patterns.length; i++) { - if (compPat === null) { - compPat = new CompositePattern(patterns[i++], patterns[i]); - } else { - compPat = new CompositePattern(compPat, patterns[i]); - } - } - return new Rule(name, options, compPat, cb); - }); - } else { - rules = extd.map(parsePattern(conditions), function (cond) { - return new Rule(name, options, cond, cb); - }); - } - return rules; + var rules = [], scope, patterns, isComposite; + function processConditions(conditions, scope) { + var l = conditions.length, + merged = [], + fnMerge = _mergePatterns(merged), + isRules = extd.every(conditions, function (cond) {return isArray(cond);}), + condition, rules, patterns; + // + if( isRules && conditions.length === 1 ) { + isRules = false; + conditions = conditions[0]; + } + // + function isSinglePattern(patterns) { + var ret = true; + if( patterns.length > 1 ) { + if( isArray(patterns[0]) ) { + ret = false; + } + // else it's OR [ p, p,...] which we treat as a single rule which results in multiple rules + } + return ret; + } + // + function patternFromCondition(condition, scope) { + var patterns; + condition.scope = scope; + patterns = parsePattern(condition); + return patterns; + } + // + function compositePattern(patterns) { + + return extd.map(merged, function (patterns) { + var compPat = null; + for (var i = 0; i < patterns.length; i++) { + if (compPat === null) { + compPat = new CompositePattern(patterns[i++], patterns[i]); + } else { + compPat = new CompositePattern(compPat, patterns[i]); + } + } + return new Rule(name, options, compPat, cb); + }); + } + // + function singlePattern(pattern) { + return extd.map(patterns, function (cond) { + return new Rule(name, options, cond, cb); + }); + } + // + if( isRules ) { + for (var i = 0; i < l; i++) { + condition = conditions[i]; + condition.scope = scope; + patterns = patternFromCondition(condition, scope); + extd.forEach( patterns, fnMerge ); + } + rules = compositePattern(merged); + } + else { + patterns = patternFromCondition(conditions, scope); + if( isSinglePattern(patterns) ) { + rules = singlePattern(patterns); + } + else { + extd.forEach( patterns, fnMerge ); + rules = compositePattern(merged); + } + } + return rules; + } + // + if (isArray(options)) { + cb = conditions; + conditions = options; + options = {}; + } else { + options = options || {}; + } + scope = options.scope || {}; + return processConditions(conditions, scope); } - exports.createRule = createRule; - -