pip-services4-expressions-node
Version:
Tokenizers, parsers and expression calculators in Node.js / ES2017
343 lines • 15.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MustacheParser = void 0;
/** @module calculator */
const MustacheTokenType_1 = require("./MustacheTokenType");
const MustacheTokenizer_1 = require("../tokenizers/MustacheTokenizer");
const TokenType_1 = require("../../tokenizers/TokenType");
const MustacheToken_1 = require("./MustacheToken");
const MustacheException_1 = require("../MustacheException");
const MustacheErrorCode_1 = require("./MustacheErrorCode");
const MustacheLexicalState_1 = require("./MustacheLexicalState");
/**
* Implements an mustache parser class.
*/
class MustacheParser {
constructor() {
this._tokenizer = new MustacheTokenizer_1.MustacheTokenizer();
this._template = "";
this._originalTokens = [];
this._initialTokens = [];
this._variableNames = [];
this._resultTokens = [];
}
/**
* The mustache template.
*/
get template() {
return this._template;
}
/**
* The mustache template.
*/
set template(value) {
this.parseString(value);
}
get originalTokens() {
return this._originalTokens;
}
set originalTokens(value) {
this.parseTokens(value);
}
/**
* The list of original mustache tokens.
*/
get initialTokens() {
return this._initialTokens;
}
/**
* The list of parsed mustache tokens.
*/
get resultTokens() {
return this._resultTokens;
}
/**
* The list of found variable names.
*/
get variableNames() {
return this._variableNames;
}
/**
* Sets a new mustache string and parses it into internal byte code.
* @param mustache A new mustache string.
*/
parseString(mustache) {
this.clear();
this._template = mustache != null ? mustache.trim() : "";
this._originalTokens = this.tokenizeMustache(this._template);
this.performParsing();
}
parseTokens(tokens) {
this.clear();
this._originalTokens = tokens;
this._template = this.composeMustache(tokens);
this.performParsing();
}
/**
* Clears parsing results.
*/
clear() {
this._template = 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 MustacheException_1.MustacheException(null, MustacheErrorCode_1.MustacheErrorCode.UnexpectedEnd, "Unexpected end of mustache", 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 mustache 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 where the token is.
* @param column The column number where the token is.
*/
addTokenToResult(type, value, line, column) {
const token = new MustacheToken_1.MustacheToken(type, value, line, column);
this._resultTokens.push(token);
return token;
}
tokenizeMustache(mustache) {
mustache = mustache != null ? mustache.trim() : "";
if (mustache.length > 0) {
this._tokenizer.skipWhitespaces = true;
this._tokenizer.skipComments = true;
this._tokenizer.skipEof = true;
this._tokenizer.decodeStrings = true;
return this._tokenizer.tokenizeBuffer(mustache);
}
else {
return [];
}
}
composeMustache(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 MustacheException_1.MustacheException(null, MustacheErrorCode_1.MustacheErrorCode.ErrorNear, "Syntax error near " + token.value, token.line, token.column);
}
this.lookupVariables();
}
}
/**
* Tokenizes the given mustache and prepares an initial tokens list.
*/
completeLexicalAnalysis() {
let state = MustacheLexicalState_1.MustacheLexicalState.Value;
let closingBracket = null;
let operator1 = null;
let operator2 = null;
let variable = null;
for (const token of this._originalTokens) {
let tokenType = MustacheTokenType_1.MustacheTokenType.Unknown;
let tokenValue = null;
if (state == MustacheLexicalState_1.MustacheLexicalState.Comment) {
if (token.value == "}}" || token.value == "}}}") {
state = MustacheLexicalState_1.MustacheLexicalState.Closure;
}
else {
continue;
}
}
switch (token.type) {
case TokenType_1.TokenType.Special:
if (state == MustacheLexicalState_1.MustacheLexicalState.Value) {
tokenType = MustacheTokenType_1.MustacheTokenType.Value;
tokenValue = token.value;
}
break;
case TokenType_1.TokenType.Symbol:
if (state == MustacheLexicalState_1.MustacheLexicalState.Value && (token.value == "{{" || token.value == "{{{")) {
closingBracket = token.value == "{{" ? "}}" : "}}}";
state = MustacheLexicalState_1.MustacheLexicalState.Operator1;
continue;
}
if (state == MustacheLexicalState_1.MustacheLexicalState.Operator1 && token.value == "!") {
operator1 = token.value;
state = MustacheLexicalState_1.MustacheLexicalState.Comment;
continue;
}
if (state == MustacheLexicalState_1.MustacheLexicalState.Operator1 && (token.value == "/" || token.value == "#" || token.value == "^")) {
operator1 = token.value;
state = MustacheLexicalState_1.MustacheLexicalState.Operator2;
continue;
}
if (state == MustacheLexicalState_1.MustacheLexicalState.Variable && (token.value == "}}" || token.value == "}}}")) {
if (operator1 != "/") {
variable = operator2;
operator2 = null;
}
state = MustacheLexicalState_1.MustacheLexicalState.Closure;
// Pass through
}
if (state == MustacheLexicalState_1.MustacheLexicalState.Closure && (token.value == "}}" || token.value == "}}}")) {
if (closingBracket != token.value) {
throw new MustacheException_1.MustacheException(null, MustacheErrorCode_1.MustacheErrorCode.MismatchedBrackets, "Mismatched brackets. Expected '" + closingBracket + "'", token.line, token.column);
}
if (operator1 == "#" && (operator2 == null || operator2 == "if")) {
tokenType = MustacheTokenType_1.MustacheTokenType.Section;
tokenValue = variable;
}
if (operator1 == "#" && operator2 == "unless") {
tokenType = MustacheTokenType_1.MustacheTokenType.InvertedSection;
tokenValue = variable;
}
if (operator1 == "^" && operator2 == null) {
tokenType = MustacheTokenType_1.MustacheTokenType.InvertedSection;
tokenValue = variable;
}
if (operator1 == "/") {
tokenType = MustacheTokenType_1.MustacheTokenType.SectionEnd;
tokenValue = variable;
}
if (operator1 == null) {
tokenType = closingBracket == "}}" ? MustacheTokenType_1.MustacheTokenType.Variable : MustacheTokenType_1.MustacheTokenType.EscapedVariable;
tokenValue = variable;
}
if (tokenType == MustacheTokenType_1.MustacheTokenType.Unknown) {
throw new MustacheException_1.MustacheException(null, MustacheErrorCode_1.MustacheErrorCode.Internal, "Internal error", token.line, token.column);
}
operator1 = null;
operator2 = null;
variable = null;
state = MustacheLexicalState_1.MustacheLexicalState.Value;
}
break;
case TokenType_1.TokenType.Word:
if (state == MustacheLexicalState_1.MustacheLexicalState.Operator1) {
state = MustacheLexicalState_1.MustacheLexicalState.Variable;
}
if (state == MustacheLexicalState_1.MustacheLexicalState.Operator2 && (token.value == "if" || token.value == "unless")) {
operator2 = token.value;
state = MustacheLexicalState_1.MustacheLexicalState.Variable;
continue;
}
if (state == MustacheLexicalState_1.MustacheLexicalState.Operator2) {
state = MustacheLexicalState_1.MustacheLexicalState.Variable;
}
if (state == MustacheLexicalState_1.MustacheLexicalState.Variable) {
variable = token.value;
state = MustacheLexicalState_1.MustacheLexicalState.Closure;
continue;
}
break;
case TokenType_1.TokenType.Whitespace:
continue;
}
if (tokenType == MustacheTokenType_1.MustacheTokenType.Unknown) {
throw new MustacheException_1.MustacheException(null, MustacheErrorCode_1.MustacheErrorCode.UnexpectedSymbol, "Unexpected symbol '" + token.value + "'", token.line, token.column);
}
this._initialTokens.push(new MustacheToken_1.MustacheToken(tokenType, tokenValue, token.line, token.column));
}
if (state != MustacheLexicalState_1.MustacheLexicalState.Value) {
throw new MustacheException_1.MustacheException(null, MustacheErrorCode_1.MustacheErrorCode.UnexpectedEnd, "Unexpected end of file", 0, 0);
}
}
/**
* Performs a syntax analysis at level 0.
*/
performSyntaxAnalysis() {
this.checkForMoreTokens();
while (this.hasMoreTokens()) {
const token = this.getCurrentToken();
this.moveToNextToken();
if (token.type == MustacheTokenType_1.MustacheTokenType.SectionEnd) {
throw new MustacheException_1.MustacheException(null, MustacheErrorCode_1.MustacheErrorCode.UnexpectedSectionEnd, "Unexpected section end for variable '" + token.value + "'", token.line, token.column);
}
const result = this.addTokenToResult(token.type, token.value, token.line, token.column);
if (token.type == MustacheTokenType_1.MustacheTokenType.Section || token.type == MustacheTokenType_1.MustacheTokenType.InvertedSection) {
result.tokens.push(...this.performSyntaxAnalysisForSection(token.value));
}
}
}
/**
* Performs a syntax analysis for section
*/
performSyntaxAnalysisForSection(variable) {
const result = [];
this.checkForMoreTokens();
while (this.hasMoreTokens()) {
const token = this.getCurrentToken();
this.moveToNextToken();
if (token.type == MustacheTokenType_1.MustacheTokenType.SectionEnd && (token.value == variable || token.value == null)) {
return result;
}
if (token.type == MustacheTokenType_1.MustacheTokenType.SectionEnd) {
throw new MustacheException_1.MustacheException(null, MustacheErrorCode_1.MustacheErrorCode.UnexpectedSectionEnd, "Unexpected section end for variable '" + variable + "'", token.line, token.column);
}
const resultToken = new MustacheToken_1.MustacheToken(token.type, token.value, token.line, token.column);
if (token.type == MustacheTokenType_1.MustacheTokenType.Section || token.type == MustacheTokenType_1.MustacheTokenType.InvertedSection) {
resultToken.tokens.push(...this.performSyntaxAnalysisForSection(token.value));
}
result.push(resultToken);
}
const token = this.getCurrentToken();
throw new MustacheException_1.MustacheException(null, MustacheErrorCode_1.MustacheErrorCode.NotClosedSection, "Not closed section for variable '" + variable + "'", token.line, token.column);
}
/**
* Retrieves variables from the parsed output.
*/
lookupVariables() {
if (this._originalTokens == null)
return;
this._variableNames = [];
for (const token of this._initialTokens) {
if (token.type != MustacheTokenType_1.MustacheTokenType.Value
&& token.type != MustacheTokenType_1.MustacheTokenType.Comment
&& token.value != null) {
const variableName = token.value.toLowerCase();
const found = this._variableNames.some((v) => v.toLowerCase() == variableName);
if (!found) {
this._variableNames.push(token.value);
}
}
}
}
}
exports.MustacheParser = MustacheParser;
//# sourceMappingURL=MustacheParser.js.map