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}