package org.activityinfo.model.expr;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
import com.google.common.collect.PeekingIterator;
import com.google.common.collect.Sets;
import org.activityinfo.model.expr.diagnostic.ExprSyntaxException;
import org.activityinfo.model.expr.functions.*;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
public class ExprParser {
private static final Set<String> INFIX_OPERATORS = Sets.newHashSet("+", "-", "*", "/", "&&", "||", "==", "!=" );
private static final Set<String> PREFIX_OPERATORS = Sets.newHashSet("!" );
public static final Set<String> FUNCTIONS = Collections.unmodifiableSet(Sets.newHashSet(
ContainsAllFunction.INSTANCE.getId(),
ContainsAnyFunction.INSTANCE.getId(),
NotContainsAllFunction.INSTANCE.getId(),
NotContainsAnyFunction.INSTANCE.getId()
));
private PeekingIterator<Token> lexer;
public ExprParser(Iterator<Token> tokens) {
this.lexer = Iterators.peekingIterator(Iterators.filter(tokens, new Predicate<Token>() {
@Override
public boolean apply(Token token) {
return token.getType() != TokenType.WHITESPACE;
}
}));
}
public ExprNode parse() {
ExprNode expr = parseSimple();
if (!lexer.hasNext()) {
return expr;
}
Token token = lexer.peek();
if (isInfixOperator(token)) {
lexer.next();
ExprFunction function = ExprFunctions.get(token.getString());
ExprNode right = parse();
return new FunctionCallNode(function, expr, right);
} else if(token.getType() == TokenType.DOT) {
lexer.next();
return new CompoundExpr(expr, new SymbolExpr(expectNext(TokenType.SYMBOL, "Field name").getString()));
} else {
return expr;
}
}
private boolean isInfixOperator(Token token) {
return token.getType() == TokenType.OPERATOR &&
INFIX_OPERATORS.contains(token.getString());
}
public ExprNode parseSimple() {
Token token = lexer.next();
if (token.getType() == TokenType.PAREN_START) {
return parseGroup();
} else if (token.getType() == TokenType.SYMBOL) {
if(isFunction(token)) {
return parseFunctionCall(token);
} else {
return new SymbolExpr(token.getString());
}
} else if (token.getType() == TokenType.NUMBER) {
return new ConstantExpr(Double.parseDouble(token.getString()));
} else if (token.getType() == TokenType.BOOLEAN_LITERAL) {
return new ConstantExpr(Boolean.parseBoolean(token.getString()));
} else if (prefixOperator(token)) {
ExprFunction function = ExprFunctions.get(token.getString());
ExprNode right = parse();
return new FunctionCallNode(function, right);
} else if (token.getType() == TokenType.STRING_LITERAL) {
return new ConstantExpr(token.getString());
} else if (token.getType() == TokenType.SYMBOL) {
return new SymbolExpr(token.getString());
} else {
throw new ExprSyntaxException("Unexpected token '" + token.getString() + "' at position " + token.getTokenStart() + "'");
}
}
private ExprNode parseFunctionCall(Token token) {
ExprFunction function = ExprFunctions.get(token.getString());
List<ExprNode> arguments = Lists.newArrayList();
while(lexer.hasNext()) {
Token next = lexer.next();
if (next.getType() == TokenType.COMMA || next.getType() == TokenType.PAREN_START) {
continue;
} else if (next.getType() == TokenType.PAREN_END) {
break;
} else if (next.getType() == TokenType.SYMBOL) {
arguments.add(new SymbolExpr(next.getString()));
} else {
throw new ExprSyntaxException("Unexpected token '" + token.getString() + "' at position " + token.getTokenStart() + "'");
}
}
return new FunctionCallNode(function, arguments);
}
private boolean prefixOperator(Token token) {
return token.getType() == TokenType.OPERATOR && PREFIX_OPERATORS.contains(token.getString());
}
private boolean isFunction(Token token) {
return token.getType() == TokenType.SYMBOL && FUNCTIONS.contains(token.getString()) && lexer.peek().getType() == TokenType.PAREN_START;
}
private ExprNode parseGroup() {
ExprNode expr = parse();
expectNext(TokenType.PAREN_END, "')'");
return new GroupExpr(expr);
}
/**
* Retrieves the next token, and throws an exception if it does not match
* the expected type.
*/
private Token expectNext(TokenType expectedType, String description) {
if(!lexer.hasNext()) {
throw new ExprSyntaxException("Syntax error: expected " + description + " but found end of input.");
}
Token token = lexer.next();
if (token.getType() != expectedType) {
throw new ExprSyntaxException("Syntax error at " + token.getTokenStart() + ": expected " + description + " but found '" + token.getString() + "'");
}
return token;
}
public static ExprNode parse(String expression) {
try {
ExprParser parser = new ExprParser(new ExprLexer(expression));
return parser.parse();
} catch(Exception e) {
throw new RuntimeException("Failed to parse expression: " + expression, e);
}
}
}