@sap/odata-v4
Version:
OData V4.0 server library
118 lines (108 loc) • 4.29 kB
JavaScript
;
const TokenKind = require('./UriTokenizer').TokenKind;
const BinaryExpression = require('./BinaryExpression');
const BinaryOperatorKind = BinaryExpression.OperatorKind;
const UnaryExpression = require('./UnaryExpression');
const UnaryOperatorKind = UnaryExpression.OperatorKind;
const LiteralExpression = require('./LiteralExpression');
const EdmPrimitiveTypeKind = require('../edm/EdmPrimitiveTypeKind');
const UriSyntaxError = require('../errors/UriSyntaxError');
/**
* Parses search expressions according to the following (rewritten) grammar:
* <pre>
* SearchExpression ::= OrExpression
* OrExpression ::= AndExpression ('OR' AndExpression)*
* AndExpression ::= Term ('AND'? Term)*
* Term ::= ('NOT'? (Word | Phrase)) | ('(' SearchExpression ')')
* </pre>
*/
class SearchParser {
/**
* Parse search expression.
* @param {UriTokenizer} tokenizer tokenizer
* @returns {Expression} the root of the search-expression tree
*/
parse(tokenizer) {
return this._parseOrExpression(tokenizer);
}
/**
* Parse search expression with zero or more 'OR' operators.
* @param {UriTokenizer} tokenizer tokenizer
* @returns {Expression} expression
* @private
*/
_parseOrExpression(tokenizer) {
let left = this._parseAndExpression(tokenizer);
while (tokenizer.next(TokenKind.OrOperatorSearch)) {
let right = this._parseAndExpression(tokenizer);
left = new BinaryExpression(left, BinaryOperatorKind.OR, right, EdmPrimitiveTypeKind.Boolean);
}
return left;
}
/**
* Parse search expression with zero or more 'AND' operators.
* @param {UriTokenizer} tokenizer tokenizer
* @returns {Expression} expression
* @private
*/
_parseAndExpression(tokenizer) {
let left = this._parseTerm(tokenizer);
while (tokenizer.next(TokenKind.AndOperatorSearch)) {
// Could be whitespace or whitespace-surrounded 'AND'.
let right = this._parseTerm(tokenizer);
left = new BinaryExpression(left, BinaryOperatorKind.AND, right, EdmPrimitiveTypeKind.Boolean);
}
return left;
}
/**
* Parse search term.
* @param {UriTokenizer} tokenizer tokenizer
* @returns {Expression} expression
* @private
*/
_parseTerm(tokenizer) {
if (tokenizer.next(TokenKind.OPEN)) {
let expression = this._parseOrExpression(tokenizer);
tokenizer.requireNext(TokenKind.CLOSE);
return expression;
} else if (tokenizer.next(TokenKind.NotOperatorSearch)) {
return this._parseNot(tokenizer);
} else if (tokenizer.next(TokenKind.Word)) {
return new LiteralExpression(tokenizer.getText(), undefined);
} else if (tokenizer.next(TokenKind.Phrase)) {
return this._parsePhrase(tokenizer);
}
throw new UriSyntaxError(UriSyntaxError.Message.PHRASE_OR_WORD_EXPECTED,
tokenizer.getParseString(), tokenizer.getPosition());
}
/**
* Parse search expression after the 'NOT' operator.
* @param {UriTokenizer} tokenizer tokenizer
* @returns {UnaryExpression} unary expression with operator 'NOT'
* @private
*/
_parseNot(tokenizer) {
if (tokenizer.next(TokenKind.Word)) {
return new UnaryExpression(UnaryOperatorKind.NOT, new LiteralExpression(tokenizer.getText(), undefined));
} else if (tokenizer.next(TokenKind.Phrase)) {
return new UnaryExpression(UnaryOperatorKind.NOT, this._parsePhrase(tokenizer));
}
throw new UriSyntaxError(UriSyntaxError.Message.SEARCH_NOT_MUST_BE_FOLLOWED_BY_A_TERM,
tokenizer.getParseString(), tokenizer.getPosition());
}
/**
* Parse search phrase.
* @param {UriTokenizer} tokenizer tokenizer
* @returns {LiteralExpression} literal expression
* @private
*/
_parsePhrase(tokenizer) {
const literal = tokenizer.getText();
return new LiteralExpression(
literal.substring(1, literal.length - 1)
.replace('\\"', '"')
.replace('\\\\', '\\'),
undefined);
}
}
module.exports = SearchParser;