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

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

There is a newer version: 3.8.0
Show newest version
/*******************************************************************
 * © 2020 SAP SE or an SAP affiliate company. All rights reserved. *
 *******************************************************************/
package com.sap.cds.impl.parser;

import static com.sap.cds.impl.builder.model.ArithmeticExpr.dividedBy;
import static com.sap.cds.impl.builder.model.ArithmeticExpr.minus;
import static com.sap.cds.impl.builder.model.ArithmeticExpr.plus;
import static com.sap.cds.impl.builder.model.ArithmeticExpr.times;
import static com.sap.cds.impl.builder.model.ArithmeticNegation.negate;
import static com.sap.cds.impl.builder.model.StructuredTypeRefImpl.typeRef;
import static com.sap.cds.impl.parser.token.RefSegmentImpl.refSegment;
import static com.sap.cds.ql.cqn.CqnComparisonPredicate.Operator.EQ;
import static com.sap.cds.ql.cqn.CqnComparisonPredicate.Operator.GE;
import static com.sap.cds.ql.cqn.CqnComparisonPredicate.Operator.GT;
import static com.sap.cds.ql.cqn.CqnComparisonPredicate.Operator.IS;
import static com.sap.cds.ql.cqn.CqnComparisonPredicate.Operator.LE;
import static com.sap.cds.ql.cqn.CqnComparisonPredicate.Operator.LT;
import static com.sap.cds.ql.cqn.CqnComparisonPredicate.Operator.NE;
import static java.util.stream.Collectors.toList;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.stream.Stream;

import com.sap.cds.impl.builder.model.BetweenPredicate;
import com.sap.cds.impl.builder.model.ComparisonPredicate;
import com.sap.cds.impl.builder.model.CqnNull;
import com.sap.cds.impl.builder.model.ExistsSubQuery;
import com.sap.cds.impl.builder.model.InPredicate;
import com.sap.cds.impl.builder.model.MatchPredicate;
import com.sap.cds.impl.parser.builder.ExpressionBuilder;
import com.sap.cds.ql.Predicate;
import com.sap.cds.ql.cqn.CqnComparisonPredicate.Operator;
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.CqnReference.Segment;
import com.sap.cds.ql.cqn.CqnSelect;
import com.sap.cds.ql.cqn.CqnStructuredTypeRef;
import com.sap.cds.ql.cqn.CqnSubQuery;
import com.sap.cds.ql.cqn.CqnSyntaxException;
import com.sap.cds.ql.cqn.CqnToken;
import com.sap.cds.ql.cqn.CqnValue;

/**
 * 
 * GRAMMAR:
 * 
 * predicate = value pred_op value
 * 
 * pred_op = '=' | '>' | '<' | '>=' | '<=' | '<>' | 'is' |
 * 'in' | 'between' `
 * 
 * value = numericExpression | val
 * 
 * numericExpression = numericTerm [ ( '+' | '-' ) numericTerm ]*
 * 
 * numericTerm = numericPrimary [ ( '*' | '/' ) numericPrimary ]*
 * 
 * numericPrimary = '(' numericExpression ')' | numericValue
 * 
 * numericValue = NUM_VAL | PLAIN_NUM | REF | PARAM | FUNC
 * 
 * val = STRING_VAL | PLAIN_STRING
 */
public class ExprParser extends AbstractCqnExpressionParser {

	public CqnValue parseValue(Stream tokens) {
		return parseValue(tokens.collect(toList()));
	}

	public CqnValue parseValue(List tokenList) {
		if (tokenList.isEmpty()) {
			return null;
		}

		tokens = new LinkedList<>(tokenList);
		aheadToken = tokens.getFirst();
		pos = 0;

		CqnValue value = value();

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

		return value;
	}

	private CqnValue value() {
		if (peek("(", "-", "+") || isNumeric()) {
			return numericExpression();
		}
		return getValue();
	}

	private CqnValue numericExpression() {
		CqnValue expr = numericTerm();
		while (peek("+", "-")) {
			if (is("+")) {
				expr = plus(expr, numericTerm());
			} else if (is("-")) {
				expr = minus(expr, numericTerm());
			}
		}

		return expr;
	}

	private CqnValue numericTerm() {
		CqnValue term = numericFactor();
		while (peek("*", "/")) {
			if (is("*")) {
				term = times(term, numericFactor());
			} else if (is("/")) {
				term = dividedBy(term, numericFactor());
			}
		}

		return term;
	}

	private CqnValue numericFactor() {
		if (is("+")) {
			// nop
		}
		if (is("-")) {
			CqnValue val = numericFactor();
			return negate(val);
		}

		return numericPrimary();
	}

	private CqnValue numericPrimary() {
		if (is("(")) {
			CqnValue expr = numericExpression();
			expect(")");

			return expr;
		}

		return numericValue();
	}

	private CqnValue numericValue() {
		if (!isNumeric()) {
			throw new CqnSyntaxException("Unexpeceted non-numeric token");
		}

		return getValue();
	}

	@Override
	protected CqnPredicate predicate() {
		if (isPredicate()) {
			return getPredicate();
		}
		if (is("exists")) {
			return exists();
		}
		CqnValue value = value();
		CqnPlain plain = getPlain();
		switch (plain.plain().toLowerCase(Locale.US)) {
		case "=":
			return comparison(value, EQ);
		case ">":
			return comparison(value, GT);
		case "<":
			return comparison(value, LT);
		case ">=":
			return comparison(value, GE);
		case "<=":
			return comparison(value, LE);
		case "<>":
			return comparison(value, NE);
		case "is":
			return isNull(value, IS);
		case "in":
			return in(value);
		case "between":
			return between(value);
		default:
			List tokens = new ArrayList<>();
			tokens.add(value);
			tokens.add(plain);
			while (hasNext() && !peek("and") && !peek("or") && !peek(")")) {
				tokens.add(aheadToken);
				nextToken();
			}

			return ExpressionBuilder.xpr(tokens.stream()).buildPredicate();
		}

	}

	private CqnPredicate exists() {
		if (isRef()) {
			CqnReference ref = get(CqnReference.class);
			int n = ref.segments().size();
			List segments = new ArrayList<>(ref.segments().subList(0, n - 1));
			segments.add(refSegment(ref.lastSegment()));
			CqnStructuredTypeRef typeRef = typeRef(segments);

			CqnPredicate predicate = ref.targetSegment().filter().orElse(null);

			return MatchPredicate.any(typeRef, predicate);
		}

		return new ExistsSubQuery(getSelect());
	}

	private CqnSelect getSelect() {
		if (aheadToken instanceof CqnSubQuery) {
			CqnSelect select = ((CqnSubQuery) aheadToken).query();
			nextToken();
			return select;
		}
		return get(CqnSelect.class);
	}

	private CqnPredicate isNull(CqnValue value, Operator is) {
		boolean negate = false;
		if (is("not")) {
			negate = true;
		}
		expect("null");

		Predicate pred = ComparisonPredicate.pred(value, IS, CqnNull.getInstance());
		if (negate) {
			pred = pred.not();
		}

		return pred;
	}

	private CqnPredicate between(CqnValue value) {
		CqnValue low = getValue();
		expect("and"); // between needs special treatment because of this 'and'
		CqnValue high = getValue();

		return BetweenPredicate.between(value, low, high);
	}

	private CqnPredicate in(CqnValue value) {
		expect("(");
		List vs = new ArrayList<>();
		vs.add(getValue());
		while (is(",")) {
			vs.add(getValue());
		}
		if (!is(")")) {
			throw new CqnSyntaxException("Missing closing parenthesis");
		}
		return InPredicate.in(value, vs.stream());
	}

	private CqnPredicate comparison(CqnValue lhs, Operator op) {

		return ComparisonPredicate.pred(lhs, op, value());
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy