UNPKG

logica

Version:

a compile-to-javascript predicate logic language

167 lines (138 loc) 3.73 kB
var parse = require('./postParser.js'); /* Options: prettyPrint - generate code with indentation for readability */ 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 isLiteral = function (node) { return node.type === 'NUMBER' || node.type === 'STRING' || node.type === 'NULL' || node.type === 'BOOLEAN' } function generateCode(parsed, opts) { opts = opts || {}; var AST = parsed.AST; var references = {}; var line = 0; var indentation = -1; function indent() { indentation++; return !opts.prettyPrint ? '' : (new Array(indentation + 1)).join(' '); } function outdent() { indentation--; return !opts.prettyPrint ? '' : (new Array(indentation + 1)).join(' '); } function newline() { line++; return !opts.prettyPrint || line === 1 ? '' : '\n' } function Operation(node) { var ops = [node.headOperand].concat(node.tailOperands) // check for consistently typed literals var type = ops.filter(isLiteral).reduce(function (acc, op) { acc = acc || op.type; if (op.type !== acc) { throw new SyntaxError('Literal operand type mismatch: ' + op.val + ' is a ' + op.type + ', expected ' + acc) } return acc; }, null) var body = newline() + indent(); body += "$['" + node.operator + "'](" + Operand(node.headOperand) + ', ' + node.tailOperands.map(Operand).join(', ') + ")"; outdent() return body; } var addBool = function (op) { if (op.type === 'SYMBOL') { references[op.val] = TYPE.boolean } } function Combination(node) { node.operands.forEach(addBool) switch (node.combinator) { case "AND": return '(' + node.operands.map(Operand).join(' && ') + ')' case "OR": return '(' + node.operands.map(Operand).join(' || ') + ')' case "NOT": var op = node.operands[0] return '!(' + Operand(op) + ')' } } function Variable(node) { return "v['" + node.val + "']" } function Literal(node) { switch (node.type) { case 'NUMBER': case 'STRING': case 'BOOLEAN': return node.val; case 'NULL': return 'null'; } return node.val } function Operand(node) { switch (node.type) { case "OPERATION": return Operation(node) case "COMBINATION": return Combination(node) case "SYMBOL": if (!(node.val in references)) { references[node.val] = TYPE.any } return Variable(node) default: return Literal(node) } } function Wrapper(body) { var ref = JSON.stringify(references) if (opts.fullWrapper) { return "function ($, v) { " + (Object.keys(references).length ? (newline() + "$.$(v," + ref + ");") : '') + newline() + "return " + body + "; }"; } else { return '/**/' + ref + '/**/' + body; } } // support boolean literals as root node var body switch(AST.type) { case 'COMBINATION': body = Combination(AST) break; case 'OPERATION': body = Operation(AST) break; case 'BOOLEAN': body = Operand(AST) break; } return Wrapper(body) } function compile(source, opts) { source = source.trim(); var parsed = parse(source) return generateCode(parsed, opts); } module.exports = compile module.exports.generate = generateCode;