blob: f86a657a105e9293385319f8945a12301f36171d [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010 Cloudsmith Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Cloudsmith Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.equinox.internal.p2.metadata.expression.parser;
import java.util.*;
import org.eclipse.equinox.internal.p2.metadata.expression.IExpressionConstants;
import org.eclipse.equinox.internal.p2.metadata.expression.LDAPApproximation;
import org.eclipse.equinox.p2.metadata.expression.*;
public class ExpressionParser extends Stack<IExpression> implements IExpressionConstants, IExpressionParser {
private static final long serialVersionUID = 5481439062356612378L;
protected static final int TOKEN_OR = 1;
protected static final int TOKEN_AND = 2;
protected static final int TOKEN_EQUAL = 10;
protected static final int TOKEN_NOT_EQUAL = 11;
protected static final int TOKEN_LESS = 12;
protected static final int TOKEN_LESS_EQUAL = 13;
protected static final int TOKEN_GREATER = 14;
protected static final int TOKEN_GREATER_EQUAL = 15;
protected static final int TOKEN_MATCHES = 16;
protected static final int TOKEN_NOT = 20;
protected static final int TOKEN_DOT = 21;
protected static final int TOKEN_COMMA = 22;
protected static final int TOKEN_PIPE = 23;
protected static final int TOKEN_DOLLAR = 24;
protected static final int TOKEN_IF = 25;
protected static final int TOKEN_ELSE = 26;
protected static final int TOKEN_LP = 30;
protected static final int TOKEN_RP = 31;
protected static final int TOKEN_LB = 32;
protected static final int TOKEN_RB = 33;
protected static final int TOKEN_LC = 34;
protected static final int TOKEN_RC = 35;
protected static final int TOKEN_IDENTIFIER = 40;
protected static final int TOKEN_LITERAL = 41;
protected static final int TOKEN_NULL = 50;
protected static final int TOKEN_TRUE = 51;
protected static final int TOKEN_FALSE = 52;
private static final int TOKEN_ALL = 60;
private static final int TOKEN_EXISTS = 61;
protected static final int TOKEN_END = 0;
protected static final int TOKEN_ERROR = -1;
protected static final Map<String, Integer> keywords;
static {
keywords = new HashMap<String, Integer>();
keywords.put(KEYWORD_FALSE, new Integer(TOKEN_FALSE));
keywords.put(KEYWORD_NULL, new Integer(TOKEN_NULL));
keywords.put(KEYWORD_TRUE, new Integer(TOKEN_TRUE));
keywords.put(KEYWORD_ALL, new Integer(TOKEN_ALL));
keywords.put(KEYWORD_EXISTS, new Integer(TOKEN_EXISTS));
}
protected final IExpressionFactory factory;
protected String expression;
protected int tokenPos;
protected int currentToken;
protected int lastTokenPos;
protected Object tokenValue;
protected String rootVariable;
public ExpressionParser(IExpressionFactory factory) {
this.factory = factory;
}
public synchronized IExpression parse(String exprString) {
expression = exprString;
tokenPos = 0;
currentToken = 0;
tokenValue = null;
IExpression thisVariable = factory.thisVariable();
rootVariable = ExpressionUtil.getName(thisVariable);
push(thisVariable);
try {
nextToken();
IExpression expr = currentToken == TOKEN_END ? factory.constant(Boolean.TRUE) : parseCondition();
assertToken(TOKEN_END);
return expr;
} finally {
clear(); // pop all items
}
}
public synchronized IExpression parseQuery(String exprString) {
expression = exprString;
tokenPos = 0;
currentToken = 0;
tokenValue = null;
rootVariable = VARIABLE_EVERYTHING;
IExpression everythingVariable = factory.variable(VARIABLE_EVERYTHING);
push(everythingVariable);
try {
nextToken();
IExpression expr = parseCondition();
assertToken(TOKEN_END);
return expr;
} finally {
clear(); // pop all items
}
}
protected Map<String, Integer> keywordToTokenMap() {
return keywords;
}
protected IExpression parseCondition() {
// Just a hook in this parser. Conditions are not supported
return parseOr();
}
protected IExpression parseOr() {
IExpression expr = parseAnd();
if (currentToken != TOKEN_OR)
return expr;
ArrayList<IExpression> exprs = new ArrayList<IExpression>();
exprs.add(expr);
do {
nextToken();
exprs.add(parseAnd());
} while (currentToken == TOKEN_OR);
return factory.or(exprs.toArray(new IExpression[exprs.size()]));
}
protected IExpression parseAnd() {
IExpression expr = parseBinary();
if (currentToken != TOKEN_AND)
return expr;
ArrayList<IExpression> exprs = new ArrayList<IExpression>();
exprs.add(expr);
do {
nextToken();
exprs.add(parseBinary());
} while (currentToken == TOKEN_AND);
return factory.and(exprs.toArray(new IExpression[exprs.size()]));
}
protected IExpression parseBinary() {
IExpression expr = parseNot();
for (;;) {
switch (currentToken) {
case TOKEN_OR :
case TOKEN_AND :
case TOKEN_RP :
case TOKEN_RB :
case TOKEN_RC :
case TOKEN_COMMA :
case TOKEN_IF :
case TOKEN_ELSE :
case TOKEN_END :
break;
case TOKEN_EQUAL :
case TOKEN_NOT_EQUAL :
case TOKEN_GREATER :
case TOKEN_GREATER_EQUAL :
case TOKEN_LESS :
case TOKEN_LESS_EQUAL :
case TOKEN_MATCHES :
int realToken = currentToken;
nextToken();
IExpression rhs;
if (realToken == TOKEN_MATCHES && currentToken == TOKEN_LITERAL && tokenValue instanceof String)
rhs = factory.constant(new LDAPApproximation((String) tokenValue));
else
rhs = parseNot();
switch (realToken) {
case TOKEN_EQUAL :
expr = factory.equals(expr, rhs);
break;
case TOKEN_NOT_EQUAL :
expr = factory.not(factory.equals(expr, rhs));
break;
case TOKEN_GREATER :
expr = factory.greater(expr, rhs);
break;
case TOKEN_GREATER_EQUAL :
expr = factory.greaterEqual(expr, rhs);
break;
case TOKEN_LESS :
expr = factory.less(expr, rhs);
break;
case TOKEN_LESS_EQUAL :
expr = factory.lessEqual(expr, rhs);
break;
default :
expr = factory.matches(expr, rhs);
}
continue;
default :
throw syntaxError();
}
break;
}
return expr;
}
protected IExpression parseNot() {
if (currentToken == TOKEN_NOT) {
nextToken();
IExpression expr = parseNot();
return factory.not(expr);
}
return parseCollectionExpression();
}
protected IExpression parseCollectionExpression() {
IExpression expr = parseCollectionLHS();
if (expr == null) {
expr = parseMember();
if (currentToken != TOKEN_DOT)
return expr;
nextToken();
}
for (;;) {
int funcToken = currentToken;
nextToken();
assertToken(TOKEN_LP);
nextToken();
expr = parseCollectionRHS(expr, funcToken);
if (currentToken != TOKEN_DOT)
break;
nextToken();
}
return expr;
}
protected IExpression parseCollectionLHS() {
IExpression expr = null;
switch (currentToken) {
case TOKEN_EXISTS :
case TOKEN_ALL :
expr = getVariableOrRootMember(rootVariable);
break;
}
return expr;
}
protected IExpression parseCollectionRHS(IExpression expr, int funcToken) {
switch (funcToken) {
case TOKEN_EXISTS :
expr = factory.exists(expr, parseLambdaDefinition());
break;
case TOKEN_ALL :
expr = factory.all(expr, parseLambdaDefinition());
break;
default :
throw syntaxError();
}
return expr;
}
protected IExpression parseLambdaDefinition() {
assertToken(TOKEN_IDENTIFIER);
IExpression each = factory.variable((String) tokenValue);
push(each);
try {
nextToken();
assertToken(TOKEN_PIPE);
nextToken();
IExpression body = parseCondition();
assertToken(TOKEN_RP);
nextToken();
return factory.lambda(each, body);
} finally {
pop();
}
}
protected IExpression parseMember() {
IExpression expr = parseUnary();
String name;
while (currentToken == TOKEN_DOT || currentToken == TOKEN_LB) {
int savePos = tokenPos;
int saveToken = currentToken;
Object saveTokenValue = tokenValue;
nextToken();
if (saveToken == TOKEN_DOT) {
switch (currentToken) {
case TOKEN_IDENTIFIER :
name = (String) tokenValue;
nextToken();
expr = factory.member(expr, name);
break;
default :
tokenPos = savePos;
currentToken = saveToken;
tokenValue = saveTokenValue;
return expr;
}
} else {
IExpression atExpr = parseMember();
assertToken(TOKEN_RB);
nextToken();
expr = factory.at(expr, atExpr);
}
}
return expr;
}
protected IExpression parseUnary() {
IExpression expr;
switch (currentToken) {
case TOKEN_LP :
nextToken();
expr = parseCondition();
assertToken(TOKEN_RP);
nextToken();
break;
case TOKEN_LITERAL :
expr = factory.constant(tokenValue);
nextToken();
break;
case TOKEN_IDENTIFIER :
expr = getVariableOrRootMember((String) tokenValue);
nextToken();
break;
case TOKEN_NULL :
expr = factory.constant(null);
nextToken();
break;
case TOKEN_TRUE :
expr = factory.constant(Boolean.TRUE);
nextToken();
break;
case TOKEN_FALSE :
expr = factory.constant(Boolean.FALSE);
nextToken();
break;
case TOKEN_DOLLAR :
expr = parseParameter();
break;
default :
throw syntaxError();
}
return expr;
}
private IExpression parseParameter() {
if (currentToken == TOKEN_DOLLAR) {
nextToken();
if (currentToken == TOKEN_LITERAL && tokenValue instanceof Integer) {
IExpression param = factory.indexedParameter(((Integer) tokenValue).intValue());
nextToken();
return param;
}
}
throw syntaxError();
}
protected IExpression[] parseArray() {
IExpression expr = parseCondition();
if (currentToken != TOKEN_COMMA)
return new IExpression[] {expr};
ArrayList<IExpression> operands = new ArrayList<IExpression>();
operands.add(expr);
do {
nextToken();
if (currentToken == TOKEN_LC)
// We don't allow lambdas in the array
break;
operands.add(parseCondition());
} while (currentToken == TOKEN_COMMA);
return operands.toArray(new IExpression[operands.size()]);
}
protected void assertToken(int token) {
if (currentToken != token)
throw syntaxError();
}
protected IExpression getVariableOrRootMember(String id) {
int idx = size();
while (--idx >= 0) {
IExpression v = get(idx);
if (id.equals(v.toString()))
return v;
}
if (rootVariable == null || rootVariable.equals(id))
throw syntaxError("No such variable: " + id); //$NON-NLS-1$
return factory.member(getVariableOrRootMember(rootVariable), id);
}
protected void nextToken() {
tokenValue = null;
int top = expression.length();
char c = 0;
while (tokenPos < top) {
c = expression.charAt(tokenPos);
if (!Character.isWhitespace(c))
break;
++tokenPos;
}
if (tokenPos >= top) {
lastTokenPos = top;
currentToken = TOKEN_END;
return;
}
lastTokenPos = tokenPos;
switch (c) {
case '|' :
if (tokenPos + 1 < top && expression.charAt(tokenPos + 1) == '|') {
tokenValue = OPERATOR_OR;
currentToken = TOKEN_OR;
tokenPos += 2;
} else {
currentToken = TOKEN_PIPE;
++tokenPos;
}
break;
case '&' :
if (tokenPos + 1 < top && expression.charAt(tokenPos + 1) == '&') {
tokenValue = OPERATOR_AND;
currentToken = TOKEN_AND;
tokenPos += 2;
} else
currentToken = TOKEN_ERROR;
break;
case '=' :
if (tokenPos + 1 < top && expression.charAt(tokenPos + 1) == '=') {
tokenValue = OPERATOR_EQUALS;
currentToken = TOKEN_EQUAL;
tokenPos += 2;
} else
currentToken = TOKEN_ERROR;
break;
case '!' :
if (tokenPos + 1 < top && expression.charAt(tokenPos + 1) == '=') {
tokenValue = OPERATOR_NOT_EQUALS;
currentToken = TOKEN_NOT_EQUAL;
tokenPos += 2;
} else {
currentToken = TOKEN_NOT;
++tokenPos;
}
break;
case '~' :
if (tokenPos + 1 < top && expression.charAt(tokenPos + 1) == '=') {
tokenValue = OPERATOR_MATCHES;
currentToken = TOKEN_MATCHES;
tokenPos += 2;
} else
currentToken = TOKEN_ERROR;
break;
case '>' :
if (tokenPos + 1 < top && expression.charAt(tokenPos + 1) == '=') {
tokenValue = OPERATOR_GT_EQUAL;
currentToken = TOKEN_GREATER_EQUAL;
tokenPos += 2;
} else {
currentToken = TOKEN_GREATER;
++tokenPos;
}
break;
case '<' :
if (tokenPos + 1 < top && expression.charAt(tokenPos + 1) == '=') {
tokenValue = OPERATOR_LT_EQUAL;
currentToken = TOKEN_LESS_EQUAL;
tokenPos += 2;
} else {
currentToken = TOKEN_LESS;
++tokenPos;
}
break;
case '?' :
currentToken = TOKEN_IF;
++tokenPos;
break;
case ':' :
currentToken = TOKEN_ELSE;
++tokenPos;
break;
case '.' :
currentToken = TOKEN_DOT;
++tokenPos;
break;
case '$' :
currentToken = TOKEN_DOLLAR;
++tokenPos;
break;
case '{' :
currentToken = TOKEN_LC;
++tokenPos;
break;
case '}' :
currentToken = TOKEN_RC;
++tokenPos;
break;
case '(' :
currentToken = TOKEN_LP;
++tokenPos;
break;
case ')' :
currentToken = TOKEN_RP;
++tokenPos;
break;
case '[' :
currentToken = TOKEN_LB;
++tokenPos;
break;
case ']' :
currentToken = TOKEN_RB;
++tokenPos;
break;
case ',' :
currentToken = TOKEN_COMMA;
++tokenPos;
break;
case '"' :
case '\'' :
parseDelimitedString(c);
break;
case '/' :
parseDelimitedString(c);
if (currentToken == TOKEN_LITERAL)
tokenValue = SimplePattern.compile((String) tokenValue);
break;
default :
if (Character.isDigit(c)) {
int start = tokenPos++;
while (tokenPos < top && Character.isDigit(expression.charAt(tokenPos)))
++tokenPos;
tokenValue = Integer.valueOf(expression.substring(start, tokenPos));
currentToken = TOKEN_LITERAL;
break;
}
if (Character.isJavaIdentifierStart(c)) {
int start = tokenPos++;
while (tokenPos < top && Character.isJavaIdentifierPart(expression.charAt(tokenPos)))
++tokenPos;
String word = expression.substring(start, tokenPos);
Integer token = keywordToTokenMap().get(word);
if (token == null)
currentToken = TOKEN_IDENTIFIER;
else
currentToken = token.intValue();
tokenValue = word;
break;
}
throw syntaxError();
}
}
protected void popVariable() {
if (isEmpty())
throw syntaxError();
pop();
}
protected ExpressionParseException syntaxError() {
Object tv = tokenValue;
if (tv == null) {
if (lastTokenPos >= expression.length())
return syntaxError("Unexpected end of expression"); //$NON-NLS-1$
tv = expression.substring(lastTokenPos, lastTokenPos + 1);
}
return syntaxError("Unexpected token \"" + tv + '"'); //$NON-NLS-1$
}
protected ExpressionParseException syntaxError(String message) {
return new ExpressionParseException(expression, message, tokenPos);
}
private void parseDelimitedString(char delim) {
int start = ++tokenPos;
StringBuffer buf = new StringBuffer();
int top = expression.length();
while (tokenPos < top) {
char ec = expression.charAt(tokenPos);
if (ec == delim)
break;
if (ec == '\\') {
if (++tokenPos == top)
break;
ec = expression.charAt(tokenPos);
}
buf.append(ec);
++tokenPos;
}
if (tokenPos == top) {
tokenPos = start - 1;
currentToken = TOKEN_ERROR;
} else {
++tokenPos;
tokenValue = buf.toString();
currentToken = TOKEN_LITERAL;
}
}
}