logica
Version:
a compile-to-javascript predicate logic language
167 lines (138 loc) • 3.73 kB
JavaScript
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;