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