All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.camel.language.simple.BaseSimpleParser Maven / Gradle / Ivy

There is a newer version: 4.6.0
Show newest version
/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.camel.language.simple;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.List;

import org.apache.camel.language.simple.ast.Block;
import org.apache.camel.language.simple.ast.BlockEnd;
import org.apache.camel.language.simple.ast.BlockStart;
import org.apache.camel.language.simple.ast.SimpleNode;
import org.apache.camel.language.simple.ast.UnaryExpression;
import org.apache.camel.language.simple.types.SimpleParserException;
import org.apache.camel.language.simple.types.SimpleToken;
import org.apache.camel.language.simple.types.SimpleTokenType;
import org.apache.camel.language.simple.types.TokenType;

/**
 * Base class for Simple language parser.
 * 

* This parser is based on the principles of a * recursive descent parser. */ public abstract class BaseSimpleParser { protected final String expression; protected final List tokens = new ArrayList(); protected final List nodes = new ArrayList(); protected SimpleToken token; protected int previousIndex; protected int index; protected boolean allowEscape = true; protected BaseSimpleParser(String expression, boolean allowEscape) { this.expression = expression; this.allowEscape = allowEscape; } /** * Advances the parser position to the next known {@link SimpleToken} * in the input. */ protected void nextToken() { if (index < expression.length()) { SimpleToken next = SimpleTokenizer.nextToken(expression, index, allowEscape); // add token tokens.add(next); token = next; // position index after the token previousIndex = index; index += next.getLength(); } else { // end of tokens token = new SimpleToken(new SimpleTokenType(TokenType.eol, null), index); } } /** * Advances the parser position to the next known {@link SimpleToken} * in the input. * * @param filter filter for accepted token types */ protected void nextToken(TokenType... filter) { if (index < expression.length()) { SimpleToken next = SimpleTokenizer.nextToken(expression, index, allowEscape, filter); // add token tokens.add(next); token = next; // position index after the token previousIndex = index; index += next.getLength(); } else { // end of tokens token = new SimpleToken(new SimpleTokenType(TokenType.eol, null), index); } } /** * Clears the parser state, which means it can be used for parsing a new input. */ protected void clear() { token = null; previousIndex = 0; index = 0; tokens.clear(); nodes.clear(); } /** * Prepares blocks, such as functions, single or double quoted texts. *

* This process prepares the {@link Block}s in the AST. This is done * by linking child {@link SimpleNode nodes} which are within the start and end of the blocks, * as child to the given block. This is done to have the AST graph updated and prepared properly. *

* So when the AST node is later used to create the {@link org.apache.camel.Predicate}s * or {@link org.apache.camel.Expression}s to be used by Camel then the AST graph * has a linked and prepared graph of nodes which represent the input expression. */ protected void prepareBlocks() { List answer = new ArrayList(); Deque stack = new ArrayDeque<>(); for (SimpleNode token : nodes) { if (token instanceof BlockStart) { // a new block is started, so push on the stack stack.push((Block) token); } else if (token instanceof BlockEnd) { // end block is just an abstract mode, so we should not add it if (stack.isEmpty()) { throw new SimpleParserException(token.getToken().getType().getType() + " has no matching start token", token.getToken().getIndex()); } Block top = stack.pop(); // if there is a block on the stack then it should accept the child token Block block = stack.isEmpty() ? null : stack.peek(); if (block != null) { if (!block.acceptAndAddNode(top)) { throw new SimpleParserException(block.getToken().getType() + " cannot accept " + token.getToken().getType(), token.getToken().getIndex()); } } else { // no block, so add to answer answer.add(top); } } else { // if there is a block on the stack then it should accept the child token Block block = stack.isEmpty() ? null : stack.peek(); if (block != null) { if (!block.acceptAndAddNode(token)) { throw new SimpleParserException(block.getToken().getType() + " cannot accept " + token.getToken().getType(), token.getToken().getIndex()); } } else { // no block, so add to answer answer.add(token); } } } // replace nodes from the stack nodes.clear(); nodes.addAll(answer); } /** * Prepares unary expressions. *

* This process prepares the unary expressions in the AST. This is done * by linking the unary operator with the left hand side node, * to have the AST graph updated and prepared properly. *

* So when the AST node is later used to create the {@link org.apache.camel.Predicate}s * or {@link org.apache.camel.Expression}s to be used by Camel then the AST graph * has a linked and prepared graph of nodes which represent the input expression. */ protected void prepareUnaryExpressions() { Deque stack = new ArrayDeque<>(); for (SimpleNode node : nodes) { if (node instanceof UnaryExpression) { UnaryExpression token = (UnaryExpression) node; // remember the logical operator String operator = token.getOperator().toString(); SimpleNode previous = stack.isEmpty() ? null : stack.pop(); if (previous == null) { throw new SimpleParserException("Unary operator " + operator + " has no left hand side token", token.getToken().getIndex()); } else { token.acceptLeft(previous); } } stack.push(node); } // replace nodes from the stack nodes.clear(); nodes.addAll(stack); // must reverse as it was added from a stack that is reverse Collections.reverse(nodes); } // -------------------------------------------------------------- // grammar // -------------------------------------------------------------- /** * Accept the given token. *

* This is to be used by the grammar to accept tokens and then continue parsing * using the grammar, such as a function grammar. * * @param accept the token * @return true if accepted, false otherwise. */ protected boolean accept(TokenType accept) { return token == null || token.getType().getType() == accept; } /** * Expect a given token * * @param expect the token to expect * @throws SimpleParserException is thrown if the token is not as expected */ protected void expect(TokenType expect) throws SimpleParserException { if (token != null && token.getType().getType() == expect) { return; } else if (token == null) { // use the previous index as that is where the problem is throw new SimpleParserException("expected symbol " + expect + " but reached eol", previousIndex); } else { // use the previous index as that is where the problem is throw new SimpleParserException("expected symbol " + expect + " but was " + token.getType().getType(), previousIndex); } } /** * Expect and accept a given number of tokens in sequence. *

* This is used to accept whitespace or string literals. * * @param expect the token to accept */ protected void expectAndAcceptMore(TokenType expect) { expect(expect); while (!token.getType().isEol() && token.getType().getType() == expect) { nextToken(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy