
com.github.leeonky.dal.ast.node.ListScopeNode Maven / Gradle / Ivy
package com.github.leeonky.dal.ast.node;
import com.github.leeonky.dal.ast.opt.Factory;
import com.github.leeonky.dal.compiler.Notations;
import com.github.leeonky.dal.runtime.*;
import com.github.leeonky.dal.runtime.RuntimeContextBuilder.DALRuntimeContext;
import com.github.leeonky.interpreter.Clause;
import com.github.leeonky.interpreter.SyntaxException;
import com.github.leeonky.util.Zipped;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import static com.github.leeonky.dal.ast.node.DALExpression.expression;
import static com.github.leeonky.dal.ast.node.InputNode.INPUT_NODE;
import static com.github.leeonky.dal.ast.node.SortGroupNode.NOP_COMPARATOR;
import static com.github.leeonky.dal.ast.node.SymbolNode.Type.BRACKET;
import static com.github.leeonky.dal.runtime.ExpressionException.exception;
import static com.github.leeonky.dal.runtime.ExpressionException.opt1;
import static com.github.leeonky.util.Zipped.zip;
import static java.lang.String.format;
import static java.util.Comparator.naturalOrder;
import static java.util.Comparator.reverseOrder;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
public class ListScopeNode extends DALNode {
private List verificationExpressions;
private List inputExpressions;
private List> inputClauses;
private final Type type;
private final Style style;
private final Comparator comparator;
public ListScopeNode(List> clauses, Comparator comparator, Style style) {
type = guessType(clauses);
inputClauses = clauses;
this.comparator = comparator;
this.style = style;
}
public ListScopeNode(List verificationExpressions, Type type, Comparator comparator, Style style) {
this.verificationExpressions = inputExpressions = new ArrayList<>(verificationExpressions);
this.type = type;
this.comparator = comparator;
this.style = style;
}
public ListScopeNode(List> clauses) {
this(clauses, NOP_COMPARATOR, Style.LIST);
}
private List getVerificationExpressions(Data.DataList list) {
return verificationExpressions != null ? verificationExpressions : buildVerificationExpressions(list)
.stream().filter(node -> !(node instanceof ListEllipsisNode)).collect(toList());
}
private List buildVerificationExpressions(Data.DataList list) {
if (inputExpressions != null)
return inputExpressions;
return new ArrayList() {{
List> usefulInputClauses = inputClauses;
if (type == Type.FIRST_N_ITEMS)
usefulInputClauses.remove(usefulInputClauses.size() - 1);
if (type == Type.LAST_N_ITEMS) {
int negativeIndex = -1;
for (int i = usefulInputClauses.size() - 1; i >= 0; i--)
add(0, usefulInputClauses.get(i).expression(expression(INPUT_NODE, Factory.executable(Notations.EMPTY),
new SymbolNode(negativeIndex--, BRACKET))));
} else {
Zipped, Integer> zipped = zip(usefulInputClauses, list.indexes());
zipped.forEachElement((clause, index) -> add(clause.expression(
expression(INPUT_NODE, Factory.executable(Notations.EMPTY), new SymbolNode(index, BRACKET)))));
if (type == Type.ALL_ITEMS) {
if (zipped.hasLeft()) {
String message = format("Different list size\nExpected: <%d>\nActual: <%d>", usefulInputClauses.size(), zipped.index());
throw style == Style.ROW ? new DifferentCellSize(message, getPositionBegin())
: new AssertionFailure(message, getPositionBegin());
}
if (zipped.hasRight() && !list.infinite()) {
String message = format("Different list size\nExpected: <%d>\nActual: <%d>", usefulInputClauses.size(), list.size());
throw style == Style.ROW ? new DifferentCellSize(message, getPositionBegin())
: new AssertionFailure(message, getPositionBegin());
}
}
}
}};
}
@Override
protected ExpectationFactory toVerify(DALRuntimeContext context) {
return (operator, actual) -> new ExpectationFactory.Expectation() {
@Override
public Data matches() {
return equalTo();
}
@Override
public Data equalTo() {
try {
Data.DataList list = opt1(actual::list).sort(comparator);
return list.wrap().execute(() -> type == Type.CONTAINS ? verifyContainElement(context, list)
: verifyCorrespondingElement(context, getVerificationExpressions(list)));
} catch (ListMappingElementAccessException e) {
throw exception(expression -> e.toDalError(expression.left().getOperandPosition()));
}
}
@Override
public ExpectationFactory.Type type() {
return ExpectationFactory.Type.LIST;
}
};
}
// TODO tobe refactored
private List buildVerificationExpressions() {
if (inputExpressions != null)
return inputExpressions;
return new ArrayList() {{
if (type == Type.LAST_N_ITEMS) {
int negativeIndex = -1;
for (int i = inputClauses.size() - 1; i >= 0; i--) {
add(0, inputClauses.get(i).expression(expression(INPUT_NODE, Factory.executable(Notations.EMPTY),
new SymbolNode(negativeIndex--, BRACKET))));
}
} else {
for (int i = 0; i < inputClauses.size(); i++)
add(inputClauses.get(i).expression(expression(INPUT_NODE, Factory.executable(Notations.EMPTY),
new SymbolNode(i, BRACKET))));
}
}};
}
private Type guessType(List> clauses) {
List isListEllipsis = clauses.stream().map(this::isListEllipsis).collect(toList());
long ellipsesCount = isListEllipsis.stream().filter(Boolean::booleanValue).count();
if (ellipsesCount > 0) {
if (ellipsesCount == 1) {
if (isListEllipsis.get(0))
return Type.LAST_N_ITEMS;
if (isListEllipsis.get(isListEllipsis.size() - 1))
return Type.FIRST_N_ITEMS;
} else if (ellipsesCount == 2 && isListEllipsis.get(0) && isListEllipsis.get(isListEllipsis.size() - 1))
return Type.CONTAINS;
throw new SyntaxException("Invalid ellipsis", clauses.get(isListEllipsis.lastIndexOf(true))
.expression(null).getOperandPosition());
}
return Type.ALL_ITEMS;
}
@Override
public String inspect() {
if (type == Type.CONTAINS)
return inputClauses.stream().map(clause -> clause.expression(INPUT_NODE).inspect())
.collect(joining(", ", "[", "]"));
return buildVerificationExpressions().stream().map(DALNode::inspect).collect(joining(", ", "[", "]"));
}
private Data verifyContainElement(DALRuntimeContext context, Data.DataList list) {
Iterator iterator = list.indexes().iterator();
List> expected = trimFirstEllipsis();
Data result = context.wrap(null);
for (int clauseIndex = 0; clauseIndex < expected.size(); clauseIndex++) {
Clause clause = expected.get(clauseIndex);
try {
while (true) {
int elementIndex = getElementIndex(clause, iterator);
try {
clause.expression(expression(INPUT_NODE, Factory.executable(Notations.EMPTY),
new SymbolNode(elementIndex, BRACKET))).evaluate(context);
break;
} catch (AssertionFailure ignore) {
}
}
} catch (AssertionFailure exception) {
throw style == Style.LIST ? exception : new RowAssertionFailure(clauseIndex, exception);
}
}
return result;
}
private int getElementIndex(Clause clause, Iterator iterator) {
if (iterator.hasNext())
return iterator.next();
throw new AssertionFailure("No such element", clause.expression(INPUT_NODE).getOperandPosition());
}
private List> trimFirstEllipsis() {
return inputClauses.subList(1, inputClauses.size() - 1);
}
private Data verifyCorrespondingElement(DALRuntimeContext context, List expressions) {
Data result = context.wrap(null);
if (style != Style.LIST)
for (int index = 0; index < expressions.size(); index++)
try {
result = expressions.get(index).evaluateData(context);
} catch (DifferentCellSize differentCellSize) {
throw new RowAssertionFailure(index, differentCellSize);
} catch (DalException dalException) {
if (style == Style.TABLE)
throw new ElementAssertionFailure(index, dalException);
throw dalException;
}
else {
for (DALNode expression : expressions)
result = expression.evaluateData(context);
}
return result;
}
private boolean isListEllipsis(Clause clause) {
return clause.expression(INPUT_NODE) instanceof ListEllipsisNode;
}
public enum Type {
ALL_ITEMS, FIRST_N_ITEMS, LAST_N_ITEMS, CONTAINS
}
public enum Style {
LIST, TABLE, ROW
}
public static class NatureOrder extends ListScopeNode {
@SuppressWarnings("unchecked")
public NatureOrder(List> clauses) {
super(clauses, Comparator.comparing(Data::instance, (Comparator) naturalOrder()), Style.LIST);
}
@Override
public String inspect() {
return "+" + super.inspect();
}
}
public static class ReverseOrder extends ListScopeNode {
@SuppressWarnings("unchecked")
public ReverseOrder(List> clauses) {
super(clauses, Comparator.comparing(Data::instance, (Comparator) reverseOrder()), Style.LIST);
}
@Override
public String inspect() {
return "-" + super.inspect();
}
}
static class DifferentCellSize extends AssertionFailure {
public DifferentCellSize(String format, int position) {
super(format, position);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy