UNPKG

logica

Version:

a compile-to-javascript predicate logic language

166 lines (150 loc) 4.43 kB
// UMD voodo: (function (root, factory) { if (typeof exports === 'object') { module.exports = factory(); } else if (typeof define === 'function' && define.amd) { define(factory); } }(this, function () { function merge(objA, objB) { var newObj = {} for (var key in objA) { newObj[key] = objA[key]; } for (var key in objB) { newObj[key] = objB[key]; } return newObj; } var toArray = function (a) { return Array.prototype.slice.call(a); } var tail = function (a) { return Array.prototype.slice.call(a, 1); } var I = function (x) { return x; } var TYPE = { 'any': 0, '0': 'any', 'nullable': 1, '1': 'nullable', 'null': 2, '2': 'null', 'boolean': 4, '4': 'boolean', 'number': 8, '8': 'number', 'string': 16, '16': 'string' } var defaultOperators = { 'EQ': function (operand1) { var operands = toArray(arguments); var type = typeof operand1; return operands.every(function (op) { if (typeof op !== type) { throw new TypeError('Operand type mismatch'); } return op === operand1; }) }, 'GT': function (op1, op2) { if (arguments.length !== 2) { throw new Error('GT must be called with exactly two operands') } if (typeof op1 !== typeof op2 || typeof op1 !== 'number') { throw new Error('GT must be called with Number operands') } return op1 > op2; }, 'GTE': function (op1, op2) { if (arguments.length !== 2) { throw new Error('GTE must be called with exactly two operands') } if (typeof op1 !== typeof op2 || typeof op1 !== 'number') { throw new Error('GTE must be called with Number operands') } return op1 >= op2; }, 'LT': function (op1, op2) { if (arguments.length !== 2) { throw new Error('LT must be called with exactly two operands') } if (typeof op1 !== typeof op2 || typeof op1 !== 'number') { throw new Error('LT must be called with Number operands') } return op1 < op2; }, 'LTE': function (op1, op2) { if (arguments.length !== 2) { throw new Error('LTE must be called with exactly two operands') } if (typeof op1 !== typeof op2 || typeof op1 !== 'number') { throw new Error('LTE must be called with Number operands') } return op1 <= op2; }, 'IN': function (head) { var rest = []; tail(arguments) .forEach(function (op) { if (typeof op === 'string') { rest.push.apply(rest, op.split(/[, ]/) .map(function (str) { return str.trim() })) } rest.push(op) }) if (typeof head === 'string') { return head.split(/[, ]/).every(function (headOp) { return rest.some(function (op) { return op === headOp }) }) } else { return rest.some(function (op) { return op === head }) } }, // check for required typed references in state object '$': function(state, refs) { for(var ref in refs) { if (!(ref in state)) { throw new ReferenceError('Symbol `' + ref + '` used in expression but not defined') } var expectedType = TYPE[refs[ref]]; if (expectedType === 'any') return; var type = typeof state[ref]; if (type !== expectedType) { throw new TypeError('Symbol `' + ref + '` must be a ' + expectedType + ', but is ' + type) } } } } var functionBodyRegex = /function\s*?\(.*?\)\s*?{(.*)}/; function hydrate(src, customOperators) { var operators = !customOperators ? defaultOperators : merge(defaultOperators, customOperators); var fn; try{ if (src.substr(0,4) === '/**/') { // use compact source representation src = src.split('/**/') var symbols = src[1], expr = src[2]; src = "$.$(v," + symbols + ");return " + expr + ";"; fn = new Function('$','v', src); fn.symbols = JSON.parse(symbols); } else { // try hydrating from full function src var body = src.match(functionBodyRegex)[1]; fn = new Function('$','v', body) } } catch (e) { console.log(e.stack) throw new Error('Invalid function source', e); } return function (state) { return fn.call(null, operators, state); } } return hydrate; }));