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 }