cedar_policy_core/expr_builder.rs
1/*
2 * Copyright Cedar Contributors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17//! Contains the trait [`ExprBuilder`], defining a generic interface for
18//! building different expression data structures (e.g., AST and EST).
19
20use nonempty::NonEmpty;
21use smol_str::SmolStr;
22
23use crate::{
24 ast::{
25 BinaryOp, EntityType, ExpressionConstructionError, Literal, Name, Pattern, SlotId, UnaryOp,
26 Unknown, Var,
27 },
28 parser::{cst, Loc},
29};
30
31#[cfg(feature = "tolerant-ast")]
32use {crate::parser::err::ParseErrors, std::fmt::Debug};
33
34/// Defines a generic interface for building different expression data
35/// structures.
36#[allow(clippy::wrong_self_convention)]
37pub trait ExprBuilder: Clone {
38 /// The type of expression constructed by this instance of `ExprBuilder`.
39 type Expr: Clone + std::fmt::Display;
40
41 /// Type for extra information stored on nodes of the expression AST. This
42 /// can be `()` if no data is stored.
43 type Data: Default;
44
45 /// Type for what error we return if we cannot construct an error node
46 ///
47 /// By default we fail on errors and this should be a ParseErrors
48 /// But when we run with error parsing enabled, can be Infallible
49 #[cfg(feature = "tolerant-ast")]
50 type ErrorType: Debug;
51
52 /// Construct a new expression builder for an expression that will not carry any data.
53 fn new() -> Self
54 where
55 Self: Sized,
56 {
57 Self::with_data(Self::Data::default())
58 }
59
60 /// Build an expression that failed to parse - can optionally include subexpressions that parsed successfully
61 #[cfg(feature = "tolerant-ast")]
62 fn error(self, parse_errors: ParseErrors) -> Result<Self::Expr, Self::ErrorType>;
63
64 /// Build an expression storing this information
65 fn with_data(data: Self::Data) -> Self;
66
67 /// Build an expression located at `l`, if `l` is Some. An implementation
68 /// may ignore this if it cannot store source information.
69 fn with_maybe_source_loc(self, l: Option<&Loc>) -> Self;
70
71 /// Build an expression located at `l`. An implementation may ignore this if
72 /// it cannot store source information.
73 fn with_source_loc(self, l: &Loc) -> Self
74 where
75 Self: Sized,
76 {
77 self.with_maybe_source_loc(Some(l))
78 }
79
80 /// Extract the location for this builder, if set. Used internally to
81 /// provide utilities that construct multiple nodes which should all be
82 /// reported as having the same source location.
83 fn loc(&self) -> Option<&Loc>;
84
85 /// Extract the data that will be stored on the constructed expression.
86 /// Used internally to provide utilities that construct multiple nodes which
87 /// will all have the same data.
88 fn data(&self) -> &Self::Data;
89
90 /// Create an expression that's just a single `Literal`.
91 ///
92 /// Note that you can pass this a `Literal`, an `Integer`, a `String`, etc.
93 fn val(self, v: impl Into<Literal>) -> Self::Expr;
94
95 /// Create an `Expr` that's just this literal `Var`
96 fn var(self, v: Var) -> Self::Expr;
97
98 /// Create an `Unknown` `Expr`
99 fn unknown(self, u: Unknown) -> Self::Expr;
100
101 /// Create an `Expr` that's just this `SlotId`
102 fn slot(self, s: SlotId) -> Self::Expr;
103
104 /// Create a ternary (if-then-else) `Expr`.
105 fn ite(self, test_expr: Self::Expr, then_expr: Self::Expr, else_expr: Self::Expr)
106 -> Self::Expr;
107
108 /// Create a 'not' expression.
109 fn not(self, e: Self::Expr) -> Self::Expr;
110
111 /// Create a '==' expression
112 fn is_eq(self, e1: Self::Expr, e2: Self::Expr) -> Self::Expr;
113
114 /// Create an 'and' expression.
115 fn and(self, e1: Self::Expr, e2: Self::Expr) -> Self::Expr;
116
117 /// Create an 'or' expression.
118 fn or(self, e1: Self::Expr, e2: Self::Expr) -> Self::Expr;
119
120 /// Create a '<' expression.
121 fn less(self, e1: Self::Expr, e2: Self::Expr) -> Self::Expr;
122
123 /// Create a '<=' expression.
124 fn lesseq(self, e1: Self::Expr, e2: Self::Expr) -> Self::Expr;
125
126 /// Create an 'add' expression.
127 fn add(self, e1: Self::Expr, e2: Self::Expr) -> Self::Expr;
128
129 /// Create a 'sub' expression.
130 fn sub(self, e1: Self::Expr, e2: Self::Expr) -> Self::Expr;
131
132 /// Create a 'mul' expression.
133 fn mul(self, e1: Self::Expr, e2: Self::Expr) -> Self::Expr;
134
135 /// Create a 'neg' expression.
136 fn neg(self, e: Self::Expr) -> Self::Expr;
137
138 /// Create an 'in' expression. First argument must evaluate to Entity type.
139 fn is_in(self, e1: Self::Expr, e2: Self::Expr) -> Self::Expr;
140
141 /// Create a 'contains' expression.
142 fn contains(self, e1: Self::Expr, e2: Self::Expr) -> Self::Expr;
143
144 /// Create a 'contains_all' expression. Arguments must evaluate to Set type
145 fn contains_all(self, e1: Self::Expr, e2: Self::Expr) -> Self::Expr;
146
147 /// Create an 'contains_any' expression. Arguments must evaluate to Set type
148 fn contains_any(self, e1: Self::Expr, e2: Self::Expr) -> Self::Expr;
149
150 /// Create an 'is_empty' expression. Argument must evaluate to Set type
151 fn is_empty(self, expr: Self::Expr) -> Self::Expr;
152
153 /// Create a 'getTag' expression.
154 fn get_tag(self, expr: Self::Expr, tag: Self::Expr) -> Self::Expr;
155
156 /// Create a 'hasTag' expression.
157 fn has_tag(self, expr: Self::Expr, tag: Self::Expr) -> Self::Expr;
158
159 /// Create an `Expr` which evaluates to a Set of the given `Expr`s
160 fn set(self, exprs: impl IntoIterator<Item = Self::Expr>) -> Self::Expr;
161
162 /// Create an `Expr` which evaluates to a Record with the given (key, value) pairs.
163 fn record(
164 self,
165 pairs: impl IntoIterator<Item = (SmolStr, Self::Expr)>,
166 ) -> Result<Self::Expr, ExpressionConstructionError>;
167
168 /// Create an `Expr` which calls the extension function with the given
169 /// `Name` on `args`
170 fn call_extension_fn(
171 self,
172 fn_name: Name,
173 args: impl IntoIterator<Item = Self::Expr>,
174 ) -> Self::Expr;
175
176 /// Create an `Expr` which gets a given attribute of a given `Entity` or record.
177 fn get_attr(self, expr: Self::Expr, attr: SmolStr) -> Self::Expr;
178
179 /// Create an `Expr` which tests for the existence of a given
180 /// attribute on a given `Entity` or record.
181 fn has_attr(self, expr: Self::Expr, attr: SmolStr) -> Self::Expr;
182
183 /// Create an `Expr` which tests for the existence of a given
184 /// non-empty list of attributes on a given `Entity` or record.
185 fn extended_has_attr(self, expr: Self::Expr, attrs: &NonEmpty<SmolStr>) -> Self::Expr {
186 let (first, rest) = attrs.split_first();
187 let has_expr = Self::new()
188 .with_maybe_source_loc(self.loc())
189 .has_attr(expr.clone(), first.to_owned());
190 let get_expr = Self::new()
191 .with_maybe_source_loc(self.loc())
192 .get_attr(expr, first.to_owned());
193 // Foldl on the attribute list
194 // It produces the following for `principal has contactInfo.address.zip`
195 // Expr.and
196 // (Expr.and
197 // (Expr.hasAttr (Expr.var .principal) "contactInfo")
198 // (Expr.hasAttr
199 // (Expr.getAttr (Expr.var .principal) "contactInfo")
200 // "address"))
201 // (Expr.hasAttr
202 // (Expr.getAttr
203 // (Expr.getAttr (Expr.var .principal) "contactInfo")
204 // "address")
205 // "zip")
206 // This is sound. However, the evaluator has to recur multiple times to the
207 // left-most node to evaluate the existence of the first attribute. The
208 // desugared expression should be the following to avoid the issue above,
209 // Expr.and
210 // Expr.hasAttr (Expr.var .principal) "contactInfo"
211 // (Expr.and
212 // (Expr.hasAttr (Expr.getAttr (Expr.var .principal) "contactInfo")"address")
213 // (Expr.hasAttr ..., "zip"))
214 rest.iter()
215 .fold((has_expr, get_expr), |(has_expr, get_expr), attr| {
216 (
217 Self::new().with_maybe_source_loc(self.loc()).and(
218 has_expr,
219 Self::new()
220 .with_maybe_source_loc(self.loc())
221 .has_attr(get_expr.clone(), attr.to_owned()),
222 ),
223 Self::new()
224 .with_maybe_source_loc(self.loc())
225 .get_attr(get_expr, attr.to_owned()),
226 )
227 })
228 .0
229 }
230
231 /// Create a 'like' expression.
232 fn like(self, expr: Self::Expr, pattern: Pattern) -> Self::Expr;
233
234 /// Create an 'is' expression.
235 fn is_entity_type(self, expr: Self::Expr, entity_type: EntityType) -> Self::Expr;
236
237 /// Create an `_ is _ in _` expression
238 fn is_in_entity_type(
239 self,
240 e1: Self::Expr,
241 entity_type: EntityType,
242 e2: Self::Expr,
243 ) -> Self::Expr
244 where
245 Self: Sized,
246 {
247 self.clone().and(
248 self.clone().is_entity_type(e1.clone(), entity_type),
249 self.is_in(e1, e2),
250 )
251 }
252
253 /// Create an application `Expr` which applies the given built-in unary
254 /// operator to the given `arg`
255 fn unary_app(self, op: impl Into<UnaryOp>, arg: Self::Expr) -> Self::Expr
256 where
257 Self: Sized,
258 {
259 match op.into() {
260 UnaryOp::Not => self.not(arg),
261 UnaryOp::Neg => self.neg(arg),
262 UnaryOp::IsEmpty => self.is_empty(arg),
263 }
264 }
265
266 /// Create an application `Expr` which applies the given built-in binary
267 /// operator to `arg1` and `arg2`
268 fn binary_app(self, op: impl Into<BinaryOp>, arg1: Self::Expr, arg2: Self::Expr) -> Self::Expr
269 where
270 Self: Sized,
271 {
272 match op.into() {
273 BinaryOp::Eq => self.is_eq(arg1, arg2),
274 BinaryOp::Less => self.less(arg1, arg2),
275 BinaryOp::LessEq => self.lesseq(arg1, arg2),
276 BinaryOp::Add => self.add(arg1, arg2),
277 BinaryOp::Sub => self.sub(arg1, arg2),
278 BinaryOp::Mul => self.mul(arg1, arg2),
279 BinaryOp::In => self.is_in(arg1, arg2),
280 BinaryOp::Contains => self.contains(arg1, arg2),
281 BinaryOp::ContainsAll => self.contains_all(arg1, arg2),
282 BinaryOp::ContainsAny => self.contains_any(arg1, arg2),
283 BinaryOp::GetTag => self.get_tag(arg1, arg2),
284 BinaryOp::HasTag => self.has_tag(arg1, arg2),
285 }
286 }
287
288 /// Create a '!=' expression.
289 fn noteq(self, e1: Self::Expr, e2: Self::Expr) -> Self::Expr
290 where
291 Self: Sized,
292 {
293 self.clone().not(self.is_eq(e1, e2))
294 }
295
296 /// Create a '>' expression.
297 fn greater(self, e1: Self::Expr, e2: Self::Expr) -> Self::Expr
298 where
299 Self: Sized,
300 {
301 // e1 > e2 is defined as !(e1 <= e2)
302 self.clone().not(self.lesseq(e1, e2))
303 }
304
305 /// Create a '>=' expression.
306 fn greatereq(self, e1: Self::Expr, e2: Self::Expr) -> Self::Expr
307 where
308 Self: Sized,
309 {
310 // e1 >= e2 is defined as !(e1 < e2)
311 self.clone().not(self.less(e1, e2))
312 }
313
314 /// Create an `and` expression that may have more than two subexpressions (A && B && C) and associate them from left to right
315 /// .e.g, [A, B, C] to (A && B) && C
316 /// or may have only one subexpression, in which case no `&&` is performed at all.
317 /// Arguments must evaluate to Bool type.
318 ///
319 /// This may create multiple AST `&&` nodes. If it does, all the nodes will have the same
320 /// source location and the same `T` data (taken from this builder) unless overridden, e.g.,
321 /// with another call to `with_source_loc()`.
322 fn and_naryl(
323 self,
324 first: Self::Expr,
325 others: impl IntoIterator<Item = Self::Expr>,
326 ) -> Self::Expr
327 where
328 Self: Sized,
329 {
330 others
331 .into_iter()
332 .fold(first, |acc, next| self.clone().and(acc, next))
333 }
334
335 /// Create an `or` expression that may have more than two subexpressions (A || B || C)
336 /// or may have only one subexpression, in which case no `||` is performed at all.
337 /// Arguments must evaluate to Bool type.
338 ///
339 /// This may create multiple AST `||` nodes. If it does, all the nodes will have the same
340 /// source location and the same `T` data (taken from this builder) unless overridden, e.g.,
341 /// with another call to `with_source_loc()`.
342 fn or_nary(self, first: Self::Expr, others: impl IntoIterator<Item = Self::Expr>) -> Self::Expr
343 where
344 Self: Sized,
345 {
346 others
347 .into_iter()
348 .fold(first, |acc, next| self.clone().or(acc, next))
349 }
350
351 /// Create expression containing addition and subtraction that may have more
352 /// than two subexpressions (A + B - C) or may have only one subexpression,
353 /// in which case no operations are performed at all.
354 fn add_nary(
355 self,
356 first: Self::Expr,
357 other: impl IntoIterator<Item = (cst::AddOp, Self::Expr)>,
358 ) -> Self::Expr
359 where
360 Self: Sized,
361 {
362 other.into_iter().fold(first, |acc, (op, next)| match op {
363 cst::AddOp::Plus => self.clone().add(acc, next),
364 cst::AddOp::Minus => self.clone().sub(acc, next),
365 })
366 }
367
368 /// Create expression containing multiplication that may have more than two
369 /// subexpressions (A * B * C) or may have only one subexpression,
370 /// in which case no operations are performed at all.
371 fn mul_nary(self, first: Self::Expr, other: impl IntoIterator<Item = Self::Expr>) -> Self::Expr
372 where
373 Self: Sized,
374 {
375 other
376 .into_iter()
377 .fold(first, |acc, next| self.clone().mul(acc, next))
378 }
379}