Implement parser for SQL-like queries

This commit is contained in:
Zaid Abdulla committed 2018-12-26 00:20:47 -08:00
1 parent 1c9061ab5a
commit eda43083ec
2 files changed
+161 -1

No files matched your search

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