/**************************************************************************************
* Copyright (C) 2008 EsperTech, Inc. All rights reserved. *
* http://esper.codehaus.org *
* http://www.espertech.com *
* ---------------------------------------------------------------------------------- *
* The software in this package is published under the terms of the GPL license *
* a copy of which has been included with this distribution in the license.txt file. *
**************************************************************************************/
package com.espertech.esper.epl.parse;
import com.espertech.esper.antlr.ASTUtil;
import com.espertech.esper.antlr.NoCaseSensitiveStream;
import com.espertech.esper.client.EPException;
import com.espertech.esper.collection.Pair;
import com.espertech.esper.epl.generated.EsperEPL2Ast;
import com.espertech.esper.epl.generated.EsperEPL2GrammarLexer;
import com.espertech.esper.epl.generated.EsperEPL2GrammarParser;
import org.antlr.runtime.*;
import org.antlr.runtime.tree.Tree;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
/**
* Helper class for parsing an expression and walking a parse tree.
*/
public class ParseHelper {
/**
* Newline.
*/
public final static String newline = System.getProperty("line.separator");
/**
* Walk parse tree starting at the rule the walkRuleSelector supplies.
*
* @param ast - ast to walk
* @param walker - walker instance
* @param walkRuleSelector - walk rule
* @param expression - the expression we are walking in string form
* @param eplStatementForErrorMsg - statement text for error messages
*/
public static void walk(Tree ast, EPLTreeWalker walker, WalkRuleSelector walkRuleSelector, String expression, String eplStatementForErrorMsg) {
// Walk tree
try {
if (log.isDebugEnabled()) {
log.debug(".walk Walking AST using walker " + walker.getClass().getName());
}
walkRuleSelector.invokeWalkRule(walker);
if (log.isDebugEnabled()) {
log.debug(".walk AST tree after walking");
ASTUtil.dumpAST(ast);
}
} catch (RuntimeException e) {
log.info("Error walking statement [" + expression + "]", e);
if (e.getCause() instanceof RecognitionException) {
throw ExceptionConvertor.convert((RecognitionException) e.getCause(), eplStatementForErrorMsg, walker);
} else {
throw e;
}
} catch (RecognitionException e) {
log.info("Error walking statement [" + expression + "]", e);
throw ExceptionConvertor.convert(e, eplStatementForErrorMsg, walker);
}
}
/**
* Parse expression using the rule the ParseRuleSelector instance supplies.
*
* @param expression - text to parse
* @param parseRuleSelector - parse rule to select
* @param addPleaseCheck - true to include depth paraphrase
* @param eplStatementErrorMsg - text for error
* @return AST - syntax tree
* @throws EPException when the AST could not be parsed
*/
public static ParseResult parse(String expression, String eplStatementErrorMsg, boolean addPleaseCheck, ParseRuleSelector parseRuleSelector, boolean rewriteScript) throws EPException {
if (log.isDebugEnabled()) {
log.debug(".parse Parsing expr=" + expression);
}
CharStream input;
try {
input = new NoCaseSensitiveStream(new StringReader(expression));
} catch (IOException ex) {
throw new EPException("IOException parsing expression '" + expression + '\'', ex);
}
EsperEPL2GrammarLexer lex = new EsperEPL2GrammarLexer(input);
TokenRewriteStream tokens = new TokenRewriteStream(lex);
EsperEPL2GrammarParser parser = new EsperEPL2GrammarParser(tokens);
Tree tree;
try {
tree = parseRuleSelector.invokeParseRule(parser);
} catch (RuntimeException e) {
if (log.isDebugEnabled()) {
log.debug("Error parsing statement [" + eplStatementErrorMsg + "]", e);
}
if (e.getCause() instanceof RecognitionException) {
throw ExceptionConvertor.convertStatement((RecognitionException) e.getCause(), eplStatementErrorMsg, addPleaseCheck, parser);
} else {
throw e;
}
} catch (RecognitionException ex) {
if (rewriteScript && isContainsScriptExpression(tokens)) {
ScriptResult rewriteExpression = rewriteTokensScript(tokens);
ParseResult result = parse(rewriteExpression.getRewrittenEPL(), eplStatementErrorMsg, addPleaseCheck, parseRuleSelector, false);
return new ParseResult(result.getTree(), result.getExpressionWithoutAnnotations(), result.getTokenStream(), rewriteExpression.getScripts());
}
log.debug("Error parsing statement [" + expression + "]", ex);
throw ExceptionConvertor.convertStatement(ex, eplStatementErrorMsg, addPleaseCheck, parser);
}
// if we are re-writing scripts and contain a script, then rewrite
if (rewriteScript && isContainsScriptExpression(tokens)) {
ScriptResult rewriteExpression = rewriteTokensScript(tokens);
ParseResult result = parse(rewriteExpression.getRewrittenEPL(), rewriteExpression.getRewrittenEPL(), addPleaseCheck, parseRuleSelector, false);
return new ParseResult(result.getTree(), result.getExpressionWithoutAnnotations(), result.getTokenStream(), rewriteExpression.getScripts());
}
if (log.isDebugEnabled()) {
log.debug(".parse Dumping AST...");
ASTUtil.dumpAST(tree);
}
return new ParseResult(tree, getNoAnnotation(expression, tree, tokens), tokens, Collections.<String>emptyList());
}
private static ScriptResult rewriteTokensScript(TokenRewriteStream tokens) {
List<String> scripts = new ArrayList<String>();
for (int i = 0; i < tokens.size(); i++) {
if (tokens.get(i).getType() == EsperEPL2Ast.EXPRESSIONDECL) {
Token tokenBefore = getTokenBefore(i, tokens);
boolean isCreateExpressionClause = tokenBefore != null && tokenBefore.getType() == EsperEPL2Ast.CREATE;
Pair<String, Integer> nameAndNameStart = findScriptName(i + 1, tokens);
int startIndex = findStartTokenScript(nameAndNameStart.getSecond(), tokens, EsperEPL2Ast.LBRACK);
if (startIndex != -1) {
int endIndex = findEndTokenScript(startIndex + 1, tokens, EsperEPL2Ast.RBRACK, EsperEPL2GrammarParser.getAfterScriptTokens(), !isCreateExpressionClause);
if (endIndex != -1) {
StringWriter writer = new StringWriter();
for (int j = startIndex + 1; j < endIndex; j++) {
writer.append(tokens.get(j).getText());
}
scripts.add(writer.toString());
rewriteScript(startIndex, endIndex, tokens);
}
}
}
}
String rewrittenEPL = tokens.toString();
return new ScriptResult(rewrittenEPL, scripts);
}
private static Token getTokenBefore(int i, TokenRewriteStream tokens) {
int position = i-1;
while (position >= 0) {
Token t = tokens.get(position);
if (t.getChannel() != 99) {
return t;
}
position--;
}
return null;
}
private static Pair<String, Integer> findScriptName(int start, TokenRewriteStream tokens) {
String lastIdent = null;
int lastIdentIndex = 0;
for (int i = start; i < tokens.size(); i++) {
if (tokens.get(i).getType() == EsperEPL2Ast.IDENT) {
lastIdent = tokens.get(i).getText();
lastIdentIndex = i;
}
if (tokens.get(i).getType() == EsperEPL2Ast.LPAREN) {
break;
}
// find beginning of script, ignore brackets
if (tokens.get(i).getType() == EsperEPL2Ast.LBRACK && tokens.get(i+1).getType() != EsperEPL2Ast.RBRACK) {
break;
}
}
if (lastIdent == null) {
throw new IllegalStateException("Failed to parse expression name");
}
return new Pair<String, Integer>(lastIdent, lastIdentIndex);
}
private static void rewriteScript(int startIndex, int endIndex, TokenRewriteStream tokens) {
if (startIndex >= endIndex - 1) {
return;
}
Token[] tokensCapture = new Token[endIndex - startIndex - 1];
int count = 0;
for (int i = startIndex + 1; i < endIndex; i++) {
tokensCapture[count] = tokens.get(i);
count++;
}
tokens.delete(startIndex + 1, endIndex - 1);
int start = endIndex -1;
tokens.insertAfter(start, "'");
for (int i = 0; i < tokensCapture.length; i++) {
if (tokensCapture[i].getType() == EsperEPL2Ast.QUOTED_STRING_LITERAL) {
tokens.insertAfter(start, "\\'");
tokens.insertAfter(start, tokensCapture[i].getText().substring(1, tokensCapture[i].getText().length() - 1));
tokens.insertAfter(start, "\\'");
}
else {
tokens.insertAfter(start, tokensCapture[i].getText());
}
}
tokens.insertAfter(start, "'");
}
private static int findStartTokenScript(int startIndex, TokenRewriteStream tokens, int tokenTypeSearch) {
int found = -1;
for (int i = startIndex; i < tokens.size(); i++) {
if (tokens.get(i).getType() == tokenTypeSearch) {
return i;
}
}
return found;
}
private static int findEndTokenScript(int startIndex, TokenRewriteStream tokens, int tokenTypeSearch, Set<Integer> afterScriptTokens, boolean requireAfterScriptToken) {
int found = -1;
for (int i = startIndex; i < tokens.size(); i++) {
if (tokens.get(i).getType() == tokenTypeSearch) {
if (!requireAfterScriptToken) {
return i;
}
// The next non-comment token must be among the afterScriptTokens, i.e. SELECT/INSERT/ON/DELETE/UPDATE
// Find next non-comment token.
for (int j = i + 1; j < tokens.size(); j++) {
Token next = tokens.get(j);
if (next.getChannel() == 0) {
if (afterScriptTokens.contains(next.getType())) {
found = i;
}
break;
}
}
}
if (found != -1) {
break;
}
}
return found;
}
private static boolean isContainsScriptExpression(TokenRewriteStream tokens) {
for (int i = 0; i < tokens.size(); i++) {
if (tokens.get(i).getType() == EsperEPL2Ast.EXPRESSIONDECL) {
int startIndex = findStartTokenScript(i + 1, tokens, EsperEPL2Ast.LBRACK);
if (startIndex != -1) {
return true;
}
}
}
return false;
}
private static String getNoAnnotation(String expression, Tree tree, CommonTokenStream tokens) {
Token lastAnnotationToken = null;
for (int i = 0; i < tree.getChildCount(); i++) {
if (tree.getChild(i).getType() == EsperEPL2Ast.ANNOTATION) {
lastAnnotationToken = tokens.get(tree.getChild(i).getTokenStopIndex());
} else {
break;
}
}
if (lastAnnotationToken == null) {
return null;
}
try {
int line = lastAnnotationToken.getLine();
int charpos = lastAnnotationToken.getCharPositionInLine();
int fromChar = charpos + lastAnnotationToken.getText().length();
if (line == 1) {
return expression.substring(fromChar).trim();
}
String[] lines = expression.split("\r\n|\r|\n");
StringBuilder buf = new StringBuilder();
buf.append(lines[line - 1].substring(fromChar));
for (int i = line; i < lines.length; i++) {
buf.append(lines[i]);
if (i < lines.length - 1) {
buf.append(newline);
}
}
return buf.toString().trim();
} catch (RuntimeException ex) {
log.error("Error determining non-annotated expression sting: " + ex.getMessage(), ex);
}
return null;
}
private static class ScriptResult {
private final String rewrittenEPL;
private final List<String> scripts;
private ScriptResult(String rewrittenEPL, List<String> scripts) {
this.rewrittenEPL = rewrittenEPL;
this.scripts = scripts;
}
public String getRewrittenEPL() {
return rewrittenEPL;
}
public List<String> getScripts() {
return scripts;
}
}
private static Log log = LogFactory.getLog(ParseHelper.class);
}