Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Functions calls used in constraint predicates #13

Closed
robblovell opened this issue Jan 9, 2013 · 3 comments
Closed

Functions calls used in constraint predicates #13

robblovell opened this issue Jan 9, 2013 · 3 comments
Assignees
Labels

Comments

@robblovell
Copy link

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;
@ghost ghost assigned doug-martin Jan 9, 2013
doug-martin added a commit to doug-martin/nools that referenced this issue Jan 9, 2013
* Fixed constraint matcher to look up identifiers in property chains
@doug-martin
Copy link
Contributor

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

@robblovell
Copy link
Author

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

robb

@robblovell
Copy link
Author

works.
r

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants