index


JSL Syntax

The JSL syntax is nothing but (constrained) JSON. The rules defining valid structure for any one item in a given JSL batch are defined below as an executable validator, using JSL itself to define the rules:



var JSL = new require('./jsl.js');
var formulas = require('./lib/formulas');

var rules  = [
[   { item : '$item'},
    { $or : [ 
        { fact : '$item'},
        { rule : '$item'}
    ] }
],
[   { fact : '$fact'},
    {$bind : [ '$fact', []]},
    { $call : [ 'length', '$fact', [1]] },
    { $each : [ '$fact', '$x', '$index', 
        {$and : [
            { $not : [ {builtin : '$x'}] },
            {jslObject : '$x'}
        ]}
    ]},
],

[   { rule : '$rule'},
    { $call : [ 'parseList', '$rule', [ '$head', '$body' ] ] },
    { jslObject : '$head' },
    { $each : [ '$body', '$x', '$index', 
        { $or : [ { jslObject : '$x'}, { builtin : '$x' } ] } ]},
],

[   { builtin : '$builtin'},
    {$bind : [ '$builtin', {} ] },
    { $call : [ 'length',  '$builtin', [1] ] },
    { $each : [ '$builtin', '$x', '$key', 
        { $and : [ { builtinName : '$key' }, { builtinArguments : '$x' } ] } ] },
],
[   { builtinName : '$x'},
    { $call : ['checkType', '$x', 'builtinName'] }
],
[   { builtinArguments : '$builtinArguments'},
    {$bind : [ '$builtinArguments', [] ] },
    { $each : [ '$builtinArguments', '$x', '$index', 
        { $or : [ { builtin : '$x' } , { jslObjectValue : '$x' } 
        ] }
    ] }
],
[   { jslObject : '$jslObject' },
    { $call : [ 'checkType', '$jslObject', 'jslObject' ] },
],
[   { jslObjectValue : '$jslObjectValue' },
    { $call : [ 'checkType', '$jslObjectValue', 'jslObjectValue' ] },
]


]


function builtinName(x) {
    return Object.keys(formulas).indexOf(x) >= 0 ? true : null;
}

function number(x) {
    return x instanceof Number ? true : null;
}

function string(x) {
    return typeof x === 'string' ? true : null;
}

function boolean (x) {
    return typeof x === 'boolean' ? true : null;
}

function jslObjectKey(x) {
    var result = true;
    if (typeof x === 'string') {
        if (x.match(/^\$/)) {
            result = null;
        }
    }
    return result;
}


function jslVariable(x) {
    var result = null;
    if (typeof x === 'string') {
        if (x.match(/^\$/)) {
            if (builtinName(x) === null) {
                result = true;
            }
        }
    }
    return result;
}

function jslScalarValue(x) {
    var result = true;
    if (typeof x === 'string') {
        if (x.match(/^\$/)) {
            if (jslVariable(x) === null) {
                result = null;
            }
        }
    }
    else {
        result = null;
    }
    return result;

}

function jslObjectValue (obj) {
    var result = true;
    if (typeof obj === 'object') {
        for (var x in obj) {
            if ( jslObjectKey(x) === null ||  jslObjectValue(obj[x]) === null) {
                result = null;
            }
        }
    }
    else if (typeof obj === 'string') {
        if (jslScalarValue(obj) === null) {
            result = null;
        }
    }
    else if (typeof obj === 'number' || typeof obj === 'boolean') {
        result = true; // noop
    }
    else {
        result = null;
    }
    return result;
}

function jslObject (obj) {
    var result = true;
    if (obj instanceof Object && !(obj instanceof Array)) {
        for (var x in obj) {
            if (jslObjectKey(x) === null ||  jslObjectValue(obj[x]) === null) {
                result = null;
            }
        }
    }
    else {
        result = null;
    }
    return result;
}


var terminalTypes = {
    builtinName : builtinName,
    number : number,
    string : string,
    boolean : boolean,
    jslVariable : jslVariable,
    jslObjectKey : jslObjectKey,
    jslScalarValue : jslScalarValue,
    jslObjectValue : jslObjectValue,
    jslObject : jslObject
}


function checkType(obj, type) {
    var result = null;
    if (terminalTypes[type] != null) {
        result = terminalTypes[type](obj);
    }
    return result;
}


var callbacks = {
    checkType : checkType
};
function validateJsl (input) { 
    var response = [];
    var query ;
    if (input instanceof Array && input.length > 0) {
        for (var i=0; i<input.length; i++) {
            query = [{item : input[i]}];
            var jsl = new JSL({rules: rules, query: query, callbacks : callbacks});
            var ret = jsl.run();
            if (ret instanceof Array && ret.length > 0 ) {
                response.push(ret);
            }
            else {
                console.log('validation failed on', input[i]);
                response = null;
                break;
            }
        }
    }
    return (response != null);
}

module.exports = {
    validateJsl : validateJsl
};

The validator defines rules that constrain the structure of any one item in a JSL batch, which can be either a fact or a rule. Further constraints that define valid structure for facts and rules are defined in subsequent rules.

Terminal types have been implemented via the checkType callback, which maintains a table of recognized terminal types. These include the usual scalar types, as well as specific constrained strings such as $variable names, and $builtin names. Finally, a jslObject type is defined which contains constrained keys and values.

The universal quantifier $each is used to compactly express rules applying to all elements of an object or array. Note that $each allows us to constrain keys as well as values of an object.