UNPKG

azurite

Version:

An open source Azure Storage API compatible server

154 lines 6.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.QueryLexer = void 0; /** * This is the lexer responsible for converting a query string into a stream of tokens. * These tokens are logical chunks of the query which can then be consumed by the parser, * while not technically necessary, this does help simplify the parsing logic (allowing it * to operate on the higher-level tokens directly). */ class QueryLexer { constructor(query) { this.query = query; this.tokenPosition = 0; /** * The list of comparison operators which are supported by the query syntax. */ this.comparisonOperators = [ "eq", "ne", "gt", "ge", "lt", "le", ]; /** * The list of unary operators which are supported by the query syntax. */ this.unaryOperators = [ "not", ]; /** * The list of logic operators which are supported by the query syntax. */ this.logicOperators = [ "and", "or", ]; /** * The list of type hints which are supported by the query syntax. * Type hints are used to explicitly specify the type of a value * when it cannot be inferred from the value itself, such as a * GUID or a binary data (which are represented as strings). * * Type hints are specified by prefixing the value with the hint * e.g. `guid'00000000-0000-0000-0000-000000000000'`. */ this.typeHints = [ "datetime", "guid", "binary", "x" ]; } next(predicate) { const token = this.peek(); if (!predicate || predicate(token)) { this.tokenPosition = token.position + token.length; return token; } else { return null; } } /** * Gets the next token in the query string without advancing the token stream. * * @returns {QueryToken} */ peek() { // We ignore whitespace (tabs, spaces, newlines etc) in the lead-up to a token this.skipWhitespace(); // If we're at the end of the query, then return an end-of-query token if (!this.query[this.tokenPosition]) { return { kind: "end-of-query", position: this.tokenPosition, length: 0 }; } // We have a few special control characters which are composed of single tokens, // namely the parentheses "()" which are used to group expressions, and the quotes // ("') which are used to delimit strings. switch (this.query[this.tokenPosition]) { case "(": return { kind: "open-paren", position: this.tokenPosition, length: 1 }; case ")": return { kind: "close-paren", position: this.tokenPosition, length: 1 }; case '"': case "'": return this.peekString(); default: // If we encounter any other character, it is safe for us to proceed to the next token. break; } // If we get to this point, we're looking for either an operator, an identifier, a number, or a type hint. // All of these cases are delimited by the above control characters, or by whitespace/end of query. return this.peekWord(); } peekString() { const start = this.tokenPosition; const openCharacter = this.query[start]; let position = start + 1; while (this.query[position]) { if (this.query[position] === openCharacter && this.query[position + 1] === openCharacter) { position += 2; } else if (this.query[position] === openCharacter) { position++; break; } else { position++; } } return { kind: "string", position: start, length: position - start, value: this.query.substring(start + 1, position - 1).replace(new RegExp(`${openCharacter}${openCharacter}`, 'g'), openCharacter) }; } peekWord() { const controlCharacters = `()"'`; const start = this.tokenPosition; let position = this.tokenPosition; while (this.query[position]?.trim() && !controlCharacters.includes(this.query[position])) { position++; } // At this point we've got the delimited token, but we need to determine whether it's one of // our special keywords or an identifier. We can do this by checking against a list of known const value = this.query.substring(start, position); if (this.logicOperators.includes(value.toLowerCase())) { return { kind: "logic-operator", position: start, length: value.length, value }; } if (this.unaryOperators.includes(value.toLowerCase())) { return { kind: "unary-operator", position: start, length: value.length, value }; } if (this.comparisonOperators.includes(value.toLowerCase())) { return { kind: "comparison-operator", position: start, length: value.length, value }; } if (value.toLowerCase() === "true" || value.toLowerCase() === "false") { return { kind: "bool", position: start, length: value.length, value }; } if (this.typeHints.includes(value.toLowerCase()) && `'"`.includes(this.query[position])) { return { kind: "type-hint", position: start, length: value.length, value }; } if (value.match(/^-?[0-9]+(\.[0-9]+)?L?$/)) { return { kind: "number", position: start, length: value.length, value }; } return { kind: "identifier", position: start, length: value.length, value }; } skipWhitespace() { while (this.query[this.tokenPosition] && !this.query[this.tokenPosition].trim()) { this.tokenPosition++; } } } exports.QueryLexer = QueryLexer; //# sourceMappingURL=QueryLexer.js.map