Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions runtime/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -255,3 +255,11 @@ java_library(
visibility = ["//:internal"],
exports = ["//runtime/src/main/java/dev/cel/runtime:metadata"],
)

java_library(
name = "concatenated_list_view",
visibility = ["//:internal"],
exports = [
"//runtime/src/main/java/dev/cel/runtime:concatenated_list_view",
],
)
4 changes: 3 additions & 1 deletion runtime/src/main/java/dev/cel/runtime/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -1142,7 +1142,9 @@ java_library(
name = "concatenated_list_view",
srcs = ["ConcatenatedListView.java"],
# used_by_android
visibility = ["//visibility:private"],
tags = [
],
deps = ["//common/annotations"],
)

java_library(
Expand Down
11 changes: 8 additions & 3 deletions runtime/src/main/java/dev/cel/runtime/ConcatenatedListView.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License aj
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
Expand All @@ -14,6 +14,7 @@

package dev.cel.runtime;

import dev.cel.common.annotations.Internal;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collection;
Expand All @@ -27,16 +28,20 @@
* comprehensions that dispatch `add_list` to concat N lists together).
*
* <p>This does not support any of the standard list operations from {@link java.util.List}.
*

* <p>CEL Library Internals. Do Not Use.
*/
final class ConcatenatedListView<E> extends AbstractList<E> {
@Internal
public final class ConcatenatedListView<E> extends AbstractList<E> {
private final List<List<? extends E>> sourceLists;
private int totalSize = 0;

ConcatenatedListView() {
this.sourceLists = new ArrayList<>();
}

ConcatenatedListView(Collection<? extends E> collection) {
public ConcatenatedListView(Collection<? extends E> collection) {
this();
addAll(collection);
}
Expand Down
17 changes: 17 additions & 0 deletions runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ java_library(
":eval_create_list",
":eval_create_map",
":eval_create_struct",
":eval_fold",
":eval_or",
":eval_test_only",
":eval_unary",
Expand Down Expand Up @@ -309,6 +310,22 @@ java_library(
],
)

java_library(
name = "eval_fold",
srcs = ["EvalFold.java"],
deps = [
":planned_interpretable",
"//runtime:concatenated_list_view",
"//runtime:evaluation_exception",
"//runtime:evaluation_listener",
"//runtime:function_resolver",
"//runtime:interpretable",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
"@maven//:org_jspecify_jspecify",
],
)

java_library(
name = "eval_helpers",
srcs = ["EvalHelpers.java"],
Expand Down
204 changes: 204 additions & 0 deletions runtime/src/main/java/dev/cel/runtime/planner/EvalFold.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package dev.cel.runtime.planner;

import com.google.common.collect.ImmutableList;
import com.google.errorprone.annotations.Immutable;
import dev.cel.runtime.CelEvaluationException;
import dev.cel.runtime.CelEvaluationListener;
import dev.cel.runtime.CelFunctionResolver;
import dev.cel.runtime.ConcatenatedListView;
import dev.cel.runtime.GlobalResolver;
import java.util.Collection;
import java.util.Map;
import org.jspecify.annotations.Nullable;

@Immutable
final class EvalFold extends PlannedInterpretable {

private final String accuVar;
private final PlannedInterpretable accuInit;
private final String iterVar;
private final String iterVar2;
private final PlannedInterpretable iterRange;
private final PlannedInterpretable condition;
private final PlannedInterpretable loopStep;
private final PlannedInterpretable result;

static EvalFold create(
long exprId,
String accuVar,
PlannedInterpretable accuInit,
String iterVar,
String iterVar2,
PlannedInterpretable iterRange,
PlannedInterpretable loopCondition,
PlannedInterpretable loopStep,
PlannedInterpretable result) {
return new EvalFold(
exprId, accuVar, accuInit, iterVar, iterVar2, iterRange, loopCondition, loopStep, result);
}

private EvalFold(
long exprId,
String accuVar,
PlannedInterpretable accuInit,
String iterVar,
String iterVar2,
PlannedInterpretable iterRange,
PlannedInterpretable condition,
PlannedInterpretable loopStep,
PlannedInterpretable result) {
super(exprId);
this.accuVar = accuVar;
this.accuInit = accuInit;
this.iterVar = iterVar;
this.iterVar2 = iterVar2;
this.iterRange = iterRange;
this.condition = condition;
this.loopStep = loopStep;
this.result = result;
}

@Override
public Object eval(GlobalResolver resolver) throws CelEvaluationException {
Object iterRangeRaw = iterRange.eval(resolver);
Folder folder = new Folder(resolver, accuVar, iterVar, iterVar2);
folder.accuVal = maybeWrapAccumulator(accuInit.eval(folder));

Object result;
if (iterRangeRaw instanceof Map) {
result = evalMap((Map<?, ?>) iterRangeRaw, folder);
} else if (iterRangeRaw instanceof Collection) {
result = evalList((Collection<?>) iterRangeRaw, folder);
} else {
throw new IllegalArgumentException("Unexpected iter_range type: " + iterRangeRaw.getClass());
}

return maybeUnwrapAccumulator(result);
}

@Override
public Object eval(GlobalResolver resolver, CelEvaluationListener listener) {
// TODO: Implement support
throw new UnsupportedOperationException("Not yet supported");
}

@Override
public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver) {
// TODO: Implement support
throw new UnsupportedOperationException("Not yet supported");
}

@Override
public Object eval(
GlobalResolver resolver,
CelFunctionResolver lateBoundFunctionResolver,
CelEvaluationListener listener) {
// TODO: Implement support
throw new UnsupportedOperationException("Not yet supported");
}

private Object evalMap(Map<?, ?> iterRange, Folder folder) throws CelEvaluationException {
for (Map.Entry<?, ?> entry : iterRange.entrySet()) {
folder.iterVarVal = entry.getKey();
if (!iterVar2.isEmpty()) {
folder.iterVar2Val = entry.getValue();
}

boolean cond = (boolean) condition.eval(folder);
if (!cond) {
return result.eval(folder);
}

// TODO: Introduce comprehension safety controls, such as iteration limit.
folder.accuVal = loopStep.eval(folder);
}
return result.eval(folder);
}

private Object evalList(Collection<?> iterRange, Folder folder) throws CelEvaluationException {
int index = 0;
for (Object item : iterRange) {
if (iterVar2.isEmpty()) {
folder.iterVarVal = item;
} else {
folder.iterVarVal = (long) index;
folder.iterVar2Val = item;
}

boolean cond = (boolean) condition.eval(folder);
if (!cond) {
return result.eval(folder);
}

folder.accuVal = loopStep.eval(folder);
index++;
}
return result.eval(folder);
}

private static Object maybeWrapAccumulator(Object val) {
if (val instanceof Collection) {
return new ConcatenatedListView<>((Collection<?>) val);
}
// TODO: Introduce mutable map support (for comp v2)
return val;
}

private static Object maybeUnwrapAccumulator(Object val) {
if (val instanceof ConcatenatedListView) {
return ImmutableList.copyOf((ConcatenatedListView<?>) val);
}

// TODO: Introduce mutable map support (for comp v2)
return val;
}

private static class Folder implements GlobalResolver {
private final GlobalResolver resolver;
private final String accuVar;
private final String iterVar;
private final String iterVar2;

private Object iterVarVal;
private Object iterVar2Val;
private Object accuVal;

private Folder(GlobalResolver resolver, String accuVar, String iterVar, String iterVar2) {
this.resolver = resolver;
this.accuVar = accuVar;
this.iterVar = iterVar;
this.iterVar2 = iterVar2;
}

@Override
public @Nullable Object resolve(String name) {
if (name.equals(accuVar)) {
return accuVal;
}

if (name.equals(iterVar)) {
return this.iterVarVal;
}

if (name.equals(iterVar2)) {
return this.iterVar2Val;
}

return resolver.resolve(name);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import dev.cel.common.ast.CelConstant;
import dev.cel.common.ast.CelExpr;
import dev.cel.common.ast.CelExpr.CelCall;
import dev.cel.common.ast.CelExpr.CelComprehension;
import dev.cel.common.ast.CelExpr.CelList;
import dev.cel.common.ast.CelExpr.CelMap;
import dev.cel.common.ast.CelExpr.CelSelect;
Expand Down Expand Up @@ -94,10 +95,12 @@ private PlannedInterpretable plan(CelExpr celExpr, PlannerContext ctx) {
return planCreateStruct(celExpr, ctx);
case MAP:
return planCreateMap(celExpr, ctx);
case COMPREHENSION:
return planComprehension(celExpr, ctx);
case NOT_SET:
throw new UnsupportedOperationException("Unsupported kind: " + celExpr.getKind());
default:
throw new IllegalArgumentException("Not yet implemented kind: " + celExpr.getKind());
throw new UnsupportedOperationException("Unexpected kind: " + celExpr.getKind());
}
}

Expand Down Expand Up @@ -280,6 +283,27 @@ private PlannedInterpretable planCreateMap(CelExpr celExpr, PlannerContext ctx)
return EvalCreateMap.create(celExpr.id(), keys, values);
}

private PlannedInterpretable planComprehension(CelExpr expr, PlannerContext ctx) {
CelComprehension comprehension = expr.comprehension();

PlannedInterpretable accuInit = plan(comprehension.accuInit(), ctx);
PlannedInterpretable iterRange = plan(comprehension.iterRange(), ctx);
PlannedInterpretable loopCondition = plan(comprehension.loopCondition(), ctx);
PlannedInterpretable loopStep = plan(comprehension.loopStep(), ctx);
PlannedInterpretable result = plan(comprehension.result(), ctx);

return EvalFold.create(
expr.id(),
comprehension.accuVar(),
accuInit,
comprehension.iterVar(),
comprehension.iterVar2(),
iterRange,
loopCondition,
loopStep,
result);
}

/**
* resolveFunction determines the call target, function name, and overload name (when unambiguous)
* from the given call expr.
Expand Down
Loading
Loading