001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.camel.language.simple;
018
019import java.util.ArrayDeque;
020import java.util.ArrayList;
021import java.util.Collections;
022import java.util.Deque;
023import java.util.List;
024
025import org.apache.camel.language.simple.ast.Block;
026import org.apache.camel.language.simple.ast.BlockEnd;
027import org.apache.camel.language.simple.ast.BlockStart;
028import org.apache.camel.language.simple.ast.SimpleNode;
029import org.apache.camel.language.simple.ast.UnaryExpression;
030import org.apache.camel.language.simple.types.SimpleParserException;
031import org.apache.camel.language.simple.types.SimpleToken;
032import org.apache.camel.language.simple.types.SimpleTokenType;
033import org.apache.camel.language.simple.types.TokenType;
034
035/**
036 * Base class for Simple language parser.
037 * <p/>
038 * This parser is based on the principles of a
039 * <a href="http://en.wikipedia.org/wiki/Recursive_descent_parser">recursive descent parser</a>.
040 */
041public abstract class BaseSimpleParser {
042
043    protected final String expression;
044    protected final List<SimpleToken> tokens = new ArrayList<>();
045    protected final List<SimpleNode> nodes = new ArrayList<>();
046    protected SimpleToken token;
047    protected int previousIndex;
048    protected int index;
049    protected boolean allowEscape = true;
050
051    protected BaseSimpleParser(String expression, boolean allowEscape) {
052        this.expression = expression;
053        this.allowEscape = allowEscape;
054    }
055
056    /**
057     * Advances the parser position to the next known {@link SimpleToken}
058     * in the input.
059     */
060    protected void nextToken() {
061        if (index < expression.length()) {
062            SimpleToken next = SimpleTokenizer.nextToken(expression, index, allowEscape);
063            // add token
064            tokens.add(next);
065            token = next;
066            // position index after the token
067            previousIndex = index;
068            index += next.getLength();
069        } else {
070            // end of tokens
071            token = new SimpleToken(new SimpleTokenType(TokenType.eol, null), index);
072        }
073    }
074
075    /**
076     * Advances the parser position to the next known {@link SimpleToken}
077     * in the input.
078     *
079     * @param filter filter for accepted token types
080     */
081    protected void nextToken(TokenType... filter) {
082        if (index < expression.length()) {
083            SimpleToken next = SimpleTokenizer.nextToken(expression, index, allowEscape, filter);
084            // add token
085            tokens.add(next);
086            token = next;
087            // position index after the token
088            previousIndex = index;
089            index += next.getLength();
090        } else {
091            // end of tokens
092            token = new SimpleToken(new SimpleTokenType(TokenType.eol, null), index);
093        }
094    }
095
096    /**
097     * Clears the parser state, which means it can be used for parsing a new input.
098     */
099    protected void clear() {
100        token = null;
101        previousIndex = 0;
102        index = 0;
103        tokens.clear();
104        nodes.clear();
105    }
106
107    /**
108     * Prepares blocks, such as functions, single or double quoted texts.
109     * <p/>
110     * This process prepares the {@link Block}s in the AST. This is done
111     * by linking child {@link SimpleNode nodes} which are within the start and end of the blocks,
112     * as child to the given block. This is done to have the AST graph updated and prepared properly.
113     * <p/>
114     * So when the AST node is later used to create the {@link org.apache.camel.Predicate}s
115     * or {@link org.apache.camel.Expression}s to be used by Camel then the AST graph
116     * has a linked and prepared graph of nodes which represent the input expression.
117     */
118    protected void prepareBlocks() {
119        List<SimpleNode> answer = new ArrayList<>();
120        Deque<Block> stack = new ArrayDeque<>();
121
122        for (SimpleNode token : nodes) {
123            if (token instanceof BlockStart) {
124                // a new block is started, so push on the stack
125                stack.push((Block) token);
126            } else if (token instanceof BlockEnd) {
127                // end block is just an abstract mode, so we should not add it
128                if (stack.isEmpty()) {
129                    throw new SimpleParserException(token.getToken().getType().getType() + " has no matching start token", token.getToken().getIndex());
130                }
131
132                Block top = stack.pop();
133                // if there is a block on the stack then it should accept the child token
134                Block block = stack.isEmpty() ? null : stack.peek();
135                if (block != null) {
136                    if (!block.acceptAndAddNode(top)) {
137                        throw new SimpleParserException(block.getToken().getType() + " cannot accept " + token.getToken().getType(), token.getToken().getIndex());
138                    }
139                } else {
140                    // no block, so add to answer
141                    answer.add(top);
142                }
143            } else {
144                // if there is a block on the stack then it should accept the child token
145                Block block = stack.isEmpty() ? null : stack.peek();
146                if (block != null) {
147                    if (!block.acceptAndAddNode(token)) {
148                        throw new SimpleParserException(block.getToken().getType() + " cannot accept " + token.getToken().getType(), token.getToken().getIndex());
149                    }
150                } else {
151                    // no block, so add to answer
152                    answer.add(token);
153                }
154            }
155        }
156
157        // replace nodes from the stack
158        nodes.clear();
159        nodes.addAll(answer);
160    }
161
162    /**
163     * Prepares unary expressions.
164     * <p/>
165     * This process prepares the unary expressions in the AST. This is done
166     * by linking the unary operator with the left hand side node,
167     * to have the AST graph updated and prepared properly.
168     * <p/>
169     * So when the AST node is later used to create the {@link org.apache.camel.Predicate}s
170     * or {@link org.apache.camel.Expression}s to be used by Camel then the AST graph
171     * has a linked and prepared graph of nodes which represent the input expression.
172     */
173    protected void prepareUnaryExpressions() {
174        Deque<SimpleNode> stack = new ArrayDeque<>();
175
176        for (SimpleNode node : nodes) {
177            if (node instanceof UnaryExpression) {
178                UnaryExpression token = (UnaryExpression) node;
179
180                // remember the logical operator
181                String operator = token.getOperator().toString();
182
183                SimpleNode previous = stack.isEmpty() ? null : stack.pop();
184                if (previous == null) {
185                    throw new SimpleParserException("Unary operator " + operator + " has no left hand side token", token.getToken().getIndex());
186                } else {
187                    token.acceptLeft(previous);
188                }
189            }
190            stack.push(node);
191        }
192
193        // replace nodes from the stack
194        nodes.clear();
195        nodes.addAll(stack);
196        // must reverse as it was added from a stack that is reverse
197        Collections.reverse(nodes);
198    }
199
200    // --------------------------------------------------------------
201    // grammar
202    // --------------------------------------------------------------
203
204    /**
205     * Accept the given token.
206     * <p/>
207     * This is to be used by the grammar to accept tokens and then continue parsing
208     * using the grammar, such as a function grammar.
209     *
210     * @param accept  the token
211     * @return <tt>true</tt> if accepted, <tt>false</tt> otherwise.
212     */
213    protected boolean accept(TokenType accept) {
214        return token == null || token.getType().getType() == accept;
215    }
216
217    /**
218     * Expect a given token
219     *
220     * @param expect the token to expect
221     * @throws SimpleParserException is thrown if the token is not as expected
222     */
223    protected void expect(TokenType expect) throws SimpleParserException {
224        if (token != null && token.getType().getType() == expect) {
225            return;
226        } else if (token == null) {
227            // use the previous index as that is where the problem is
228            throw new SimpleParserException("expected symbol " + expect + " but reached eol", previousIndex);
229        } else {
230            // use the previous index as that is where the problem is
231            throw new SimpleParserException("expected symbol " + expect + " but was " + token.getType().getType(), previousIndex);
232        }
233    }
234
235    /**
236     * Expect and accept a given number of tokens in sequence.
237     * <p/>
238     * This is used to accept whitespace or string literals.
239     *
240     * @param expect the token to accept
241     */
242    protected void expectAndAcceptMore(TokenType expect) {
243        expect(expect);
244
245        while (!token.getType().isEol() && token.getType().getType() == expect) {
246            nextToken();
247        }
248    }
249
250}