Skip to content

Commit 0d734e5

Browse files
dmitriplotnikovcopybara-github
authored andcommitted
Introduce 'list' extension functions: 'slice'
Add versions to the 'lists' extension to gradually expose new functions. PiperOrigin-RevId: 780339619
1 parent cfec87b commit 0d734e5

5 files changed

Lines changed: 160 additions & 4 deletions

File tree

extensions/src/main/java/dev/cel/extensions/BUILD.bazel

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ java_library(
2727
":sets_function",
2828
":strings",
2929
"//common:options",
30+
"//common/internal:env_visitor",
31+
"//compiler",
32+
"//compiler:compiler_builder",
3033
"@maven//:com_google_guava_guava",
3134
],
3235
)

extensions/src/main/java/dev/cel/extensions/CelExtensions.java

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020
import com.google.common.collect.ImmutableSet;
2121
import com.google.common.collect.Streams;
2222
import dev.cel.common.CelOptions;
23+
import dev.cel.common.internal.EnvVisitable;
24+
import dev.cel.compiler.CelCompiler;
25+
import dev.cel.compiler.CelCompilerFactory;
26+
import dev.cel.compiler.CelCompilerLibrary;
2327
import dev.cel.extensions.CelListsExtensions.Function;
2428
import java.util.Set;
2529

@@ -35,7 +39,6 @@ public final class CelExtensions {
3539
private static final CelProtoExtensions PROTO_EXTENSIONS = new CelProtoExtensions();
3640
private static final CelBindingsExtensions BINDINGS_EXTENSIONS = new CelBindingsExtensions();
3741
private static final CelEncoderExtensions ENCODER_EXTENSIONS = new CelEncoderExtensions();
38-
private static final CelListsExtensions LISTS_EXTENSIONS_ALL = new CelListsExtensions();
3942
private static final CelRegexExtensions REGEX_EXTENSIONS = new CelRegexExtensions();
4043

4144
/**
@@ -230,7 +233,16 @@ public static CelSetsExtensions sets(CelOptions celOptions, Set<SetsFunction> fu
230233
* CelListsExtensions.Function}.
231234
*/
232235
public static CelListsExtensions lists() {
233-
return LISTS_EXTENSIONS_ALL;
236+
return new CelListsExtensions(Integer.MAX_VALUE);
237+
}
238+
239+
/**
240+
* Extended functions for List manipulation.
241+
*
242+
* <p>Refer to README.md for functions available in each version.
243+
*/
244+
public static CelListsExtensions lists(int version) {
245+
return new CelListsExtensions(version);
234246
}
235247

236248
/**
@@ -293,5 +305,16 @@ public static ImmutableSet<String> getAllFunctionNames() {
293305
.collect(toImmutableSet());
294306
}
295307

308+
public static ImmutableSet<String> getFunctionNames(CelCompilerLibrary library) {
309+
ImmutableSet.Builder<String> builder = ImmutableSet.builder();
310+
CelCompiler compiler =
311+
CelCompilerFactory.standardCelCompilerBuilder()
312+
.setStandardEnvironmentEnabled(false)
313+
.addLibraries(library)
314+
.build();
315+
((EnvVisitable) compiler).accept((name, decls) -> builder.add(name));
316+
return builder.build();
317+
}
318+
296319
private CelExtensions() {}
297320
}

extensions/src/main/java/dev/cel/extensions/CelListsExtensions.java

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
import dev.cel.runtime.CelRuntimeBuilder;
2929
import dev.cel.runtime.CelRuntimeLibrary;
3030
import java.util.Collection;
31+
import java.util.Iterator;
32+
import java.util.List;
3133
import java.util.Set;
3234

3335
/** Internal implementation of CEL lists extensions. */
@@ -37,6 +39,25 @@ final class CelListsExtensions implements CelCompilerLibrary, CelRuntimeLibrary
3739

3840
@SuppressWarnings({"unchecked"}) // Unchecked: Type-checker guarantees casting safety.
3941
public enum Function {
42+
SLICE(
43+
CelFunctionDecl.newFunctionDeclaration(
44+
"slice",
45+
CelOverloadDecl.newMemberOverload(
46+
"list_slice",
47+
"Returns a new sub-list using the indexes provided",
48+
ListType.create(LIST_PARAM_TYPE),
49+
ListType.create(LIST_PARAM_TYPE),
50+
SimpleType.INT,
51+
SimpleType.INT)),
52+
CelFunctionBinding.from(
53+
"list_slice",
54+
ImmutableList.of(Collection.class, Long.class, Long.class),
55+
(args) -> {
56+
Collection<Object> target = (Collection<Object>) args[0];
57+
long from = (Long) args[1];
58+
long to = (Long) args[2];
59+
return CelListsExtensions.slice(target, from, to);
60+
})),
4061
FLATTEN(
4162
CelFunctionDecl.newFunctionDeclaration(
4263
"flatten",
@@ -65,6 +86,31 @@ public enum Function {
6586
SimpleType.INT)),
6687
CelFunctionBinding.from("lists_range", Long.class, CelListsExtensions::genRange));
6788

89+
private static final ImmutableSet<Function> VERSION_0 = ImmutableSet.of(SLICE);
90+
91+
private static final ImmutableSet<Function> VERSION_1 =
92+
ImmutableSet.<Function>builder().addAll(VERSION_0).add(FLATTEN).build();
93+
94+
private static final ImmutableSet<Function> VERSION_2 =
95+
ImmutableSet.<Function>builder().addAll(VERSION_1).add(RANGE).build();
96+
97+
private static final ImmutableSet<Function> VERSION_LATEST = VERSION_2;
98+
99+
private static ImmutableSet<Function> byVersion(int version) {
100+
switch (version) {
101+
case 0:
102+
return Function.VERSION_0;
103+
case 1:
104+
return Function.VERSION_1;
105+
case 2:
106+
return Function.VERSION_2;
107+
case Integer.MAX_VALUE:
108+
return Function.VERSION_LATEST;
109+
default:
110+
throw new IllegalArgumentException("Unsupported 'lists' extension version " + version);
111+
}
112+
}
113+
68114
private final CelFunctionDecl functionDecl;
69115
private final ImmutableSet<CelFunctionBinding> functionBindings;
70116

@@ -80,8 +126,8 @@ String getFunction() {
80126

81127
private final ImmutableSet<Function> functions;
82128

83-
CelListsExtensions() {
84-
this.functions = ImmutableSet.copyOf(Function.values());
129+
CelListsExtensions(int version) {
130+
this(Function.byVersion(version));
85131
}
86132

87133
CelListsExtensions(Set<Function> functions) {
@@ -98,6 +144,32 @@ public void setRuntimeOptions(CelRuntimeBuilder runtimeBuilder) {
98144
functions.forEach(function -> runtimeBuilder.addFunctionBindings(function.functionBindings));
99145
}
100146

147+
private static ImmutableList<Object> slice(Collection<Object> list, long from, long to) {
148+
Preconditions.checkArgument(from >= 0 && to >= 0, "Negative indexes not supported");
149+
Preconditions.checkArgument(to >= from, "Start index must be less than or equal to end index");
150+
Preconditions.checkArgument(to <= list.size(), "List is length %s", list.size());
151+
if (list instanceof List) {
152+
List<Object> subList = ((List<Object>) list).subList((int) from, (int) to);
153+
if (subList instanceof ImmutableList) {
154+
return (ImmutableList<Object>) subList;
155+
}
156+
return ImmutableList.copyOf(subList);
157+
} else {
158+
ImmutableList.Builder<Object> builder = ImmutableList.builder();
159+
long index = 0;
160+
for (Iterator<Object> iterator = list.iterator(); iterator.hasNext(); index++) {
161+
Object element = iterator.next();
162+
if (index >= to) {
163+
break;
164+
}
165+
if (index >= from) {
166+
builder.add(element);
167+
}
168+
}
169+
return builder.build();
170+
}
171+
}
172+
101173
@SuppressWarnings("unchecked")
102174
private static ImmutableList<Object> flatten(Collection<Object> list, long depth) {
103175
Preconditions.checkArgument(depth >= 0, "Level must be non-negative");

extensions/src/main/java/dev/cel/extensions/README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -666,8 +666,22 @@ sets.intersects([[1], [2, 3]], [[1, 2], [2, 3.0]]) // true
666666
Extended functions for list manipulation. As a general note, all indices are
667667
zero-based.
668668

669+
### Slice
670+
671+
Returns a new sub-list using the indexes provided. The `from` index is
672+
inclusive, the `to` index is exclusive.
673+
674+
<list>.slice(<int>, <int>) -> <list>
675+
676+
Examples:
677+
678+
[1,2,3,4].slice(1, 3) // return [2, 3]
679+
[1,2,3,4].slice(2, 4) // return [3, 4]
680+
669681
### Flatten
670682

683+
Introduced at version: 1
684+
671685
Flattens a list by one level, or to the specified level. Providing a negative level will error.
672686

673687
Examples:
@@ -708,6 +722,8 @@ type-checker to handle type-reductions, or union types.
708722

709723
### Range
710724

725+
Introduced at version: 2
726+
711727
Given integer size n returns a list of integers from 0 to n-1. If size <= 0
712728
then return empty list.
713729

extensions/src/test/java/dev/cel/extensions/CelListsExtensionsTest.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,48 @@ public class CelListsExtensionsTest {
3535
.addRuntimeLibraries(CelExtensions.lists())
3636
.build();
3737

38+
@Test
39+
public void functionList_byVersion() {
40+
assertThat(CelExtensions.getFunctionNames(CelExtensions.lists(0))).containsExactly("slice");
41+
assertThat(CelExtensions.getFunctionNames(CelExtensions.lists(1)))
42+
.containsExactly("slice", "flatten");
43+
assertThat(CelExtensions.getFunctionNames(CelExtensions.lists(2)))
44+
.containsExactly("slice", "flatten", "lists.range");
45+
}
46+
47+
@Test
48+
@TestParameters("{expression: '[1,2,3,4].slice(0, 4) == [1,2,3,4]'}")
49+
@TestParameters("{expression: '[1,2,3,4].slice(0, 0) == []'}")
50+
@TestParameters("{expression: '[1,2,3,4].slice(1, 1) == []'}")
51+
@TestParameters("{expression: '[1,2,3,4].slice(4, 4) == []'}")
52+
@TestParameters("{expression: '[1,2,3,4].slice(1, 3) == [2, 3]'}")
53+
public void slice_success(String expression) throws Exception {
54+
boolean result = (boolean) CEL.createProgram(CEL.compile(expression).getAst()).eval();
55+
56+
assertThat(result).isTrue();
57+
}
58+
59+
@Test
60+
@TestParameters(
61+
"{expression: '[1,2,3,4].slice(3, 0)', "
62+
+ "expectedError: 'Start index must be less than or equal to end index'}")
63+
@TestParameters("{expression: '[1,2,3,4].slice(0, 10)', expectedError: 'List is length 4'}")
64+
@TestParameters(
65+
"{expression: '[1,2,3,4].slice(-5, 10)', "
66+
+ "expectedError: 'Negative indexes not supported'}")
67+
@TestParameters(
68+
"{expression: '[1,2,3,4].slice(-5, -3)', "
69+
+ "expectedError: 'Negative indexes not supported'}")
70+
public void slice_throws(String expression, String expectedError) throws Exception {
71+
assertThat(
72+
assertThrows(
73+
CelEvaluationException.class,
74+
() -> CEL.createProgram(CEL.compile(expression).getAst()).eval()))
75+
.hasCauseThat()
76+
.hasMessageThat()
77+
.contains(expectedError);
78+
}
79+
3880
@Test
3981
@TestParameters("{expression: '[].flatten() == []'}")
4082
@TestParameters("{expression: '[[1, 2]].flatten().exists(i, i == 1)'}")

0 commit comments

Comments
 (0)