diff --git a/common/src/main/java/dev/cel/common/ast/BUILD.bazel b/common/src/main/java/dev/cel/common/ast/BUILD.bazel index b26181303..ac4df5487 100644 --- a/common/src/main/java/dev/cel/common/ast/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/ast/BUILD.bazel @@ -13,6 +13,7 @@ AST_SOURCES = [ "CelExpr.java", "CelExprFormatter.java", "CelReference.java", + "Expression.java", ] # keep sorted diff --git a/common/src/main/java/dev/cel/common/ast/CelExpr.java b/common/src/main/java/dev/cel/common/ast/CelExpr.java index f494aa114..3970c8598 100644 --- a/common/src/main/java/dev/cel/common/ast/CelExpr.java +++ b/common/src/main/java/dev/cel/common/ast/CelExpr.java @@ -31,104 +31,102 @@ import java.util.Optional; /** - * An abstract representation of a common expression. + * An abstract representation of a common expression. Refer to {@link Expression} for details. * *
This is the native type equivalent of Expr message in syntax.proto. - * - *
Expressions are abstractly represented as a collection of identifiers, select statements, - * function calls, literals, and comprehensions. All operators with the exception of the '.' - * operator are modelled as function calls. This makes it easy to represent new operators into the - * existing AST. - * - *
All references within expressions must resolve to a [Decl][] provided at type-check for an - * expression to be valid. A reference may either be a bare identifier `name` or a qualified - * identifier `google.api.name`. References may either refer to a value or a function declaration. - * - *
For example, the expression `google.api.name.startsWith('expr')` references the declaration - * `google.api.name` within a [Expr.Select][] expression, and the function declaration `startsWith`. */ @AutoValue @Internal @Immutable -public abstract class CelExpr { +public abstract class CelExpr implements Expression { - /** - * Required. An id assigned to this node by the parser which is unique in a given expression tree. - * This is used to associate type information and other attributes to a node in the parse tree. - */ + @Override public abstract long id(); /** Represents the variant of the expression. */ public abstract ExprKind exprKind(); + @Override + public ExprKind.Kind getKind() { + return exprKind().getKind(); + } + /** - * Gets the underlying constant expression. + * {@inheritDoc} * * @throws UnsupportedOperationException if expression is not {@link Kind#CONSTANT}. */ + @Override public CelConstant constant() { return exprKind().constant(); } /** - * Gets the underlying identifier expression. + * {@inheritDoc} * * @throws UnsupportedOperationException if expression is not {@link Kind#IDENT}. */ + @Override public CelIdent ident() { return exprKind().ident(); } /** - * Gets the underlying select expression. + * {@inheritDoc} * * @throws UnsupportedOperationException if expression is not {@link Kind#SELECT}. */ + @Override public CelSelect select() { return exprKind().select(); } /** - * Gets the underlying call expression. + * {@inheritDoc} * * @throws UnsupportedOperationException if expression is not {@link Kind#CALL}. */ + @Override public CelCall call() { return exprKind().call(); } /** - * Gets the underlying createList expression. + * {@inheritDoc} * * @throws UnsupportedOperationException if expression is not {@link Kind#CREATE_LIST}. */ + @Override public CelCreateList createList() { return exprKind().createList(); } /** - * Gets the underlying createStruct expression. + * {@inheritDoc} * * @throws UnsupportedOperationException if expression is not {@link Kind#CREATE_STRUCT}. */ + @Override public CelCreateStruct createStruct() { return exprKind().createStruct(); } /** - * Gets the underlying createMap expression. + * {@inheritDoc} * * @throws UnsupportedOperationException if expression is not {@link Kind#createMap}. */ + @Override public CelCreateMap createMap() { return exprKind().createMap(); } /** - * Gets the underlying comprehension expression. + * {@inheritDoc} * * @throws UnsupportedOperationException if expression is not {@link Kind#COMPREHENSION}. */ + @Override public CelComprehension comprehension() { return exprKind().comprehension(); } @@ -401,12 +399,9 @@ public abstract static class CelNotSet {} /** An identifier expression. e.g. `request`. */ @AutoValue @Immutable - public abstract static class CelIdent { - /** - * Required. Holds a single, unqualified identifier, possibly preceded by a '.'. - * - *
Qualified names are represented by the [Expr.Select][] expression.
- */
+ public abstract static class CelIdent implements Ident {
+
+ @Override
public abstract String name();
/** Builder for CelIdent. */
@@ -429,29 +424,15 @@ public static Builder newBuilder() {
/** A field selection expression. e.g. `request.auth`. */
@AutoValue
@Immutable
- public abstract static class CelSelect {
+ public abstract static class CelSelect implements Expression.Select For example, in the select expression `request.auth`, the `request` portion of the
- * expression is the `operand`.
- */
+ @Override
public abstract CelExpr operand();
- /**
- * Required. The name of the field to select.
- *
- * For example, in the select expression `request.auth`, the `auth` portion of the expression
- * would be the `field`.
- */
+ @Override
public abstract String field();
- /**
- * Whether the select is to be interpreted as a field presence test.
- *
- * This results from the macro `has(request.auth)`.
- */
+ @Override
public abstract boolean testOnly();
/** Builder for CelSelect. */
@@ -483,25 +464,18 @@ public static Builder newBuilder() {
}
}
- /**
- * A call expression, including calls to predefined functions and operators.
- *
- * For example, `value == 10`, `size(map_value)`.
- */
+ /** A call expression. See {@link Expression.Call} */
@AutoValue
@Immutable
- public abstract static class CelCall {
+ public abstract static class CelCall implements Expression.Call For example, `x` in `x.f()`.
- */
+ @Override
public abstract Optional Lists may either be homogenous, e.g. `[1, 2, 3]`, or heterogeneous, e.g. `dyn([1, 'hello',
- * 2.0])`
- */
+ /** A list creation expression. See {@link Expression.CreateList} */
@AutoValue
@Immutable
- public abstract static class CelCreateList {
- /** The elements part of the list */
+ public abstract static class CelCreateList implements Expression.CreateList When an optional-typed value is present, the value it contains is included in the list. If
- * the optional-typed value is absent, the list element is omitted from the CreateList result.
- */
+ @Override
public abstract ImmutableList Messages are constructed with a type name and composed of field ids: `types.MyType{field_id:
- * 'value'}`.
- */
+ /** A message creation expression. See {@link Expression.CreateStruct} */
@AutoValue
@Immutable
- public abstract static class CelCreateStruct {
- /** The type name of the message to be created, empty when creating map literals. */
+ public abstract static class CelCreateStruct
+ implements Expression.CreateStruct If the optional_entry field is true, the expression must resolve to an optional-typed
- * value. If the optional value is present, the key will be set; however, if the optional
- * value is absent, the key will be unset.
- */
+ @Override
public abstract CelExpr value();
- /** Whether the key-value pair is optional. */
+ @Override
public abstract boolean optionalEntry();
/** Builder for CelCreateStruct.Entry. */
@@ -829,15 +781,12 @@ public static Builder newBuilder() {
}
}
- /**
- * A map creation expression.
- *
- * Maps are constructed as `{'key_name': 'value'}`.
- */
+ /** A map creation expression. See {@link Expression.CreateMap} */
@AutoValue
@Immutable
- public abstract static class CelCreateMap {
+ public abstract static class CelCreateMap implements Expression.CreateMap If the optional_entry field is true, the expression must resolve to an optional-typed
- * value. If the optional value is present, the key will be set; however, if the optional
- * value is absent, the key will be unset.
- */
+ @Override
public abstract CelExpr value();
- /** Whether the key-value pair is optional. */
+ @Override
public abstract boolean optionalEntry();
/** Builder for CelCreateMap.Entry. */
@@ -962,65 +903,29 @@ public static CelCreateMap.Entry.Builder newBuilder() {
}
}
- /**
- * A comprehension expression applied to a list or map.
- *
- * Comprehensions are not part of the core syntax, but enabled with macros. A macro matches a
- * specific call signature within a parsed AST and replaces the call with an alternate AST block.
- * Macro expansion happens at parse time.
- *
- * The following macros are supported within CEL:
- *
- * Aggregate type macros may be applied to all elements in a list or all keys in a map:
- *
- * `all`, `exists`, `exists_one` - test a predicate expression against the inputs and return
- * `true` if the predicate is satisfied for all, any, or only one value `list.all(x, x < 10)`.
- * `filter` - test a predicate expression against the inputs and return the subset of elements
- * which satisfy the predicate: `payments.filter(p, p > 1000)`. `map` - apply an expression to all
- * elements in the input and return the output aggregate type: `[1, 2, 3].map(i, i * i)`.
- *
- * The `has(m.x)` macro tests whether the property `x` is present in struct `m`. The semantics
- * of this macro depend on the type of `m`. For proto2 messages `has(m.x)` is defined as 'defined,
- * but not set`. For proto3, the macro tests whether the property is set to its default. For map
- * and struct types, the macro tests whether the property `x` is defined on `m`.
- *
- * Comprehension evaluation can be best visualized as the following pseudocode:
- */
+ /** A comprehension expression applied to a list or map. See {@link Expression.Comprehension} */
@AutoValue
@Immutable
- public abstract static class CelComprehension {
- /** The name of the iteration variable. */
+ public abstract static class CelComprehension implements Expression.Comprehension Returns false when the result has been computed and may be used as a hint to short-circuit
- * the remainder of the comprehension.
- */
+ @Override
public abstract CelExpr loopCondition();
- /**
- * An expression which can contain iter_var and accu_var.
- *
- * Computes the next value of accu_var.
- */
+ @Override
public abstract CelExpr loopStep();
- /**
- * An expression which can contain accu_var.
- *
- * Computes the result.
- */
+ @Override
public abstract CelExpr result();
/** Builder for Comprehension. */
diff --git a/common/src/main/java/dev/cel/common/ast/CelMutableExpr.java b/common/src/main/java/dev/cel/common/ast/CelMutableExpr.java
index 7b84826f1..0a44f4234 100644
--- a/common/src/main/java/dev/cel/common/ast/CelMutableExpr.java
+++ b/common/src/main/java/dev/cel/common/ast/CelMutableExpr.java
@@ -28,18 +28,21 @@
/**
* An abstract representation of a common expression that allows mutation in any of its properties.
- * The expressions are semantically the same as that of the immutable {@link CelExpr}.
+ * The expressions are semantically the same as that of the immutable {@link CelExpr}. Refer to
+ * {@link Expression} for details.
*
* This allows for an efficient optimization of an AST without having to traverse and rebuild the
* entire tree.
*
* This class is not thread-safe by design.
*/
-public final class CelMutableExpr {
+@SuppressWarnings("unchecked") // Class ensures only the super type is used
+public final class CelMutableExpr implements Expression {
private long id;
private ExprKind.Kind exprKind;
private Object exprValue;
+ @Override
public long id() {
return id;
}
@@ -48,6 +51,7 @@ public void setId(long id) {
this.id = id;
}
+ @Override
public ExprKind.Kind getKind() {
return exprKind;
}
@@ -57,41 +61,49 @@ public CelNotSet notSet() {
return (CelNotSet) exprValue;
}
+ @Override
public CelConstant constant() {
checkExprKind(Kind.CONSTANT);
return (CelConstant) exprValue;
}
+ @Override
public CelMutableIdent ident() {
checkExprKind(Kind.IDENT);
return (CelMutableIdent) exprValue;
}
+ @Override
public CelMutableSelect select() {
checkExprKind(Kind.SELECT);
return (CelMutableSelect) exprValue;
}
+ @Override
public CelMutableCall call() {
checkExprKind(Kind.CALL);
return (CelMutableCall) exprValue;
}
+ @Override
public CelMutableCreateList createList() {
checkExprKind(Kind.CREATE_LIST);
return (CelMutableCreateList) exprValue;
}
+ @Override
public CelMutableCreateStruct createStruct() {
checkExprKind(Kind.CREATE_STRUCT);
return (CelMutableCreateStruct) exprValue;
}
+ @Override
public CelMutableCreateMap createMap() {
checkExprKind(Kind.CREATE_MAP);
return (CelMutableCreateMap) exprValue;
}
+ @Override
public CelMutableComprehension comprehension() {
checkExprKind(Kind.COMPREHENSION);
return (CelMutableComprehension) exprValue;
@@ -138,9 +150,10 @@ public void setComprehension(CelMutableComprehension comprehension) {
}
/** A mutable identifier expression. */
- public static final class CelMutableIdent {
+ public static final class CelMutableIdent implements Ident {
private String name = "";
+ @Override
public String name() {
return name;
}
@@ -181,11 +194,12 @@ private CelMutableIdent(String name) {
}
/** A mutable field selection expression. e.g. `request.auth`. */
- public static final class CelMutableSelect {
+ public static final class CelMutableSelect implements Expression.Select Lists may either be homogenous, e.g. `[1, 2, 3]`, or heterogeneous, e.g. `dyn([1, 'hello',
- * 2.0])`
- */
- public static final class CelMutableCreateStruct {
+ /** A mutable list creation expression. See {@link Expression.CreateStruct} */
+ public static final class CelMutableCreateStruct
+ implements Expression.CreateStruct Expressions are abstractly represented as a collection of identifiers, select statements,
+ * function calls, literals, and comprehensions. All operators with the exception of the '.'
+ * operator are modelled as function calls. This makes it easy to represent new operators into the
+ * existing AST.
+ *
+ * All references within expressions must resolve to a [Decl][] provided at type-check for an
+ * expression to be valid. A reference may either be a bare identifier `name` or a qualified
+ * identifier `google.api.name`. References may either refer to a value or a function declaration.
+ *
+ * For example, the expression `google.api.name.startsWith('expr')` references the declaration
+ * `google.api.name` within a [Expr.Select][] expression, and the function declaration `startsWith`.
+ */
+@Internal
+public interface Expression {
+
+ /**
+ * Required. An id assigned to this node by the parser which is unique in a given expression tree.
+ * This is used to associate type information and other attributes to a node in the parse tree.
+ */
+ long id();
+
+ /** Represents the enumeration value for the underlying expression kind. */
+ CelExpr.ExprKind.Kind getKind();
+
+ /** Gets the underlying constant expression. */
+ CelConstant constant();
+
+ /** Gets the underlying identifier expression. */
+ Ident ident();
+
+ /** Gets the underlying call expression. */
+ Qualified names are represented by the [Expr.Select][] expression.
+ */
+ String name();
+ }
+
+ /** A call expression, including calls to predefined functions and operators. */
+ interface Call For example, `x` in `x.f()`.
+ */
+ Optional For example, `foo` in `f(foo)` or `x.f(foo)`.
+ */
+ List Lists may either be homogenous, e.g. `[1, 2, 3]`, or heterogeneous, e.g. `dyn([1, 'hello',
+ * 2.0])`
+ */
+ interface CreateList When an optional-typed value is present, the value it contains is included in the list. If
+ * the optional-typed value is absent, the list element is omitted from the CreateList result.
+ */
+ List For example, in the select expression `request.auth`, the `request` portion of the
+ * expression is the `operand`.
+ */
+ E operand();
+
+ /**
+ * Required. The name of the field to select.
+ *
+ * For example, in the select expression `request.auth`, the `auth` portion of the expression
+ * would be the `field`.
+ */
+ String field();
+
+ /**
+ * Whether the select is to be interpreted as a field presence test.
+ *
+ * This results from the macro `has(request.auth)`.
+ */
+ boolean testOnly();
+ }
+
+ /**
+ * A message creation expression.
+ *
+ * Messages are constructed with a type name and composed of field ids: `types.MyType{field_id:
+ * 'value'}`.
+ */
+ interface CreateStruct If the optional_entry field is true, the expression must resolve to an optional-typed
+ * value. If the optional value is present, the key will be set; however, if the optional
+ * value is absent, the key will be unset.
+ */
+ T value();
+
+ /** Whether the key-value pair is optional. */
+ boolean optionalEntry();
+ }
+ }
+
+ /**
+ * A map creation expression.
+ *
+ * Maps are constructed as `{'key_name': 'value'}`.
+ */
+ interface CreateMap If the optional_entry field is true, the expression must resolve to an optional-typed
+ * value. If the optional value is present, the key will be set; however, if the optional
+ * value is absent, the key will be unset.
+ */
+ T value();
+
+ boolean optionalEntry();
+ }
+ }
+
+ /**
+ * A comprehension expression applied to a list or map.
+ *
+ * Comprehensions are not part of the core syntax, but enabled with macros. A macro matches a
+ * specific call signature within a parsed AST and replaces the call with an alternate AST block.
+ * Macro expansion happens at parse time.
+ *
+ * The following macros are supported within CEL:
+ *
+ * Aggregate type macros may be applied to all elements in a list or all keys in a map:
+ *
+ * `all`, `exists`, `exists_one` - test a predicate expression against the inputs and return
+ * `true` if the predicate is satisfied for all, any, or only one value `list.all(x, x < 10)`.
+ * `filter` - test a predicate expression against the inputs and return the subset of elements
+ * which satisfy the predicate: `payments.filter(p, p > 1000)`. `map` - apply an expression to all
+ * elements in the input and return the output aggregate type: `[1, 2, 3].map(i, i * i)`.
+ *
+ * The `has(m.x)` macro tests whether the property `x` is present in struct `m`. The semantics
+ * of this macro depend on the type of `m`. For proto2 messages `has(m.x)` is defined as 'defined,
+ * but not set`. For proto3, the macro tests whether the property is set to its default. For map
+ * and struct types, the macro tests whether the property `x` is defined on `m`.
+ *
+ * Comprehension evaluation can be best visualized as the following pseudocode:
+ */
+ interface Comprehension Returns false when the result has been computed and may be used as a hint to short-circuit
+ * the remainder of the comprehension.
+ */
+ E loopCondition();
+
+ /**
+ * An expression which can contain iter_var and accu_var.
+ *
+ * Computes the next value of accu_var.
+ */
+ E loopStep();
+
+ /**
+ * An expression which can contain accu_var.
+ *
+ * Computes the result.
+ */
+ E result();
+ }
+}