2828import dev .cel .common .ast .CelExpr .CelCall ;
2929import dev .cel .common .ast .CelExpr .CelList ;
3030import dev .cel .common .ast .CelExpr .CelMap ;
31+ import dev .cel .common .ast .CelExpr .CelSelect ;
3132import dev .cel .common .ast .CelExpr .CelStruct ;
3233import dev .cel .common .ast .CelExpr .CelStruct .Entry ;
3334import dev .cel .common .ast .CelReference ;
@@ -58,6 +59,7 @@ public final class ProgramPlanner {
5859 private final CelValueProvider valueProvider ;
5960 private final DefaultDispatcher dispatcher ;
6061 private final AttributeFactory attributeFactory ;
62+ private final CelContainer container ;
6163
6264 /**
6365 * Plans a {@link Program} from the provided parsed-only or type-checked {@link
@@ -168,7 +170,6 @@ private Interpretable planCall(CelExpr expr, PlannerContext ctx) {
168170 evaluatedArgs [argIndex + offset ] = plan (args .get (argIndex ), ctx );
169171 }
170172
171- // TODO: Handle all specialized calls (logical operators, conditionals, equals etc)
172173 String functionName = resolvedFunction .functionName ();
173174 Operator operator = Operator .findReverse (functionName ).orElse (null );
174175 if (operator != null ) {
@@ -177,6 +178,8 @@ private Interpretable planCall(CelExpr expr, PlannerContext ctx) {
177178 return EvalOr .create (evaluatedArgs );
178179 case LOGICAL_AND :
179180 return EvalAnd .create (evaluatedArgs );
181+ case CONDITIONAL :
182+ return EvalConditional .create (evaluatedArgs );
180183 default :
181184 // fall-through
182185 }
@@ -207,16 +210,7 @@ private Interpretable planCall(CelExpr expr, PlannerContext ctx) {
207210
208211 private Interpretable planCreateStruct (CelExpr celExpr , PlannerContext ctx ) {
209212 CelStruct struct = celExpr .struct ();
210- CelType structType =
211- typeProvider
212- .findType (struct .messageName ())
213- .orElseThrow (
214- () -> new IllegalArgumentException ("Undefined type name: " + struct .messageName ()));
215- if (!structType .kind ().equals (CelKind .STRUCT )) {
216- throw new IllegalArgumentException (
217- String .format (
218- "Expected struct type for %s, got %s" , structType .name (), structType .kind ()));
219- }
213+ StructType structType = resolveStructType (struct );
220214
221215 ImmutableList <Entry > entries = struct .entries ();
222216 String [] keys = new String [entries .size ()];
@@ -228,7 +222,7 @@ private Interpretable planCreateStruct(CelExpr celExpr, PlannerContext ctx) {
228222 values [i ] = plan (entry .value (), ctx );
229223 }
230224
231- return EvalCreateStruct .create (valueProvider , ( StructType ) structType , keys , values );
225+ return EvalCreateStruct .create (valueProvider , structType , keys , values );
232226 }
233227
234228 private Interpretable planCreateList (CelExpr celExpr , PlannerContext ctx ) {
@@ -267,7 +261,7 @@ private Interpretable planCreateMap(CelExpr celExpr, PlannerContext ctx) {
267261 private ResolvedFunction resolveFunction (
268262 CelExpr expr , ImmutableMap <Long , CelReference > referenceMap ) {
269263 CelCall call = expr .call ();
270- Optional <CelExpr > target = call .target ();
264+ Optional <CelExpr > maybeTarget = call .target ();
271265 String functionName = call .function ();
272266
273267 CelReference reference = referenceMap .get (expr .id ());
@@ -279,22 +273,89 @@ private ResolvedFunction resolveFunction(
279273 .setFunctionName (functionName )
280274 .setOverloadId (reference .overloadIds ().get (0 ));
281275
282- target .ifPresent (builder ::setTarget );
276+ maybeTarget .ifPresent (builder ::setTarget );
283277
284278 return builder .build ();
285279 }
286280 }
287281
288- // Parsed-only.
289- // TODO: Handle containers.
290- if (!target .isPresent ()) {
282+ // Parsed-only function resolution.
283+ //
284+ // There are two distinct cases we must handle:
285+ //
286+ // 1. Non-qualified function calls. This will resolve into either:
287+ // - A simple global call foo()
288+ // - A fully qualified global call through normal container resolution foo.bar.qux()
289+ // 2. Qualified function calls:
290+ // - A member call on an identifier foo.bar()
291+ // - A fully qualified global call, through normal container resolution or abbreviations
292+ // foo.bar.qux()
293+ if (!maybeTarget .isPresent ()) {
294+ for (String cand : container .resolveCandidateNames (functionName )) {
295+ CelResolvedOverload overload = dispatcher .findOverload (cand ).orElse (null );
296+ if (overload != null ) {
297+ return ResolvedFunction .newBuilder ().setFunctionName (cand ).build ();
298+ }
299+ }
300+
301+ // Normal global call
291302 return ResolvedFunction .newBuilder ().setFunctionName (functionName ).build ();
292- } else {
293- return ResolvedFunction .newBuilder ()
294- .setFunctionName (functionName )
295- .setTarget (target .get ())
296- .build ();
297303 }
304+
305+ CelExpr target = maybeTarget .get ();
306+ String qualifiedPrefix = toQualifiedName (target ).orElse (null );
307+ if (qualifiedPrefix != null ) {
308+ String qualifiedName = qualifiedPrefix + "." + functionName ;
309+ for (String cand : container .resolveCandidateNames (qualifiedName )) {
310+ CelResolvedOverload overload = dispatcher .findOverload (cand ).orElse (null );
311+ if (overload != null ) {
312+ return ResolvedFunction .newBuilder ().setFunctionName (cand ).build ();
313+ }
314+ }
315+ }
316+
317+ // Normal member call
318+ return ResolvedFunction .newBuilder ().setFunctionName (functionName ).setTarget (target ).build ();
319+ }
320+
321+ private StructType resolveStructType (CelStruct struct ) {
322+ String messageName = struct .messageName ();
323+ for (String typeName : container .resolveCandidateNames (messageName )) {
324+ CelType structType = typeProvider .findType (typeName ).orElse (null );
325+ if (structType == null ) {
326+ continue ;
327+ }
328+
329+ if (!structType .kind ().equals (CelKind .STRUCT )) {
330+ throw new IllegalArgumentException (
331+ String .format (
332+ "Expected struct type for %s, got %s" , structType .name (), structType .kind ()));
333+ }
334+
335+ return (StructType ) structType ;
336+ }
337+
338+ throw new IllegalArgumentException ("Undefined type name: " + messageName );
339+ }
340+
341+ /** Converts a given expression into a qualified name, if possible. */
342+ private Optional <String > toQualifiedName (CelExpr operand ) {
343+ switch (operand .getKind ()) {
344+ case IDENT :
345+ return Optional .of (operand .ident ().name ());
346+ case SELECT :
347+ CelSelect select = operand .select ();
348+ String maybeQualified = toQualifiedName (select .operand ()).orElse (null );
349+ if (maybeQualified != null ) {
350+ return Optional .of (maybeQualified + "." + select .field ());
351+ }
352+
353+ break ;
354+ default :
355+ // fall-through
356+ }
357+
358+ return Optional .empty ();
298359 }
299360
300361 @ AutoValue
@@ -336,17 +397,22 @@ private static PlannerContext create(CelAbstractSyntaxTree ast) {
336397 }
337398
338399 public static ProgramPlanner newPlanner (
339- CelTypeProvider typeProvider , CelValueProvider valueProvider , DefaultDispatcher dispatcher ) {
340- return new ProgramPlanner (typeProvider , valueProvider , dispatcher );
400+ CelTypeProvider typeProvider ,
401+ CelValueProvider valueProvider ,
402+ DefaultDispatcher dispatcher ,
403+ CelContainer container ) {
404+ return new ProgramPlanner (typeProvider , valueProvider , dispatcher , container );
341405 }
342406
343407 private ProgramPlanner (
344- CelTypeProvider typeProvider , CelValueProvider valueProvider , DefaultDispatcher dispatcher ) {
408+ CelTypeProvider typeProvider ,
409+ CelValueProvider valueProvider ,
410+ DefaultDispatcher dispatcher ,
411+ CelContainer container ) {
345412 this .typeProvider = typeProvider ;
346413 this .valueProvider = valueProvider ;
347- // TODO: Container support
348414 this .dispatcher = dispatcher ;
349- this .attributeFactory =
350- AttributeFactory .newAttributeFactory (CelContainer . newBuilder (). build () , typeProvider );
415+ this .container = container ;
416+ this . attributeFactory = AttributeFactory .newAttributeFactory (container , typeProvider );
351417 }
352418}
0 commit comments