unified-query
Version:
Composable search input with autocompletion and a rich query-language parser for the Unified Data System
143 lines (142 loc) • 5.2 kB
JavaScript
/* -------------------------------------------------------------------------- */
/* analyser */
/* -------------------------------------------------------------------------- */
export function timestampAnalyzer(seg, { allowBoolean = false }) {
const timestamps = [];
const errors = [];
/* operator duplication guards */
let seenLt = false;
let seenGt = false;
/* utility token exclusive branch */
let utilValue;
/* boolean branch */
let booleanValue;
/* ------------------------------------------------------------------ */
/* iterate with index so we can enforce “utility must be first” */
/* ------------------------------------------------------------------ */
seg.tokens.forEach((tok, idx) => {
/* -------- date / time / datetime -------------------------------- */
if (tok.kind === 'date' || tok.kind === 'time' || tok.kind === 'datetime') {
if (utilValue) {
errors.push(utilMixErr(tok));
return;
}
if (booleanValue !== undefined) {
errors.push(combineErr(tok));
return;
}
if (tok.op) {
if (tok.op === '<') {
if (seenLt) {
errors.push(dupErr(tok, '<'));
return;
}
seenLt = true;
}
else {
if (seenGt) {
errors.push(dupErr(tok, '>'));
return;
}
seenGt = true;
}
timestamps.push({ op: tok.op, value: tok.value });
}
else {
timestamps.push(tok.value);
}
return;
}
/* -------- date utility ------------------------------------------ */
if (tok.kind === 'dateutil') {
// must be first and exclusive; utilities cannot have op
if (idx !== 0) {
errors.push(utilFirstErr(tok));
return;
}
if (tok.op) {
errors.push(utilOpErr(tok));
return;
}
if (seg.tokens.length > 1) {
errors.push(utilMixErr(tok));
return;
}
utilValue = tok.value;
return;
}
/* -------- boolean ------------------------------------------------ */
if (allowBoolean && tok.kind === 'boolean') {
if (utilValue) {
errors.push(utilMixErr(tok));
return;
}
if (timestamps.length) {
errors.push(combineErr(tok));
return;
}
if (booleanValue !== undefined) {
errors.push({
message: 'duplicate boolean flag',
token: tok.raw, from: tok.from, to: tok.to,
});
return;
}
booleanValue = tok.value;
return;
}
/* -------- anything else ----------------------------------------- */
errors.push({
message: `invalid token "${tok.raw}"`,
token: tok.raw, from: tok.from, to: tok.to,
});
});
/* ------------------------------------------------------------------ */
/* derive parsed result */
/* ------------------------------------------------------------------ */
let parsed;
if (utilValue)
parsed = utilValue;
else if (booleanValue !== undefined)
parsed = booleanValue;
else if (timestamps.length)
parsed = timestamps;
else if (allowBoolean)
parsed = undefined; // no-arg case for @deleted
else
parsed = timestamps; // empty list (created/etc.)
return { parsed, errors };
/* ------------------------------------------------------------------ */
/* helpers */
/* ------------------------------------------------------------------ */
function dupErr(tok, op) {
return {
message: `duplicate "${op}" operator`,
token: tok.raw, from: tok.from, to: tok.to,
};
}
function combineErr(tok) {
return {
message: 'cannot combine boolean flag with timestamps',
token: tok.raw, from: tok.from, to: tok.to,
};
}
function utilFirstErr(tok) {
return {
message: 'utility value must be first token',
token: tok.raw, from: tok.from, to: tok.to,
};
}
function utilMixErr(tok) {
return {
message: 'cannot combine utility value with other arguments',
token: tok.raw, from: tok.from, to: tok.to,
};
}
function utilOpErr(tok) {
return {
message: 'comparison operators are not allowed on utility values',
token: tok.raw, from: tok.from, to: tok.to,
};
}
}