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
+2
-1
@@ -14,7 +14,8 @@
|
|||||||
"koa-bodyparser": "^4.2.0",
|
"koa-bodyparser": "^4.2.0",
|
||||||
"koa-router": "^7.3.0",
|
"koa-router": "^7.3.0",
|
||||||
"koa-static": "^4.0.2",
|
"koa-static": "^4.0.2",
|
||||||
"mithril": "^1.1.5"
|
"mithril": "^1.1.5",
|
||||||
|
"parsimmon": "^1.6.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"cssnano": "^3.10.0",
|
"cssnano": "^3.10.0",
|
||||||
|
|||||||
@@ -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