Skip to content

Commit 887ff30

Browse files
authored
Specialize macro list folds (#929)
Recognize map and filter macro fold shapes during planning and build result lists with a mutable evaluation-local buffer. This avoids repeated accumulator list copying while preserving generic fold fallback for non-standard accumulator references.
1 parent f0e77f1 commit 887ff30

4 files changed

Lines changed: 398 additions & 13 deletions

File tree

core/src/main/java/org/projectnessie/cel/interpreter/Interpretable.java

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,18 @@
2828
import static org.projectnessie.cel.interpreter.Coster.Cost.estimateCost;
2929
import static org.projectnessie.cel.interpreter.Coster.costOf;
3030

31+
import java.util.ArrayList;
3132
import java.util.Arrays;
3233
import java.util.HashMap;
34+
import java.util.List;
3335
import java.util.Map;
3436
import java.util.Objects;
3537
import java.util.Set;
3638
import org.projectnessie.cel.common.operators.Operator;
3739
import org.projectnessie.cel.common.types.Err;
3840
import org.projectnessie.cel.common.types.IterableT;
3941
import org.projectnessie.cel.common.types.IteratorT;
42+
import org.projectnessie.cel.common.types.ListT;
4043
import org.projectnessie.cel.common.types.MapT;
4144
import org.projectnessie.cel.common.types.Overloads;
4245
import org.projectnessie.cel.common.types.StringT;
@@ -49,6 +52,7 @@
4952
import org.projectnessie.cel.common.types.traits.FieldTester;
5053
import org.projectnessie.cel.common.types.traits.Negater;
5154
import org.projectnessie.cel.common.types.traits.Receiver;
55+
import org.projectnessie.cel.common.types.traits.Sizer;
5256
import org.projectnessie.cel.common.types.traits.Trait;
5357
import org.projectnessie.cel.interpreter.Activation.VarActivation;
5458
import org.projectnessie.cel.interpreter.AttributeFactory.Attribute;
@@ -1108,6 +1112,111 @@ public String toString() {
11081112
}
11091113
}
11101114

1115+
final class EvalListFold extends AbstractEval implements Coster {
1116+
final String iterVar;
1117+
final Interpretable iterRange;
1118+
final Interpretable filter;
1119+
final Interpretable transform;
1120+
private final TypeAdapter adapter;
1121+
1122+
EvalListFold(
1123+
long id,
1124+
String iterVar,
1125+
Interpretable iterRange,
1126+
Interpretable filter,
1127+
Interpretable transform,
1128+
TypeAdapter adapter) {
1129+
super(id);
1130+
this.iterVar = iterVar;
1131+
this.iterRange = iterRange;
1132+
this.filter = filter;
1133+
this.transform = transform;
1134+
this.adapter = adapter;
1135+
}
1136+
1137+
@Override
1138+
public Val eval(org.projectnessie.cel.interpreter.Activation ctx) {
1139+
Val foldRange = iterRange.eval(ctx);
1140+
if (!foldRange.type().hasTrait(Trait.IterableType)) {
1141+
return valOrErr(
1142+
foldRange, "got '%s', expected iterable type", foldRange.getClass().getName());
1143+
}
1144+
1145+
VarActivation iterCtx = new VarActivation();
1146+
iterCtx.parent = ctx;
1147+
iterCtx.name = iterVar;
1148+
List<Val> values = new ArrayList<>(listCapacity(foldRange));
1149+
IteratorT it = ((IterableT) foldRange).iterator();
1150+
while (it.hasNext() == True) {
1151+
iterCtx.val = it.next();
1152+
1153+
if (filter != null) {
1154+
Val include = filter.eval(iterCtx);
1155+
if (include == False) {
1156+
continue;
1157+
}
1158+
if (include != True) {
1159+
return noSuchOverload(null, Operator.Conditional.id, include);
1160+
}
1161+
}
1162+
1163+
Val value = transform.eval(iterCtx);
1164+
if (isUnknownOrError(value)) {
1165+
return value;
1166+
}
1167+
values.add(value);
1168+
}
1169+
return ListT.newValArrayList(adapter, values.toArray(new Val[0]));
1170+
}
1171+
1172+
private int listCapacity(Val foldRange) {
1173+
if (foldRange.type().hasTrait(Trait.SizerType)) {
1174+
long size = ((Sizer) foldRange).size().intValue();
1175+
if (size > 0 && size <= Integer.MAX_VALUE) {
1176+
return (int) size;
1177+
}
1178+
}
1179+
return 0;
1180+
}
1181+
1182+
@Override
1183+
public Cost cost() {
1184+
Cost range = estimateCost(iterRange);
1185+
Cost result = estimateCost(transform);
1186+
if (filter != null) {
1187+
result = result.add(estimateCost(filter));
1188+
}
1189+
Val foldRange = iterRange.eval(emptyActivation());
1190+
if (!foldRange.type().hasTrait(Trait.IterableType)) {
1191+
return Cost.Unknown;
1192+
}
1193+
long rangeCnt = 0L;
1194+
IteratorT it = ((IterableT) foldRange).iterator();
1195+
while (it.hasNext() == True) {
1196+
it.next();
1197+
rangeCnt++;
1198+
}
1199+
return range.add(result.multiply(rangeCnt));
1200+
}
1201+
1202+
@Override
1203+
public String toString() {
1204+
return "EvalListFold{"
1205+
+ "id="
1206+
+ id
1207+
+ ", iterVar='"
1208+
+ iterVar
1209+
+ '\''
1210+
+ ", iterRange="
1211+
+ iterRange
1212+
+ ", filter="
1213+
+ filter
1214+
+ ", transform="
1215+
+ transform
1216+
+ '}';
1217+
}
1218+
}
1219+
11111220
// Optional Intepretable implementations that specialize, subsume, or extend the core evaluation
11121221
// plan via decorators.
11131222

@@ -1677,6 +1786,65 @@ public String toString() {
16771786
}
16781787
}
16791788

1789+
/** EvalExhaustiveListFold evaluates every filter and transform without short-circuiting. */
1790+
final class EvalExhaustiveListFold extends AbstractEval implements Coster {
1791+
private final EvalListFold fold;
1792+
1793+
EvalExhaustiveListFold(EvalListFold fold) {
1794+
super(fold.id);
1795+
this.fold = fold;
1796+
}
1797+
1798+
@Override
1799+
public Val eval(org.projectnessie.cel.interpreter.Activation ctx) {
1800+
Val foldRange = fold.iterRange.eval(ctx);
1801+
if (!foldRange.type().hasTrait(Trait.IterableType)) {
1802+
return valOrErr(
1803+
foldRange, "got '%s', expected iterable type", foldRange.getClass().getName());
1804+
}
1805+
1806+
VarActivation iterCtx = new VarActivation();
1807+
iterCtx.parent = ctx;
1808+
iterCtx.name = fold.iterVar;
1809+
List<Val> values = new ArrayList<>(fold.listCapacity(foldRange));
1810+
Val result = null;
1811+
IteratorT it = ((IterableT) foldRange).iterator();
1812+
while (it.hasNext() == True) {
1813+
iterCtx.val = it.next();
1814+
1815+
Val include = fold.filter != null ? fold.filter.eval(iterCtx) : True;
1816+
Val value = fold.transform.eval(iterCtx);
1817+
if (include == False) {
1818+
continue;
1819+
}
1820+
if (include != True) {
1821+
result = noSuchOverload(null, Operator.Conditional.id, include);
1822+
continue;
1823+
}
1824+
if (result == null) {
1825+
if (isUnknownOrError(value)) {
1826+
result = value;
1827+
} else {
1828+
values.add(value);
1829+
}
1830+
}
1831+
}
1832+
return result != null
1833+
? result
1834+
: ListT.newValArrayList(fold.adapter, values.toArray(new Val[0]));
1835+
}
1836+
1837+
@Override
1838+
public Cost cost() {
1839+
return fold.cost();
1840+
}
1841+
1842+
@Override
1843+
public String toString() {
1844+
return "EvalExhaustiveListFold{" + fold + '}';
1845+
}
1846+
}
1847+
16801848
/** evalAttr evaluates an Attribute value. */
16811849
final class EvalAttr extends AbstractEval
16821850
implements InterpretableAttribute, Coster, Qualifier, Attribute {

core/src/main/java/org/projectnessie/cel/interpreter/InterpretableDecorator.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,11 @@
3535
import org.projectnessie.cel.interpreter.Interpretable.EvalExhaustiveAnd;
3636
import org.projectnessie.cel.interpreter.Interpretable.EvalExhaustiveConditional;
3737
import org.projectnessie.cel.interpreter.Interpretable.EvalExhaustiveFold;
38+
import org.projectnessie.cel.interpreter.Interpretable.EvalExhaustiveListFold;
3839
import org.projectnessie.cel.interpreter.Interpretable.EvalExhaustiveOr;
3940
import org.projectnessie.cel.interpreter.Interpretable.EvalFold;
4041
import org.projectnessie.cel.interpreter.Interpretable.EvalList;
42+
import org.projectnessie.cel.interpreter.Interpretable.EvalListFold;
4143
import org.projectnessie.cel.interpreter.Interpretable.EvalMap;
4244
import org.projectnessie.cel.interpreter.Interpretable.EvalOr;
4345
import org.projectnessie.cel.interpreter.Interpretable.EvalSetMembership;
@@ -107,6 +109,9 @@ static InterpretableDecorator decDisableShortcircuits() {
107109
expr.step,
108110
expr.result);
109111
}
112+
if (i instanceof EvalListFold) {
113+
return new EvalExhaustiveListFold((EvalListFold) i);
114+
}
110115
if (i instanceof InterpretableAttribute) {
111116
InterpretableAttribute expr = (InterpretableAttribute) i;
112117
if (expr.attr() instanceof ConditionalAttribute) {

0 commit comments

Comments
 (0)