Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Functions calls used in constraint predicates #13

Closed
robblovell opened this Issue · 3 comments

2 participants

@robblovell

The rule engine has trouble parsing rules with constructs like:

when {
        p : Property p.name != null && p.value != -1 {name:pn,value:pv};
        d : Dimension d.name!=null && isTrue(d.range.contains(pv));
    }

but this works:

when {
        d : Dimension d.name!=null && isTrue(d.range.contains(20));
}

Here range is a class "Range" that encapsulates the "contains" function that determines if the value is in range.

The pv doesn't seem to be considered a valid symbol by the parser when parsing the "contains(pv)" function call and returns the error that "pv is not defined" (I have tried p.value here also). Pulling off the "isTrue" parses, but the rule no longer functions correctly (it always fires).

Simple models without functions work as seen in the following:

when {
     p : Property p.name != null && p.value != -1;
     d : Dimension2 d.name!=null && p.value >= d.low && d.high >= p.value;
}

Is there a fix for this? Allowing function calls here is a step up in the expressive range of the language.

Full Code below:

the dsl: /rules/routebroken.nools

define Dimension {
    name:null,
    range: new Range(0,0),

    constructor : function(n,r){
        this.name = n;
        this.range = r;
    }
}

define Property {
    name:null,
    value:-1,

    constructor : function(n,v){
        this.name = n;
        this.value = v;
    }
}

rule RouteBroken {
    priority:1,
    when {
        p : Property p.name != null && p.value != -1 {name:pn,value:pv};
        d : Dimension d.name!=null && isTrue(d.range.contains(pv));
    }
    then {
        console.log("Route broken:  Dimension:"+d.name+"=>"+d.range.getRange()+" contains("+p.value+")");
    }
}

the program: routedslbroken.js

if (!(typeof exports === "undefined")) {
    var nools = require("nools");

    var range_module = require("./modules/range/range.class.js");
    var enum_module = require("./modules/enum/enum.js");

    var Range = range_module.Range;
    var Enum = enum_module.Enum;
}

var flow = nools.compile(__dirname + "/rules/routebroken.nools");
Dimension = flow.getDefined("Dimension");
Property = flow.getDefined("Property");

r = new Range(4,200);
d = new Dimension("Quantity",r);

var session = flow.getSession();

session.assert(new Dimension("Quantity",r));
session.assert(new Property("Quantity",10));
session.assert(new Property("Quantity",100));
session.assert(new Property("Quantity",1000)); // should not fire a rule.

session.match().then(
    function(){
        console.log("Done");
    },
    function(err){
        //uh oh an error occurred
        console.error(err);
    });

/modules/enum/enum.js

(function (exports) {
    function copyOwnFrom(target, source) {
        Object.getOwnPropertyNames(source).forEach(function(propName) {
            Object.defineProperty(target, propName,
                Object.getOwnPropertyDescriptor(source, propName));
        });
        return target;
    }

    function Symbol(name, props) {
        this.name = name;
        if (props) {
            copyOwnFrom(this, props);
        }
        Object.freeze(this);
    }
    /** We don’t want the mutable Object.prototype in the prototype chain */
    Symbol.prototype = Object.create(null);
    Symbol.prototype.constructor = Symbol;
    /**
     * Without Object.prototype in the prototype chain, we need toString()
     * in order to display symbols.
     */
    Symbol.prototype.toString = function () {
        return "|"+this.name+"|";
    };
    Object.freeze(Symbol.prototype);

    Enum = function (obj) {
        if (arguments.length === 1 && obj !== null && typeof obj === "object") {
            Object.keys(obj).forEach(function (name) {
                this[name] = new Symbol(name, obj[name]);
            }, this);
        } else {
            Array.prototype.forEach.call(arguments, function (name) {
                this[name] = new Symbol(name);
            }, this);
        }
        Object.freeze(this);
    }
    Enum.prototype.symbols = function() {
        return Object.keys(this).map(
            function(key) {
                return this[key];
            }, this
        );
    }
    Enum.prototype.contains = function(sym) {
        if (! sym instanceof Symbol) return false;
        return this[sym.name] === sym;
    }
    exports.Enum = Enum;
    exports.Symbol = Symbol;
}(typeof exports === "undefined" ? this.enums = {} : exports));
// Explanation of this pattern: http://www.2ality.com/2011/08/universal-modules.html

/modules/range/range.class.js

(function (exports) {

    Range = function (start_, end_, step_) {
        var range;
        var typeofrange;
        var typeofStart;
        var typeofEnd;
        var largerange = 100;
        var rangelow,rangehigh;

        Array.prototype.contains = function(k) {
            for(var p in this)
                if(this[p] === k)
                    return true;
            return false;
        }

        var init = function(start, end, step) {
            range = [];
            typeofStart = typeof start;
            typeofEnd = typeof end;

            if (typeof(start) == "object")
            {
                if (start instanceof Array) {
                    range = start;
                    typeofrange = "array";
                }
                // TODO: Hash?
                else { // assume an enum if it isn't an array.
                    range = start;
                    typeofrange = "enum";
                }

                return;
            }

            if (step === 0) {
                throw TypeError("Step cannot be zero.");
            }

            if (typeofStart == "undefined" || typeofEnd == "undefined") {
                throw TypeError("Must pass start and end arguments.");
            } else if (typeofStart != typeofEnd) {
                throw TypeError("Start and end arguments must be of same type.");
            }

            typeof step == "undefined" && (step = 1);

            if (end < start) {
                step = -step;
            }

            rangelow=start;
            rangehigh=end;
            if (typeofStart == "number") {
                if ((end-start)/step >= largerange || step == 1) {
                    typeofrange = "range";
                    if (step != 1){
                        throw TypeError("Step size must be 1 for ranges larger than "+largerange+".");
                    }
                }
                else {
                    typeofrange = typeofStart;
                    while (step > 0 ? end >= start : end <= start) {
                        range.push(start);
                        start += step;
                    }

                }

            } else if (typeofStart == "string") {

                typeofrange = typeofStart;
                if (start.length != 1 || end.length != 1) {
                    throw TypeError("Only strings with one character are supported.");
                }

                start = start.charCodeAt(0);
                end = end.charCodeAt(0);

                while (step > 0 ? end >= start : end <= start) {
                    range.push(String.fromCharCode(start));
                    start += step;
                }

            } else {
                throw TypeError("Only string and number types are supported");
            }
        };
        var getRange = function() { if (typeofrange=="range") return getLow()+".."+getHigh();
            else return range; };
        var getLow = function () { return rangelow; };
        var getHigh = function () { return rangehigh; };

        var contains = function (value) {
            if (typeofrange == "range") {
                return value >= rangelow && value <= rangehigh;
            }
            return range.contains(value)
            /*if (typeofrange == "enum") {
                return range.contains(value)
            }
            for(var p in range)
                if(this[p] === value)
                    return true;
            return false;

            */
        }

        init(start_, end_, step_);

        return {
            getRange:getRange,
            getLow:getLow,
            getHigh:getHigh,
            contains:contains
        };

    };

    exports.Range = Range;
}(typeof exports === "undefined" ? this.range = {} : exports));
//exports.Range = Range;
@doug-martin doug-martin was assigned
@doug-martin doug-martin referenced this issue from a commit in doug-martin/nools
@doug-martin doug-martin fixed issue #13
* Fixed constraint matcher to look up identifiers in property chains
8780e1b
@doug-martin
Owner

Hi!

Thanks for the detailed issue!

So the issue ended up being in the getIdentifiers in the constraintMatcher where It was not gathering identifiers from functions in a property chain.

If you update your version of nools to 0.0.5 your code will work!

-Doug

@doug-martin doug-martin closed this
@robblovell

Cool, Thanks, glad I was able to help identify the problem quickly.

robb

@robblovell

works.
r

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.