pip-services4-expressions-node
Version:
Tokenizers, parsers and expression calculators in Node.js / ES2017
516 lines • 23.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ExpressionParser = void 0;
/** @module calculator */
const ExpressionTokenType_1 = require("./ExpressionTokenType");
const ExpressionTokenizer_1 = require("../tokenizers/ExpressionTokenizer");
const TokenType_1 = require("../../tokenizers/TokenType");
const ExpressionToken_1 = require("./ExpressionToken");
const SyntaxException_1 = require("../SyntaxException");
const SyntaxErrorCode_1 = require("../SyntaxErrorCode");
const Variant_1 = require("../../variants/Variant");
const pip_services4_commons_node_1 = require("pip-services4-commons-node");
const pip_services4_commons_node_2 = require("pip-services4-commons-node");
/**
* Implements an expression parser class.
*/
class ExpressionParser {
constructor() {
/**
* Defines a list of operators.
*/
this.Operators = [
"(", ")", "[", "]", "+", "-", "*", "/", "%", "^",
"=", "<>", "!=", ">", "<", ">=", "<=", "<<", ">>",
"AND", "OR", "XOR", "NOT", "IS", "IN", "NULL", "LIKE", ","
];
/**
* Defines a list of operator token types.
* Note: it must match to operators.
*/
this.OperatorTypes = [
ExpressionTokenType_1.ExpressionTokenType.LeftBrace, ExpressionTokenType_1.ExpressionTokenType.RightBrace,
ExpressionTokenType_1.ExpressionTokenType.LeftSquareBrace, ExpressionTokenType_1.ExpressionTokenType.RightSquareBrace,
ExpressionTokenType_1.ExpressionTokenType.Plus, ExpressionTokenType_1.ExpressionTokenType.Minus,
ExpressionTokenType_1.ExpressionTokenType.Star, ExpressionTokenType_1.ExpressionTokenType.Slash,
ExpressionTokenType_1.ExpressionTokenType.Procent, ExpressionTokenType_1.ExpressionTokenType.Power,
ExpressionTokenType_1.ExpressionTokenType.Equal, ExpressionTokenType_1.ExpressionTokenType.NotEqual,
ExpressionTokenType_1.ExpressionTokenType.NotEqual, ExpressionTokenType_1.ExpressionTokenType.More,
ExpressionTokenType_1.ExpressionTokenType.Less, ExpressionTokenType_1.ExpressionTokenType.EqualMore,
ExpressionTokenType_1.ExpressionTokenType.EqualLess, ExpressionTokenType_1.ExpressionTokenType.ShiftLeft,
ExpressionTokenType_1.ExpressionTokenType.ShiftRight, ExpressionTokenType_1.ExpressionTokenType.And,
ExpressionTokenType_1.ExpressionTokenType.Or, ExpressionTokenType_1.ExpressionTokenType.Xor,
ExpressionTokenType_1.ExpressionTokenType.Not, ExpressionTokenType_1.ExpressionTokenType.Is,
ExpressionTokenType_1.ExpressionTokenType.In, ExpressionTokenType_1.ExpressionTokenType.Null,
ExpressionTokenType_1.ExpressionTokenType.Like, ExpressionTokenType_1.ExpressionTokenType.Comma
];
this._tokenizer = new ExpressionTokenizer_1.ExpressionTokenizer();
this._expression = "";
this._originalTokens = [];
this._initialTokens = [];
this._variableNames = [];
this._resultTokens = [];
}
/**
* The expression string.
*/
get expression() {
return this._expression;
}
/**
* The expression string.
*/
set expression(value) {
this.parseString(value);
}
get originalTokens() {
return this._originalTokens;
}
set originalTokens(value) {
this.parseTokens(value);
}
/**
* The list of original expression tokens.
*/
get initialTokens() {
return this._initialTokens;
}
/**
* The list of parsed expression tokens.
*/
get resultTokens() {
return this._resultTokens;
}
/**
* The list of found variable names.
*/
get variableNames() {
return this._variableNames;
}
/**
* Sets a new expression string and parses it into internal byte code.
* @param expression A new expression string.
*/
parseString(expression) {
this.clear();
this._expression = expression != null ? expression.trim() : "";
this._originalTokens = this.tokenizeExpression(this._expression);
this.performParsing();
}
parseTokens(tokens) {
this.clear();
this._originalTokens = tokens;
this._expression = this.composeExpression(tokens);
this.performParsing();
}
/**
* Clears parsing results.
*/
clear() {
this._expression = null;
this._originalTokens = [];
this._initialTokens = [];
this._resultTokens = [];
this._currentTokenIndex = 0;
this._variableNames = [];
}
/**
* Checks are there more tokens for processing.
* @returns <code>true</code> if some tokens are present.
*/
hasMoreTokens() {
return this._currentTokenIndex < this._initialTokens.length;
}
/**
* Checks are there more tokens available and throws exception if no more tokens available.
*/
checkForMoreTokens() {
if (!this.hasMoreTokens()) {
throw new SyntaxException_1.SyntaxException(null, SyntaxErrorCode_1.SyntaxErrorCode.UnexpectedEnd, "Unexpected end of expression.", 0, 0);
}
}
/**
* Gets the current token object.
* @returns The current token object.
*/
getCurrentToken() {
return this._currentTokenIndex < this._initialTokens.length
? this._initialTokens[this._currentTokenIndex] : null;
}
/**
* Gets the next token object.
* @returns The next token object.
*/
getNextToken() {
return (this._currentTokenIndex + 1) < this._initialTokens.length
? this._initialTokens[this._currentTokenIndex + 1] : null;
}
/**
* Moves to the next token object.
*/
moveToNextToken() {
this._currentTokenIndex++;
}
/**
* Adds an expression to the result list
* @param type The type of the token to be added.
* @param value The value of the token to be added.
* @param line The line number where the token is.
* @param column The column number where the token is.
*/
addTokenToResult(type, value, line, column) {
this._resultTokens.push(new ExpressionToken_1.ExpressionToken(type, value, line, column));
}
/**
* Matches available tokens types with types from the list.
* If tokens matchs then shift the list.
* @param types A list of token types to compare.
* <code>true</code> if token types match.
*/
matchTokensWithTypes(...types) {
let matches = false;
for (let i = 0; i < types.length; i++) {
if (this._currentTokenIndex + i < this._initialTokens.length) {
matches = this._initialTokens[this._currentTokenIndex + i].type == types[i];
}
else {
matches = false;
break;
}
}
if (matches) {
this._currentTokenIndex += types.length;
}
return matches;
}
tokenizeExpression(expression) {
expression = expression != null ? expression.trim() : "";
if (expression.length > 0) {
this._tokenizer.skipWhitespaces = true;
this._tokenizer.skipComments = true;
this._tokenizer.skipEof = true;
this._tokenizer.decodeStrings = true;
return this._tokenizer.tokenizeBuffer(expression);
}
else {
return [];
}
}
composeExpression(tokens) {
let builder = "";
for (const token of tokens) {
builder = builder + token.value;
}
return builder;
}
performParsing() {
if (this._originalTokens.length > 0) {
this.completeLexicalAnalysis();
this.performSyntaxAnalysis();
if (this.hasMoreTokens()) {
const token = this.getCurrentToken();
throw new SyntaxException_1.SyntaxException(null, SyntaxErrorCode_1.SyntaxErrorCode.ErrorNear, "Syntax error near " + token.value, token.line, token.column);
}
}
}
/**
* Tokenizes the given expression and prepares an initial tokens list.
*/
completeLexicalAnalysis() {
for (const token of this._originalTokens) {
let tokenType = ExpressionTokenType_1.ExpressionTokenType.Unknown;
let tokenValue = Variant_1.Variant.Empty;
switch (token.type) {
case TokenType_1.TokenType.Comment:
case TokenType_1.TokenType.Whitespace:
continue;
case TokenType_1.TokenType.Keyword:
{
const temp = token.value.toUpperCase();
if (temp == "TRUE") {
tokenType = ExpressionTokenType_1.ExpressionTokenType.Constant;
tokenValue = Variant_1.Variant.fromBoolean(true);
}
else if (temp == "FALSE") {
tokenType = ExpressionTokenType_1.ExpressionTokenType.Constant;
tokenValue = Variant_1.Variant.fromBoolean(false);
}
else {
for (let index = 0; index < this.Operators.length; index++) {
if (temp == this.Operators[index]) {
tokenType = this.OperatorTypes[index];
break;
}
}
}
break;
}
case TokenType_1.TokenType.Word:
{
tokenType = ExpressionTokenType_1.ExpressionTokenType.Variable;
tokenValue = Variant_1.Variant.fromString(token.value);
break;
}
case TokenType_1.TokenType.Integer:
{
tokenType = ExpressionTokenType_1.ExpressionTokenType.Constant;
tokenValue = Variant_1.Variant.fromInteger(pip_services4_commons_node_1.IntegerConverter.toInteger(token.value));
break;
}
case TokenType_1.TokenType.Float:
{
tokenType = ExpressionTokenType_1.ExpressionTokenType.Constant;
tokenValue = Variant_1.Variant.fromFloat(pip_services4_commons_node_2.FloatConverter.toFloat(token.value));
break;
}
case TokenType_1.TokenType.Quoted:
{
tokenType = ExpressionTokenType_1.ExpressionTokenType.Constant;
tokenValue = Variant_1.Variant.fromString(token.value);
break;
}
case TokenType_1.TokenType.Symbol:
{
const temp = token.value.toUpperCase();
for (let i = 0; i < this.Operators.length; i++) {
if (temp == this.Operators[i]) {
tokenType = this.OperatorTypes[i];
break;
}
}
break;
}
}
if (tokenType == ExpressionTokenType_1.ExpressionTokenType.Unknown) {
throw new SyntaxException_1.SyntaxException(null, SyntaxErrorCode_1.SyntaxErrorCode.UnknownSymbol, "Unknown symbol " + token.value, token.line, token.column);
}
this._initialTokens.push(new ExpressionToken_1.ExpressionToken(tokenType, tokenValue, token.line, token.column));
}
}
/**
* Performs a syntax analysis at level 0.
*/
performSyntaxAnalysis() {
this.checkForMoreTokens();
this.performSyntaxAnalysisAtLevel1();
while (this.hasMoreTokens()) {
const token = this.getCurrentToken();
if (token.type == ExpressionTokenType_1.ExpressionTokenType.And
|| token.type == ExpressionTokenType_1.ExpressionTokenType.Or
|| token.type == ExpressionTokenType_1.ExpressionTokenType.Xor) {
this.moveToNextToken();
this.performSyntaxAnalysisAtLevel1();
this.addTokenToResult(token.type, Variant_1.Variant.Empty, token.line, token.column);
continue;
}
break;
}
}
/**
* Performs a syntax analysis at level 1.
*/
performSyntaxAnalysisAtLevel1() {
this.checkForMoreTokens();
const token = this.getCurrentToken();
if (token.type == ExpressionTokenType_1.ExpressionTokenType.Not) {
this.moveToNextToken();
this.performSyntaxAnalysisAtLevel2();
this.addTokenToResult(token.type, Variant_1.Variant.Empty, token.line, token.column);
}
else {
this.performSyntaxAnalysisAtLevel2();
}
}
/**
* Performs a syntax analysis at level 2.
*/
performSyntaxAnalysisAtLevel2() {
this.checkForMoreTokens();
this.performSyntaxAnalysisAtLevel3();
while (this.hasMoreTokens()) {
const token = this.getCurrentToken();
if (token.type == ExpressionTokenType_1.ExpressionTokenType.Equal
|| token.type == ExpressionTokenType_1.ExpressionTokenType.NotEqual
|| token.type == ExpressionTokenType_1.ExpressionTokenType.More
|| token.type == ExpressionTokenType_1.ExpressionTokenType.Less
|| token.type == ExpressionTokenType_1.ExpressionTokenType.EqualMore
|| token.type == ExpressionTokenType_1.ExpressionTokenType.EqualLess) {
this.moveToNextToken();
this.performSyntaxAnalysisAtLevel3();
this.addTokenToResult(token.type, Variant_1.Variant.Empty, token.line, token.column);
continue;
}
break;
}
}
/**
* Performs a syntax analysis at level 3.
*/
performSyntaxAnalysisAtLevel3() {
this.checkForMoreTokens();
this.performSyntaxAnalysisAtLevel4();
while (this.hasMoreTokens()) {
const token = this.getCurrentToken();
if (token.type == ExpressionTokenType_1.ExpressionTokenType.Plus
|| token.type == ExpressionTokenType_1.ExpressionTokenType.Minus
|| token.type == ExpressionTokenType_1.ExpressionTokenType.Like) {
this.moveToNextToken();
this.performSyntaxAnalysisAtLevel4();
this.addTokenToResult(token.type, Variant_1.Variant.Empty, token.line, token.column);
}
else if (this.matchTokensWithTypes(ExpressionTokenType_1.ExpressionTokenType.Not, ExpressionTokenType_1.ExpressionTokenType.Like)) {
this.performSyntaxAnalysisAtLevel4();
this.addTokenToResult(ExpressionTokenType_1.ExpressionTokenType.NotLike, Variant_1.Variant.Empty, token.line, token.column);
}
else if (this.matchTokensWithTypes(ExpressionTokenType_1.ExpressionTokenType.Is, ExpressionTokenType_1.ExpressionTokenType.Null)) {
this.addTokenToResult(ExpressionTokenType_1.ExpressionTokenType.IsNull, Variant_1.Variant.Empty, token.line, token.column);
}
else if (this.matchTokensWithTypes(ExpressionTokenType_1.ExpressionTokenType.Is, ExpressionTokenType_1.ExpressionTokenType.Not, ExpressionTokenType_1.ExpressionTokenType.Null)) {
this.addTokenToResult(ExpressionTokenType_1.ExpressionTokenType.IsNotNull, Variant_1.Variant.Empty, token.line, token.column);
}
else if (this.matchTokensWithTypes(ExpressionTokenType_1.ExpressionTokenType.Not, ExpressionTokenType_1.ExpressionTokenType.In)) {
this.performSyntaxAnalysisAtLevel4();
this.addTokenToResult(ExpressionTokenType_1.ExpressionTokenType.NotIn, Variant_1.Variant.Empty, token.line, token.column);
}
else {
break;
}
}
}
/**
* Performs a syntax analysis at level 4.
*/
performSyntaxAnalysisAtLevel4() {
this.checkForMoreTokens();
this.performSyntaxAnalysisAtLevel5();
while (this.hasMoreTokens()) {
const token = this.getCurrentToken();
if (token.type == ExpressionTokenType_1.ExpressionTokenType.Star
|| token.type == ExpressionTokenType_1.ExpressionTokenType.Slash
|| token.type == ExpressionTokenType_1.ExpressionTokenType.Procent) {
this.moveToNextToken();
this.performSyntaxAnalysisAtLevel5();
this.addTokenToResult(token.type, Variant_1.Variant.Empty, token.line, token.column);
continue;
}
break;
}
}
/**
* Performs a syntax analysis at level 5.
*/
performSyntaxAnalysisAtLevel5() {
this.checkForMoreTokens();
this.performSyntaxAnalysisAtLevel6();
while (this.hasMoreTokens()) {
const token = this.getCurrentToken();
if (token.type == ExpressionTokenType_1.ExpressionTokenType.Power
|| token.type == ExpressionTokenType_1.ExpressionTokenType.In
|| token.type == ExpressionTokenType_1.ExpressionTokenType.ShiftLeft
|| token.type == ExpressionTokenType_1.ExpressionTokenType.ShiftRight) {
this.moveToNextToken();
this.performSyntaxAnalysisAtLevel6();
this.addTokenToResult(token.type, Variant_1.Variant.Empty, token.line, token.column);
continue;
}
break;
}
}
/**
* Performs a syntax analysis at level 6.
*/
performSyntaxAnalysisAtLevel6() {
this.checkForMoreTokens();
// Process unary '+' or '-'.
let unaryToken = this.getCurrentToken();
if (unaryToken.type == ExpressionTokenType_1.ExpressionTokenType.Plus) {
unaryToken = null;
this.moveToNextToken();
}
else if (unaryToken.type == ExpressionTokenType_1.ExpressionTokenType.Minus) {
unaryToken = new ExpressionToken_1.ExpressionToken(ExpressionTokenType_1.ExpressionTokenType.Unary, unaryToken.value, unaryToken.line, unaryToken.line);
this.moveToNextToken();
}
else {
unaryToken = null;
}
this.checkForMoreTokens();
// Identify function calls.
let primitiveToken = this.getCurrentToken();
const nextToken = this.getNextToken();
if (primitiveToken.type == ExpressionTokenType_1.ExpressionTokenType.Variable
&& nextToken != null && nextToken.type == ExpressionTokenType_1.ExpressionTokenType.LeftBrace) {
primitiveToken = new ExpressionToken_1.ExpressionToken(ExpressionTokenType_1.ExpressionTokenType.Function, primitiveToken.value, primitiveToken.line, primitiveToken.column);
}
if (primitiveToken.type == ExpressionTokenType_1.ExpressionTokenType.Constant) {
this.moveToNextToken();
this.addTokenToResult(primitiveToken.type, primitiveToken.value, primitiveToken.line, primitiveToken.column);
}
else if (primitiveToken.type == ExpressionTokenType_1.ExpressionTokenType.Variable) {
this.moveToNextToken();
const temp = primitiveToken.value.asString;
if (this._variableNames.indexOf(temp) < 0) {
this._variableNames.push(temp);
}
this.addTokenToResult(primitiveToken.type, primitiveToken.value, primitiveToken.line, primitiveToken.column);
}
else if (primitiveToken.type == ExpressionTokenType_1.ExpressionTokenType.LeftBrace) {
this.moveToNextToken();
this.performSyntaxAnalysis();
this.checkForMoreTokens();
primitiveToken = this.getCurrentToken();
if (primitiveToken.type != ExpressionTokenType_1.ExpressionTokenType.RightBrace) {
throw new SyntaxException_1.SyntaxException(null, SyntaxErrorCode_1.SyntaxErrorCode.MissedCloseParenthesis, "Expected ')' was not found", primitiveToken.line, primitiveToken.column);
}
this.moveToNextToken();
}
else if (primitiveToken.type == ExpressionTokenType_1.ExpressionTokenType.Function) {
this.moveToNextToken();
let token = this.getCurrentToken();
if (token.type != ExpressionTokenType_1.ExpressionTokenType.LeftBrace) {
throw new SyntaxException_1.SyntaxException(null, SyntaxErrorCode_1.SyntaxErrorCode.Internal, "Internal error", token.line, token.column);
}
let paramCount = 0;
do {
this.moveToNextToken();
token = this.getCurrentToken();
if (token == null || token.type == ExpressionTokenType_1.ExpressionTokenType.RightBrace) {
break;
}
paramCount++;
this.performSyntaxAnalysis();
token = this.getCurrentToken();
} while (token != null && token.type == ExpressionTokenType_1.ExpressionTokenType.Comma);
this.checkForMoreTokens();
if (token.type != ExpressionTokenType_1.ExpressionTokenType.RightBrace) {
throw new SyntaxException_1.SyntaxException(null, SyntaxErrorCode_1.SyntaxErrorCode.MissedCloseParenthesis, "Expected ')' was not found", token.line, token.column);
}
this.moveToNextToken();
this.addTokenToResult(ExpressionTokenType_1.ExpressionTokenType.Constant, new Variant_1.Variant(paramCount), primitiveToken.line, primitiveToken.column);
this.addTokenToResult(primitiveToken.type, primitiveToken.value, primitiveToken.line, primitiveToken.column);
}
else {
throw new SyntaxException_1.SyntaxException(null, SyntaxErrorCode_1.SyntaxErrorCode.ErrorAt, "Syntax error at " + primitiveToken.value, primitiveToken.line, primitiveToken.column);
}
if (unaryToken != null) {
this.addTokenToResult(unaryToken.type, Variant_1.Variant.Empty, unaryToken.line, unaryToken.column);
}
// Process [] operator.
if (this.hasMoreTokens()) {
primitiveToken = this.getCurrentToken();
if (primitiveToken.type == ExpressionTokenType_1.ExpressionTokenType.LeftSquareBrace) {
this.moveToNextToken();
this.performSyntaxAnalysis();
this.checkForMoreTokens();
primitiveToken = this.getCurrentToken();
if (primitiveToken.type != ExpressionTokenType_1.ExpressionTokenType.RightSquareBrace) {
throw new SyntaxException_1.SyntaxException(null, SyntaxErrorCode_1.SyntaxErrorCode.MissedCloseSquareBracket, "Expected ']' was not found", primitiveToken.line, primitiveToken.column);
}
this.moveToNextToken();
this.addTokenToResult(ExpressionTokenType_1.ExpressionTokenType.Element, Variant_1.Variant.Empty, primitiveToken.line, primitiveToken.column);
}
}
}
}
exports.ExpressionParser = ExpressionParser;
//# sourceMappingURL=ExpressionParser.js.map