160 lines
3.6 KiB
JavaScript
160 lines
3.6 KiB
JavaScript
"use strict";
|
|
|
|
const parsimmon = require("parsimmon");
|
|
|
|
// Turn escaped characters into real ones (e.g. "\\n" becomes "\n").
|
|
function interpretEscapes(str) {
|
|
let escapes = {
|
|
b: "\b",
|
|
f: "\f",
|
|
n: "\n",
|
|
r: "\r",
|
|
t: "\t"
|
|
};
|
|
return str.replace(/\\(u[0-9a-fA-F]{4}|[^u])/, (_, escape) => {
|
|
let type = escape.charAt(0);
|
|
let hex = escape.slice(1);
|
|
if (type === "u") return String.fromCharCode(parseInt(hex, 16));
|
|
|
|
if (escapes.hasOwnProperty(type)) return escapes[type];
|
|
|
|
return type;
|
|
});
|
|
}
|
|
|
|
const lang = parsimmon.createLanguage({
|
|
ComparisonOperator: function(r) {
|
|
return parsimmon
|
|
.alt(
|
|
parsimmon.string(">="),
|
|
parsimmon.string("<>"),
|
|
parsimmon.string("<="),
|
|
parsimmon.string("="),
|
|
parsimmon.string(">"),
|
|
parsimmon.string("<")
|
|
)
|
|
.skip(r._);
|
|
},
|
|
IsNullOperator: function(r) {
|
|
return parsimmon
|
|
.alt(
|
|
parsimmon
|
|
.regexp(/is\s+null/i)
|
|
.result("IS NULL")
|
|
.desc("IS NULL"),
|
|
parsimmon
|
|
.regexp(/is\s+not\s+null/i)
|
|
.result("IS NOT NULL")
|
|
.desc("IS NOT NULL")
|
|
)
|
|
.skip(r._);
|
|
},
|
|
NotOperator: function(r) {
|
|
return parsimmon
|
|
.regexp(/not/i)
|
|
.result("NOT")
|
|
.skip(r._)
|
|
.desc("NOT");
|
|
},
|
|
AndOperator: function(r) {
|
|
return parsimmon
|
|
.regexp(/and/i)
|
|
.result("AND")
|
|
.skip(r._)
|
|
.desc("AND");
|
|
},
|
|
OrOperator: function(r) {
|
|
return parsimmon
|
|
.regexp(/or/i)
|
|
.result("OR")
|
|
.skip(r._)
|
|
.desc("OR");
|
|
},
|
|
Parameter: function(r) {
|
|
return parsimmon
|
|
.regexp(/[a-zA-Z0-9_.*]+/)
|
|
.skip(r._)
|
|
.desc("parameter");
|
|
},
|
|
StringValueSql: function(r) {
|
|
return parsimmon
|
|
.regexp(/'([^']*)'/, 1)
|
|
.atLeast(1)
|
|
.skip(r._)
|
|
.map(s => s.join("'"))
|
|
.desc("string");
|
|
},
|
|
StringValueJs: function(r) {
|
|
return parsimmon
|
|
.regexp(/"((?:\\.|.)*?)"/, 1)
|
|
.skip(r._)
|
|
.map(interpretEscapes)
|
|
.desc("string");
|
|
},
|
|
NumberValue: function(r) {
|
|
return parsimmon
|
|
.regexp(/-?(0|[1-9][0-9]*)([.][0-9]+)?([eE][+-]?[0-9]+)?/)
|
|
.skip(r._)
|
|
.map(Number)
|
|
.desc("number");
|
|
},
|
|
Value: function(r) {
|
|
return parsimmon.alt(r.NumberValue, r.StringValueSql, r.StringValueJs);
|
|
},
|
|
Comparison: function(r) {
|
|
return parsimmon.alt(
|
|
parsimmon.seqMap(r.Parameter, r.IsNullOperator, (p, o) => [o, p]),
|
|
parsimmon.seqMap(
|
|
r.Parameter,
|
|
r.ComparisonOperator,
|
|
r.Value,
|
|
(p, o, v) => [o, p, v]
|
|
)
|
|
);
|
|
},
|
|
Expression: function(r) {
|
|
function binaryLeft(operatorsParser, nextParser) {
|
|
return parsimmon.seqMap(
|
|
nextParser,
|
|
parsimmon.seq(operatorsParser, nextParser).many(),
|
|
(first, rest) =>
|
|
rest.reduce((acc, ch) => {
|
|
let [op, another] = ch;
|
|
if (op === acc[0]) return acc.concat([another]);
|
|
if (op === another[0]) return [op, acc].concat(another.slice(1));
|
|
return [op, acc, another];
|
|
}, first)
|
|
);
|
|
}
|
|
|
|
function unary(operatorsParser, nextParser) {
|
|
return parsimmon.seq(operatorsParser, nextParser).or(nextParser);
|
|
}
|
|
|
|
return binaryLeft(
|
|
r.OrOperator,
|
|
binaryLeft(
|
|
r.AndOperator,
|
|
unary(
|
|
r.NotOperator,
|
|
r.Comparison.or(
|
|
r.Expression.wrap(
|
|
parsimmon.string("("),
|
|
parsimmon.string(")").skip(r._)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
).trim(r._);
|
|
},
|
|
_: function() {
|
|
return parsimmon.optWhitespace;
|
|
}
|
|
});
|
|
|
|
function parse(filter) {
|
|
return lang.Expression.tryParse(filter);
|
|
}
|
|
|
|
exports.parse = parse;
|