org.junit.platform.launcher.tagexpression.ShuntingYard Maven / Gradle / Ivy
/*
* Copyright 2015-2019 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package org.junit.platform.launcher.tagexpression;
import static java.lang.Integer.MIN_VALUE;
import static org.junit.platform.launcher.tagexpression.Operator.nullaryOperator;
import static org.junit.platform.launcher.tagexpression.ParseStatus.emptyTagExpression;
import static org.junit.platform.launcher.tagexpression.ParseStatus.missingClosingParenthesis;
import static org.junit.platform.launcher.tagexpression.ParseStatus.missingOpeningParenthesis;
import static org.junit.platform.launcher.tagexpression.ParseStatus.success;
import static org.junit.platform.launcher.tagexpression.TagExpressions.tag;
import java.util.List;
/**
* This is based on a modified version of the
*
* Shunting-yard algorithm.
*
* @since 1.1
*/
class ShuntingYard {
private static final Operator RightParenthesis = nullaryOperator(")", -1);
private static final Operator LeftParenthesis = nullaryOperator("(", -2);
private static final Operator Sentinel = nullaryOperator("sentinel", MIN_VALUE);
private static final Token SentinelToken = new Token(-1, "");
private final Operators validOperators = new Operators();
private final Stack> expressions = new DequeStack<>();
private final Stack> operators = new DequeStack<>();
private final List tokens;
ShuntingYard(List tokens) {
this.tokens = tokens;
pushOperatorAt(SentinelToken, Sentinel);
}
public ParseResult execute() {
// @formatter:off
ParseStatus parseStatus = processTokens()
.process(this::consumeRemainingOperators)
.process(this::ensureOnlySingleExpressionRemains);
// @formatter:on
if (parseStatus.isError()) {
return ParseResults.error(parseStatus.errorMessage);
}
return ParseResults.success(expressions.pop().element);
}
private ParseStatus processTokens() {
ParseStatus parseStatus = success();
for (Token token : tokens) {
parseStatus = parseStatus.process(() -> process(token));
}
return parseStatus;
}
private ParseStatus process(Token token) {
if (LeftParenthesis.represents(token.string())) {
pushOperatorAt(token, LeftParenthesis);
return success();
}
if (RightParenthesis.represents(token.string())) {
return findMatchingLeftParenthesis(token);
}
if (validOperators.isOperator(token.string())) {
Operator operator = validOperators.operatorFor(token.string());
return findOperands(token, operator);
}
pushExpressionAt(token, tag(token.string()));
return success();
}
private ParseStatus findMatchingLeftParenthesis(Token token) {
while (!operators.isEmpty()) {
TokenWith tokenWithWithOperator = operators.pop();
Operator operator = tokenWithWithOperator.element;
if (LeftParenthesis.equals(operator)) {
return success();
}
ParseStatus parseStatus = operator.createAndAddExpressionTo(expressions, tokenWithWithOperator.token);
if (parseStatus.isError()) {
return parseStatus;
}
}
return missingOpeningParenthesis(token, RightParenthesis.representation());
}
private ParseStatus findOperands(Token token, Operator currentOperator) {
while (currentOperator.hasLowerPrecedenceThan(previousOperator())
|| currentOperator.hasSamePrecedenceAs(previousOperator()) && currentOperator.isLeftAssociative()) {
TokenWith tokenWithWithOperator = operators.pop();
ParseStatus parseStatus = tokenWithWithOperator.element.createAndAddExpressionTo(expressions,
tokenWithWithOperator.token);
if (parseStatus.isError()) {
return parseStatus;
}
}
pushOperatorAt(token, currentOperator);
return success();
}
private Operator previousOperator() {
return operators.peek().element;
}
private void pushExpressionAt(Token token, TagExpression tagExpression) {
expressions.push(new TokenWith<>(token, tagExpression));
}
private void pushOperatorAt(Token token, Operator operator) {
operators.push(new TokenWith<>(token, operator));
}
private ParseStatus consumeRemainingOperators() {
while (!operators.isEmpty()) {
TokenWith tokenWithWithOperator = operators.pop();
Operator operator = tokenWithWithOperator.element;
if (LeftParenthesis.equals(operator)) {
return missingClosingParenthesis(tokenWithWithOperator.token, operator.representation());
}
ParseStatus parseStatus = operator.createAndAddExpressionTo(expressions, tokenWithWithOperator.token);
if (parseStatus.isError()) {
return parseStatus;
}
}
return success();
}
private ParseStatus ensureOnlySingleExpressionRemains() {
if (expressions.size() == 1) {
return success();
}
if (expressions.isEmpty()) {
return emptyTagExpression();
}
TokenWith rhs = expressions.pop();
TokenWith lhs = expressions.pop();
return ParseStatus.missingOperatorBetween(lhs, rhs);
}
}