Implement parser for SQL-like queries
This commit is contained in:
1 parent
1c9061ab5a
commit
eda43083ec
2 files changed
+161
-1
No files matched your search
@@ -0,0 +1,159 @@
|
||||
"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;
|
||||
Reference in new issue
Block a user