Use whitespace as path separator like double colon

This commit is contained in:
Guillaume Gomez 2023-06-12 13:23:38 +02:00
parent 57c215b08e
commit b99bf4677b

View File

@ -261,7 +261,7 @@ function initSearch(rawSearchIndex) {
}
function isStopCharacter(c) {
return isWhitespace(c) || isEndCharacter(c);
return isEndCharacter(c);
}
function isErrorCharacter(c) {
@ -357,18 +357,69 @@ function initSearch(rawSearchIndex) {
* @return {boolean}
*/
function isSeparatorCharacter(c) {
return c === "," || isWhitespaceCharacter(c);
return c === ",";
}
/**
* Returns `true` if the given `c` character is a whitespace.
/**
* Returns `true` if the given `c` character is a path separator. For example
* `:` in `a::b` or a whitespace in `a b`.
*
* @param {string} c
*
* @return {boolean}
*/
function isWhitespaceCharacter(c) {
return c === " " || c === "\t";
function isPathSeparator(c) {
return c === ":" || isWhitespace(c);
}
/**
* Returns `true` if the previous character is `lookingFor`.
*
* @param {ParserState} parserState
* @param {String} lookingFor
*
* @return {boolean}
*/
function prevIs(parserState, lookingFor) {
let pos = parserState.pos;
while (pos > 0) {
const c = parserState.userQuery[pos - 1];
if (c === lookingFor) {
return true;
} else if (!isWhitespace(c)) {
break;
}
pos -= 1;
}
return false;
}
/**
* Returns `true` if the last element in the `elems` argument has generics.
*
* @param {Array<QueryElement>} elems
* @param {ParserState} parserState
*
* @return {boolean}
*/
function isLastElemGeneric(elems, parserState) {
return (elems.length > 0 && elems[elems.length - 1].generics.length > 0) ||
prevIs(parserState, ">");
}
/**
* Increase current parser position until it doesn't find a whitespace anymore.
*
* @param {ParserState} parserState
*/
function skipWhitespace(parserState) {
while (parserState.pos < parserState.userQuery.length) {
const c = parserState.userQuery[parserState.pos];
if (!isWhitespace(c)) {
break;
}
parserState.pos += 1;
}
}
/**
@ -380,11 +431,14 @@ function initSearch(rawSearchIndex) {
* @return {QueryElement} - The newly created `QueryElement`.
*/
function createQueryElement(query, parserState, name, generics, isInGenerics) {
if (name === "*" || (name.length === 0 && generics.length === 0)) {
return;
const path = name.trim();
if (path.length === 0 && generics.length === 0) {
throw ["Unexpected ", parserState.userQuery[parserState.pos]];
} else if (path === "*") {
throw ["Unexpected ", "*"];
}
if (query.literalSearch && parserState.totalElems - parserState.genericsElems > 0) {
throw ["You cannot have more than one element if you use quotes"];
throw ["Cannot have more than one element if you use quotes"];
}
const typeFilter = parserState.typeFilter;
parserState.typeFilter = null;
@ -415,38 +469,40 @@ function initSearch(rawSearchIndex) {
typeFilter: "primitive",
};
}
const pathSegments = name.split("::");
if (pathSegments.length > 1) {
for (let i = 0, len = pathSegments.length; i < len; ++i) {
const pathSegment = pathSegments[i];
if (pathSegment.length === 0) {
if (i === 0) {
throw ["Paths cannot start with ", "::"];
} else if (i + 1 === len) {
throw ["Paths cannot end with ", "::"];
}
throw ["Unexpected ", "::::"];
}
if (pathSegment === "!") {
pathSegments[i] = "never";
if (i !== 0) {
throw ["Never type ", "!", " is not associated item"];
}
}
}
if (path.startsWith("::")) {
throw ["Paths cannot start with ", "::"];
} else if (path.endsWith("::")) {
throw ["Paths cannot end with ", "::"];
} else if (path.includes("::::")) {
throw ["Unexpected ", "::::"];
} else if (path.includes(" ::")) {
throw ["Unexpected ", " ::"];
} else if (path.includes(":: ")) {
throw ["Unexpected ", ":: "];
}
const pathSegments = path.split(/::|\s+/);
// In case we only have something like `<p>`, there is no name.
if (pathSegments.length === 0 || (pathSegments.length === 1 && pathSegments[0] === "")) {
throw ["Found generics without a path"];
if (generics.length > 0 || prevIs(parserState, ">")) {
throw ["Found generics without a path"];
} else {
throw ["Unexpected ", parserState.userQuery[parserState.pos]];
}
}
for (const [i, pathSegment] of pathSegments.entries()) {
if (pathSegment === "!") {
if (i !== 0) {
throw ["Never type ", "!", " is not associated item"];
}
pathSegments[i] = "never";
}
}
parserState.totalElems += 1;
if (isInGenerics) {
parserState.genericsElems += 1;
}
return {
name: name,
name: name.trim(),
id: -1,
fullPath: pathSegments,
pathWithoutLast: pathSegments.slice(0, pathSegments.length - 1),
@ -482,15 +538,21 @@ function initSearch(rawSearchIndex) {
foundExclamation = parserState.pos;
} else if (isErrorCharacter(c)) {
throw ["Unexpected ", c];
} else if (
isStopCharacter(c) ||
isSpecialStartCharacter(c) ||
isSeparatorCharacter(c)
) {
break;
} else if (c === ":") { // If we allow paths ("str::string" for example).
if (!isPathStart(parserState)) {
break;
} else if (isPathSeparator(c)) {
if (c === ":") {
if (!isPathStart(parserState)) {
break;
}
// Skip current ":".
parserState.pos += 1;
} else {
while (parserState.pos + 1 < parserState.length) {
const next_c = parserState.userQuery[parserState.pos + 1];
if (!isWhitespace(next_c)) {
break;
}
parserState.pos += 1;
}
}
if (foundExclamation !== -1) {
if (foundExclamation !== start &&
@ -503,8 +565,13 @@ function initSearch(rawSearchIndex) {
foundExclamation = -1;
}
}
// Skip current ":".
parserState.pos += 1;
} else if (
c === "[" ||
isStopCharacter(c) ||
isSpecialStartCharacter(c) ||
isSeparatorCharacter(c)
) {
break;
} else {
throw ["Unexpected ", c];
}
@ -542,6 +609,7 @@ function initSearch(rawSearchIndex) {
function getNextElem(query, parserState, elems, isInGenerics) {
const generics = [];
skipWhitespace(parserState);
let start = parserState.pos;
let end;
if (parserState.userQuery[parserState.pos] === "[") {
@ -572,8 +640,9 @@ function initSearch(rawSearchIndex) {
typeFilter: "primitive",
});
} else {
const isStringElem = parserState.userQuery[start] === "\"";
// We handle the strings on their own mostly to make code easier to follow.
if (parserState.userQuery[parserState.pos] === "\"") {
if (isStringElem) {
start += 1;
getStringElem(query, parserState, isInGenerics);
end = parserState.pos - 1;
@ -589,6 +658,9 @@ function initSearch(rawSearchIndex) {
parserState.pos += 1;
getItemsBefore(query, parserState, generics, ">");
}
if (isStringElem) {
skipWhitespace(parserState);
}
if (start >= end && generics.length === 0) {
return;
}
@ -653,7 +725,7 @@ function initSearch(rawSearchIndex) {
if (elems.length === 0) {
throw ["Expected type filter before ", ":"];
} else if (query.literalSearch) {
throw ["You cannot use quotes on type filter"];
throw ["Cannot use quotes on type filter"];
}
// The type filter doesn't count as an element since it's a modifier.
const typeFilterElem = elems.pop();
@ -668,23 +740,27 @@ function initSearch(rawSearchIndex) {
throw ["Unexpected ", c, " after ", extra];
}
if (!foundStopChar) {
let extra = [];
if (isLastElemGeneric(query.elems, parserState)) {
extra = [" after ", ">"];
} else if (prevIs(parserState, "\"")) {
throw ["Cannot have more than one element if you use quotes"];
}
if (endChar !== "") {
throw [
"Expected ",
",", // comma
", ",
"&nbsp;", // whitespace
",",
" or ",
endChar,
...extra,
", found ",
c,
];
}
throw [
"Expected ",
",", // comma
" or ",
"&nbsp;", // whitespace
",",
...extra,
", found ",
c,
];
@ -720,11 +796,17 @@ function initSearch(rawSearchIndex) {
* @param {ParserState} parserState
*/
function checkExtraTypeFilterCharacters(start, parserState) {
const query = parserState.userQuery;
const query = parserState.userQuery.slice(start, parserState.pos).trim();
for (let pos = start; pos < parserState.pos; ++pos) {
if (!isIdentCharacter(query[pos]) && !isWhitespaceCharacter(query[pos])) {
throw ["Unexpected ", query[pos], " in type filter"];
for (const c in query) {
if (!isIdentCharacter(query[c])) {
throw [
"Unexpected ",
query[c],
" in type filter (before ",
":",
")",
];
}
}
}
@ -757,11 +839,10 @@ function initSearch(rawSearchIndex) {
} else if (c === ":" && !isPathStart(parserState)) {
if (parserState.typeFilter !== null) {
throw ["Unexpected ", ":"];
}
if (query.elems.length === 0) {
} else if (query.elems.length === 0) {
throw ["Expected type filter before ", ":"];
} else if (query.literalSearch) {
throw ["You cannot use quotes on type filter"];
throw ["Cannot use quotes on type filter"];
}
// The type filter doesn't count as an element since it's a modifier.
const typeFilterElem = query.elems.pop();
@ -774,27 +855,31 @@ function initSearch(rawSearchIndex) {
continue;
}
if (!foundStopChar) {
let extra = "";
if (isLastElemGeneric(query.elems, parserState)) {
extra = [" after ", ">"];
} else if (prevIs(parserState, "\"")) {
throw ["Cannot have more than one element if you use quotes"];
}
if (parserState.typeFilter !== null) {
throw [
"Expected ",
",", // comma
", ",
"&nbsp;", // whitespace
",",
" or ",
"->", // arrow
"->",
...extra,
", found ",
c,
];
}
throw [
"Expected ",
",", // comma
",",
", ",
"&nbsp;", // whitespace
", ",
":", // colon
":",
" or ",
"->", // arrow
"->",
...extra,
", found ",
c,
];
@ -809,11 +894,18 @@ function initSearch(rawSearchIndex) {
foundStopChar = false;
}
if (parserState.typeFilter !== null) {
throw ["Unexpected ", ":", " (expected path after type filter)"];
throw [
"Unexpected ",
":",
" (expected path after type filter ",
parserState.typeFilter + ":",
")",
];
}
while (parserState.pos < parserState.length) {
if (isReturnArrow(parserState)) {
parserState.pos += 2;
skipWhitespace(parserState);
// Get returned elements.
getItemsBefore(query, parserState, query.returned, "");
// Nothing can come afterward!
@ -888,10 +980,10 @@ function initSearch(rawSearchIndex) {
* The supported syntax by this parser is as follow:
*
* ident = *(ALPHA / DIGIT / "_")
* path = ident *(DOUBLE-COLON ident) [!]
* path = ident *(DOUBLE-COLON/{WS} ident) [!]
* slice = OPEN-SQUARE-BRACKET [ nonempty-arg-list ] CLOSE-SQUARE-BRACKET
* arg = [type-filter *WS COLON *WS] (path [generics] / slice)
* type-sep = COMMA/WS *(COMMA/WS)
* type-sep = COMMA *(COMMA)
* nonempty-arg-list = *(type-sep) arg *(type-sep arg) *(type-sep)
* generics = OPEN-ANGLE-BRACKET [ nonempty-arg-list ] *(type-sep)
* CLOSE-ANGLE-BRACKET