| /******************************************************************************* |
| * 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; |
| } |
| } |
| } |