index

JSL Features

Builtins

JSL supports a few important builtins as part of the language. Please refer to the builtins chapter for a complete description.

We introduce builtins by extending our bid/offer matching procedure to also report "unmatched" orders. The rule is modified with a $or builtin which goes through all bids and either finds a matching offer, OR uses the $bind builtin to emit an unmatched warning into the bid record output along with the string 'N/A/' for the offerer name.


var JSL = require('lib-jsl');

var offers = [
[{
    type : 'offer',
    offerer : 'sandeep',
    bidder : '$bidder',
    symbol : 'ABC',
    price : 20,
    qty : 100
}],
[{
    type : 'offer',
    offerer : 'shekhar',
    bidder : '$bidder',
    symbol : 'ABC',
    price : 25,
    qty : 100
}],
[{
    type : 'offer',
    offerer : 'ruchir',
    bidder : '$bidder',
    symbol : 'ABC',
    price : 20,
    qty : 200
}]

]


var bids = [
[{
    type : 'bid',
    offerer : '$offerer',
    bidder : 'kavi',
    symbol : 'ABC',
    price : 20,
    qty : 100
}],
[{
    type : 'bid',
    offerer : '$offerer',
    bidder : 'pradeep',
    symbol : 'ABC',
    price : 30,
    qty : 100
}]


]

var rules = [
    [   
        { match : {offerer : '$offerer', bidder : '$bidder', symbol : '$symbol', price : '$price', qty : '$qty', status : '$status'}},
        { type: 'bid', bidder : '$bidder', symbol : '$symbol', price : '$price', qty : '$qty'},
        { $or : [
            { $and : [ 
                { type : 'offer', offerer : '$offerer', symbol : '$symbol', price : '$price', qty : '$qty'},
                { $bind : [ '$status', 'matched' ] }
            ] },
            { $bind : [['$status', '$offerer'] , ['**unmatched**', 'N/A'] ] }
        ] }

    ]
]

var jsl = new JSL({
    rules: rules.concat(bids, offers),
    query: [{match: '$match'}],
    transform: '$match'
});

var response = jsl.run();
console.log('matches: ', response);

module.exports = response;


/*
response 

[
    {
        "offerer": "sandeep",
        "bidder": "kavi",
        "symbol": "ABC",
        "price": 20,
        "qty": 100,
        "status": "matched"
    },
    {
        "offerer": "N/A",
        "bidder": "pradeep",
        "symbol": "ABC",
        "price": 30,
        "qty": 100,
        "status": "**unmatched**"
    }
]

*/

We use the $or, $and and $bind builtins in this example.

The $or builtin stops on first success, and $and succeeds only if all its objects do.

$bind is nothing but the equivalent of the familiar unify utility in logic programming languages. It succeeds if the two arguments unify with each other (JSL defines unification as first argument containing the second at all levels). In this example we use $bind to do the equivalent of assigning a value to a variable, as well as assignment of multiple values to multiple variables by position in arrays. In general, any two JS objects can be unified by $bind.

The general shape of builtins is as follows :

    { <$xxx> : [ arg1 , arg2 ... ] }

Note that a builtin object can have only one key, which must be a $prefixed name of a valid builtin. While $and and $or can have any number of arguments, some other builtins (e.g. $not, $bind, $query) have a fixed number of arguments

Callbacks

As an embedded logic programming library, JSL supports callbacks into the host. This is done using a special builtin: $call

We illustrate the use of callbacks by extending our bid/offer matching example to support "best match". Simply stated, we want the spread between bid and offer to be the maximum possible, so the seller gets the best possible price, and the buyer gets the best possible discount. We also include support for "open orders", where a null price value indicates an intent to buy (bid) or sell (offer) at any price. We further assume that open bid/offers should be matched at the corresponding offer/bid, i.e. with zero spread.

We now make explicit an assumption running through these example, i.e. a person (bidder or offerer) can have only one order in the system at a time. Thus, bidder/offerer names serve as unique ids in our orders database. This is just a simplification for this example.

Finally, we output feasible order schedule now, instead of all possible matches as we have done so far. This is done by keeping track of which offers have been consumed by some bidder in the matching process


var JSL = require('lib-jsl');

var offers = [
[{
    type : 'offer',
    offerer : 'sandeep',
    bidder : '$bidder',
    symbol : 'ABC',
    price : 18,
    qty : 100
}],
[{
    type : 'offer',
    offerer : 'ruchir',
    bidder : '$bidder',
    symbol : 'ABC',
    price : 20,
    qty : 100
}],
[{
    type : 'offer',
    offerer : 'prachi',
    bidder : '$bidder',
    symbol : 'ABC',
    price : 16,
    qty : 100
}],
[{
    type : 'offer',
    offerer : 'avantika',
    bidder : '$bidder',
    symbol : 'ABC',
    price : null,
    qty : 200
}]

]


var bids = [
[{
    type : 'bid',
    offerer : '$offerer',
    bidder : 'kavi',
    symbol : 'ABC',
    price : 20,
    qty : 100
}],
[{
    type : 'bid',
    offerer : '$offerer',
    bidder : 'pradeep',
    symbol : 'ABC',
    price : 21,
    qty : 100
}],
[{
    type : 'bid',
    offerer : '$offerer',
    bidder : 'taran',
    symbol : 'ABC',
    price : 20,
    qty : 200
}],
[{
    type : 'bid',
    offerer : '$offerer',
    bidder : 'naveen',
    symbol : 'ABC',
    price : 26,
    qty : 100
}],
[{
    type : 'bid',
    offerer : '$offerer',
    bidder : 'prashant',
    symbol : 'ABC',
    price : 25,
    qty : 200
}],
[{
    type : 'bid',
    offerer : '$offerer',
    bidder : 'shipu',
    symbol : 'ABC',
    price : null,
    qty : 200
}]


]

var rules = [

    [
        { match : {offerer : '$offerer', bidder : '$bidder', symbol : '$symbol', price : '$price', qty : '$qty'}},
        { $query : [ [{ accumulateBestMatches : '$x'}], null, '$y'] },
        { type : 'bid', bidder : '$bidder', symbol : '$symbol', qty : '$qty'},
        { $call : [ 'getBestMatch', '$bidder', ['$offerer', '$bidPrice', '$price'] ] }
    ],
    [
        { accumulateBestMatches : '$x'},
        { type : 'bid', bidder : '$bidder', symbol : '$symbol', price : '$bidPrice', qty : '$qty'},
        { type : 'offer', offerer : '$offerer', symbol : '$symbol', price : '$offerPrice', qty : '$qty'},
        { $call : [ 'accumulateMatch', '$bidder', '$bidPrice', '$offerer', '$offerPrice' ] }
    ]


]

var bestOffers = {};
var offersConsumed = {};
var callbacks = {
    accumulateMatch : function(bidder, bidPrice, offerer, offerPrice) {
        if (!offersConsumed[offerer]) {
            if (bestOffers[bidder] == null) {
                if (offerPrice != null && bidPrice != null && bidPrice - offerPrice > 0) { 
                    bestOffers[bidder] = [offerer, bidPrice, offerPrice];
                    offersConsumed[offerer] = true;
                }
                else if (offerPrice == null && bidPrice != null) {
                    bestOffers[bidder] = [offerer, bidPrice, bidPrice]
                    offersConsumed[offerer] = true;
                }
                else if (offerPrice != null && bidPrice == null) {
                    bestOffers[bidder] = [offerer, offerPrice, offerPrice]
                    offersConsumed[offerer] = true;
                }
            }
            else {
                var spread = bestOffers[bidder][1] - bestOffers[bidder][2];
                if (offerPrice != null && bidPrice != null && bidPrice - offerPrice > spread) { 
                    offersConsumed[bestOffers[bidder][0]] = false;
                    bestOffers[bidder] = [offerer, bidPrice, offerPrice];
                    offersConsumed[offerer] = true;
                }
                else if (offerPrice != null && bidPrice == null && offerPrice < bestOffers[bidder][2]) {
                    bestOffers[bidder] = [offerer, offerPrice, offerPrice]
                }
            }
        }
        return true; // accumulation always succeeds
    },

    getBestMatch : function(bidder) {
        var result = null;
        if (bestOffers[bidder] != null) {
            result = bestOffers[bidder]
        }
        return result;
    }
}

var jsl = new JSL({
    rules: rules.concat(bids, offers),
    query: [{match: '$match'}],
    callbacks : callbacks,
    transform: '$match'
});

var response = jsl.run();

console.log('matches: ', response);

module.exports = response;


/*
response 

[
    {
        "offerer": "prachi",
        "bidder": "kavi",
        "symbol": "ABC",
        "price": 16,
        "qty": 100
    },
    {
        "offerer": "sandeep",
        "bidder": "pradeep",
        "symbol": "ABC",
        "price": 18,
        "qty": 100
    },
    {
        "offerer": "avantika",
        "bidder": "taran",
        "symbol": "ABC",
        "price": 20,
        "qty": 200
    },
    {
        "offerer": "ruchir",
        "bidder": "naveen",
        "symbol": "ABC",
        "price": 20,
        "qty": 100
    }
]

*/

The ruless organize the matching procedure at the top level. An accumulator is called once for each possible bid/offer pair with matching qty via the $query callback which is basically a subquery mechanism (see $query. The accumlateBestMatches callback stores winning matches in a variable visible to its scope. The getBestMatch callback accesses this variable once for each bidder, failing if a best match is not found.

The callback itself is supplied as a function valued key in the callbacks object given to Jsl as part of its parameters.

var jsl = new JSL({rules : ruleset.concat(row), query: query, transform : transform, callbacks : callbacks});

The shape of the $call builtin is as follows :

    { $call : [ <'cbname'>, arg1, arg2, ..., ['$outvar'] ] }

The first argument to $call is the name of the callback to be called. This callback function should be present in the callbacks parameter when running JSL. Subsequent arguments are handed to the callback by position with the exception of ['$outvar'] if present. Note that an array object at the end of the parameters list is assumed to specify the $outvar. Any value returned by the callback is unified with the $outvar. Typically there are two ways to use $outvar:

  1. Supply a variable, so it captures the return value of the callback

  2. Supply a literal value, so that the callback succeeds only if it returns something that can be unified with it

Please note that if the callback requires an array valued parameter at the end, it must be supplied at least an empty array as the last parameter in the $call parameter list in order to avoid confusing the last input parameter as the $outvar.