shortcut negation syntax

This commit is contained in:
zadam 2020-07-19 15:25:24 +02:00
parent df69b1d8dd
commit 4c7b1d6543
4 changed files with 65 additions and 17 deletions

View File

@ -64,6 +64,11 @@ describe("Lexer expression", () => {
.toEqual(["#", "~author", ".", "title", "=", "hugh howey", "and", "note", ".", "book title", "=", "silo"]); .toEqual(["#", "~author", ".", "title", "=", "hugh howey", "and", "note", ".", "book title", "=", "silo"]);
}); });
it("negation of label and relation", () => {
expect(lexer(`#!capital ~!neighbor`).expressionTokens)
.toEqual(["#!capital", "~!neighbor"]);
});
it("negation of sub-expression", () => { it("negation of sub-expression", () => {
expect(lexer(`# not(#capital) and note.noteId != "root"`).expressionTokens) expect(lexer(`# not(#capital) and note.noteId != "root"`).expressionTokens)
.toEqual(["#", "not", "(", "#capital", ")", "and", "note", ".", "noteid", "!=", "root"]); .toEqual(["#", "not", "(", "#capital", ")", "and", "note", ".", "noteid", "!=", "root"]);

View File

@ -21,13 +21,16 @@ describe("Parser", () => {
}); });
expect(rootExp.constructor.name).toEqual("OrExp"); expect(rootExp.constructor.name).toEqual("OrExp");
const [firstSub, secondSub] = rootExp.subExpressions; const subs = rootExp.subExpressions;
expect(firstSub.constructor.name).toEqual("NoteCacheFulltextExp"); expect(subs[0].constructor.name).toEqual("NoteCacheFulltextExp");
expect(firstSub.tokens).toEqual(["hello", "hi"]); expect(subs[0].tokens).toEqual(["hello", "hi"]);
expect(secondSub.constructor.name).toEqual("NoteContentFulltextExp"); expect(subs[1].constructor.name).toEqual("NoteContentProtectedFulltextExp");
expect(secondSub.tokens).toEqual(["hello", "hi"]); expect(subs[1].tokens).toEqual(["hello", "hi"]);
expect(subs[2].constructor.name).toEqual("NoteContentUnprotectedFulltextExp");
expect(subs[2].tokens).toEqual(["hello", "hi"]);
}); });
it("simple label comparison", () => { it("simple label comparison", () => {
@ -43,6 +46,30 @@ describe("Parser", () => {
expect(rootExp.comparator).toBeTruthy(); expect(rootExp.comparator).toBeTruthy();
}); });
it("simple attribute negation", () => {
let rootExp = parser({
fulltextTokens: [],
expressionTokens: ["#!mylabel"],
parsingContext: new ParsingContext()
});
expect(rootExp.constructor.name).toEqual("NotExp");
expect(rootExp.subExpression.constructor.name).toEqual("AttributeExistsExp");
expect(rootExp.subExpression.attributeType).toEqual("label");
expect(rootExp.subExpression.attributeName).toEqual("mylabel");
rootExp = parser({
fulltextTokens: [],
expressionTokens: ["~!myrelation"],
parsingContext: new ParsingContext()
});
expect(rootExp.constructor.name).toEqual("NotExp");
expect(rootExp.subExpression.constructor.name).toEqual("AttributeExistsExp");
expect(rootExp.subExpression.attributeType).toEqual("relation");
expect(rootExp.subExpression.attributeName).toEqual("myrelation");
});
it("simple label AND", () => { it("simple label AND", () => {
const rootExp = parser({ const rootExp = parser({
fulltextTokens: [], fulltextTokens: [],

View File

@ -83,6 +83,10 @@ function lexer(str) {
continue; continue;
} }
else if (['#', '~'].includes(currentWord) && chr === '!') {
currentWord += chr;
continue;
}
else if (chr === ' ') { else if (chr === ' ') {
finishWord(); finishWord();
continue; continue;
@ -93,7 +97,10 @@ function lexer(str) {
finishWord(); finishWord();
continue; continue;
} }
else if (fulltextEnded && previousOperatorSymbol() !== isOperatorSymbol(chr)) { else if (fulltextEnded
&& !['#!', '~!'].includes(currentWord)
&& previousOperatorSymbol() !== isOperatorSymbol(chr)) {
finishWord(); finishWord();
currentWord += chr; currentWord += chr;

View File

@ -70,8 +70,8 @@ function getExpression(tokens, parsingContext, level = 0) {
i++; i++;
return new OrExp([ return new OrExp([
NoteContentUnprotectedFulltextExp(operator, [tokens[i]]), new NoteContentUnprotectedFulltextExp(operator, [tokens[i]]),
NoteContentProtectedFulltextExp(operator, [tokens[i]]) new NoteContentProtectedFulltextExp(operator, [tokens[i]])
]); ]);
} }
@ -134,6 +134,22 @@ function getExpression(tokens, parsingContext, level = 0) {
parsingContext.addError(`Unrecognized note property "${tokens[i]}"`); parsingContext.addError(`Unrecognized note property "${tokens[i]}"`);
} }
function parseAttribute(name) {
const isLabel = name.startsWith('#');
name = name.substr(1);
const isNegated = name.startsWith('!');
if (isNegated) {
name = name.substr(1);
}
const subExp = isLabel ? parseLabel(name) : parseRelation(name);
return isNegated ? new NotExp(subExp) : subExp;
}
function parseLabel(labelName) { function parseLabel(labelName) {
parsingContext.highlightedTokens.push(labelName); parsingContext.highlightedTokens.push(labelName);
@ -234,15 +250,8 @@ function getExpression(tokens, parsingContext, level = 0) {
if (Array.isArray(token)) { if (Array.isArray(token)) {
expressions.push(getExpression(token, parsingContext, level++)); expressions.push(getExpression(token, parsingContext, level++));
} }
else if (token.startsWith('#')) { else if (token.startsWith('#') || token.startsWith('~')) {
const labelName = token.substr(1); expressions.push(parseAttribute(token));
expressions.push(parseLabel(labelName));
}
else if (token.startsWith('~')) {
const relationName = token.substr(1);
expressions.push(parseRelation(relationName));
} }
else if (['orderby', 'limit'].includes(token)) { else if (['orderby', 'limit'].includes(token)) {
if (level !== 0) { if (level !== 0) {