All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sap.cds.impl.parser.AbstractCqnExpressionParser Maven / Gradle / Ivy

/*******************************************************************
 * © 2020 SAP SE or an SAP affiliate company. All rights reserved. *
 *******************************************************************/
package com.sap.cds.impl.parser;

import static com.sap.cds.impl.parser.token.CqnPlainImpl.plain;
import static com.sap.cds.ql.CQL.and;
import static com.sap.cds.ql.CQL.not;
import static com.sap.cds.ql.CQL.or;

import java.text.MessageFormat;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;

import com.sap.cds.impl.parser.token.CqnPlainImpl;
import com.sap.cds.ql.cqn.CqnListValue;
import com.sap.cds.ql.cqn.CqnLiteral;
import com.sap.cds.ql.cqn.CqnPlain;
import com.sap.cds.ql.cqn.CqnPredicate;
import com.sap.cds.ql.cqn.CqnReference;
import com.sap.cds.ql.cqn.CqnSyntaxException;
import com.sap.cds.ql.cqn.CqnToken;
import com.sap.cds.ql.cqn.CqnValue;
import com.sap.cds.ql.impl.Xpr;

/**
 * 
 * GRAMMAR:
 * 
 * search expression = boolean_term | boolean_term OR search_expression
 * 
 * boolean_term = boolean_factor | boolean_factor AND boolean_term
 * 
 * boolean_factor = [ NOT ] boolean_test
 * 
 * boolean test = predicate | ( search_expression )
 */

public abstract class AbstractCqnExpressionParser {

	protected LinkedList tokens;
	protected CqnToken aheadToken;
	protected int pos;

	public CqnPredicate parsePredicate(List tokenList) {
		return parsePredicate(tokenList.stream());
	}

	public CqnPredicate parsePredicate(Stream tokenStream) {
		tokens = unfold(tokenStream);

		if (tokens.isEmpty()) {
			return null;
		}

		aheadToken = tokens.getFirst();
		pos = 0;

		CqnPredicate expression = searchCondition();

		if (aheadToken != null) {
			throw unexpected();
		}

		return expression;
	}

	private CqnPredicate searchCondition() {
		CqnPredicate term = booleanTerm();
		while (is("or")) {
			term = or(term, booleanTerm());
		}

		return term;
	}

	private CqnPredicate booleanTerm() {
		CqnPredicate factor = booleanFactor();
		while (is("and")) {
			factor = and(factor, booleanFactor());
		}

		return factor;
	}

	private CqnPredicate booleanFactor() {
		if (is("not")) {
			CqnPredicate factor = booleanFactor();
			return not(factor);
		}

		return booleanTest();
	}

	private CqnPredicate booleanTest() {
		if (peek("(")) {
			return matchPredicate().orElseGet(() -> {
				expect("(");
				CqnPredicate expr = searchCondition();
				expect(")");

				return expr;
			});
		}

		if (isXpr()) {
			return matchPredicate().orElseGet(() -> new ExprParser().parsePredicate(getXpr().xpr()));
		}

		return predicate();
	}

	protected abstract CqnPredicate predicate();

	protected void nextToken() {
		tokens.pop();
		if (tokens.isEmpty()) {
			aheadToken = null;
		} else {
			aheadToken = tokens.getFirst();
			pos++;
		}
	}

	protected boolean hasNext() {
		return aheadToken != null;
	}

	protected CqnPredicate getPredicate() {
		return get(CqnPredicate.class);
	}

	protected CqnPlain getPlain() {
		return get(CqnPlain.class);
	}

	protected CqnLiteral getLiteral() {
		return get(CqnLiteral.class);
	}

	protected Xpr getXpr() {
		return get(Xpr.class);
	}

	protected CqnValue getValue() {
		return get(CqnValue.class);
	}

	protected CqnListValue getList() {
		return get(CqnListValue.class);
	}

	@SuppressWarnings("unchecked")
	protected  T get(Class clazz) {
		if (null == aheadToken) {
			throw new CqnSyntaxException("Unexpected end of token stream.");
		}
		if (clazz.isAssignableFrom(aheadToken.getClass())) {
			T value = (T) aheadToken;
			nextToken();
			return value;
		}
		throw unexpected();
	}

	// TODO name is misleading
	protected boolean is(String value) {
		if (peek(value)) {
			nextToken();
			return true;
		}
		return false;

	}

	protected boolean peek(String... values) {
		if (!isPlain()) {
			return false;
		}
		String plain = ((CqnPlain) aheadToken).plain();
		return Arrays.stream(values).anyMatch(plain::equalsIgnoreCase);
	}

	protected boolean isPredicate() {
		return aheadToken instanceof CqnPredicate;
	}

	protected boolean isPlain() {
		return aheadToken instanceof CqnPlain;
	}

	protected boolean isLiteral() {
		return aheadToken instanceof CqnLiteral;
	}

	protected boolean isRef() {
		return aheadToken instanceof CqnReference;
	}

	protected boolean isXpr() {
		return aheadToken instanceof Xpr;
	}

	protected boolean isList() {
		return aheadToken instanceof CqnListValue;
	}

	protected CqnSyntaxException unexpected() {
		String msg = MessageFormat.format("Unexpected token at position {0}: {1}", pos, aheadToken.toJson());
		return new CqnSyntaxException(msg);
	}

	protected void expect(String expected) {
		if (!is(expected)) {
			throw expecting(plain(expected));
		}
	}

	private CqnSyntaxException expecting(CqnPlainImpl plain) {
		String msg;
		if (aheadToken != null) {
			msg = MessageFormat.format("Unexpeceted token {0} at position {1}. Expecting {2}.", aheadToken.toJson(),
					pos, plain.toJson());
		} else {
			msg = MessageFormat.format("Expecting token {0} at position {1}.", plain.toJson(), pos);

		}
		return new CqnSyntaxException(msg);
	}

	private static LinkedList unfold(Stream tokens) {
		LinkedList list = new LinkedList<>();

		tokens.forEach(list::add);

		return list;
	}

	private Optional matchPredicate() {
		// stash
		LinkedList oldTokens = new LinkedList<>(tokens);
		CqnToken oldAhead = aheadToken;
		int oldPos = pos;

		try {
			return Optional.of(predicate());
		} catch (Exception ex) {
			// unstash
			tokens = oldTokens;
			aheadToken = oldAhead;
			pos = oldPos;

			// can't be parsed
			return Optional.empty();
		}
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy