1use crate::wasm::Expression;
2
3pub struct Expr;
19
20impl Expr {
21 pub fn int(value: i64) -> Expression {
25 Expression::IntLiteral { value }
26 }
27
28 pub fn bool(value: bool) -> Expression {
30 Expression::BoolLiteral { value }
31 }
32
33 pub fn null() -> Expression {
35 Expression::Null
36 }
37
38 pub fn param(index: u32) -> Expression {
42 Expression::Param { index }
43 }
44
45 pub fn eq(left: Expression, right: Expression) -> Expression {
49 Expression::Eq {
50 left: Box::new(left),
51 right: Box::new(right),
52 }
53 }
54
55 pub fn ne(left: Expression, right: Expression) -> Expression {
57 Expression::Ne {
58 left: Box::new(left),
59 right: Box::new(right),
60 }
61 }
62
63 pub fn lt(left: Expression, right: Expression) -> Expression {
65 Expression::Lt {
66 left: Box::new(left),
67 right: Box::new(right),
68 }
69 }
70
71 pub fn le(left: Expression, right: Expression) -> Expression {
73 Expression::Le {
74 left: Box::new(left),
75 right: Box::new(right),
76 }
77 }
78
79 pub fn gt(left: Expression, right: Expression) -> Expression {
81 Expression::Gt {
82 left: Box::new(left),
83 right: Box::new(right),
84 }
85 }
86
87 pub fn ge(left: Expression, right: Expression) -> Expression {
89 Expression::Ge {
90 left: Box::new(left),
91 right: Box::new(right),
92 }
93 }
94
95 pub fn and(left: Expression, right: Expression) -> Expression {
99 Expression::And {
100 left: Box::new(left),
101 right: Box::new(right),
102 }
103 }
104
105 pub fn or(left: Expression, right: Expression) -> Expression {
107 Expression::Or {
108 left: Box::new(left),
109 right: Box::new(right),
110 }
111 }
112
113 pub fn not(operand: Expression) -> Expression {
115 Expression::Not {
116 operand: Box::new(operand),
117 }
118 }
119
120 pub fn is_null(operand: Expression) -> Expression {
122 Expression::IsNull {
123 operand: Box::new(operand),
124 }
125 }
126
127 pub fn is_not_null(operand: Expression) -> Expression {
129 Expression::IsNotNull {
130 operand: Box::new(operand),
131 }
132 }
133
134 pub fn add(left: Expression, right: Expression) -> Expression {
138 Expression::Add {
139 left: Box::new(left),
140 right: Box::new(right),
141 }
142 }
143
144 pub fn sub(left: Expression, right: Expression) -> Expression {
146 Expression::Sub {
147 left: Box::new(left),
148 right: Box::new(right),
149 }
150 }
151
152 pub fn mul(left: Expression, right: Expression) -> Expression {
154 Expression::Mul {
155 left: Box::new(left),
156 right: Box::new(right),
157 }
158 }
159
160 pub fn div(left: Expression, right: Expression) -> Expression {
162 Expression::Div {
163 left: Box::new(left),
164 right: Box::new(right),
165 }
166 }
167
168 pub fn host_call(function_name: impl Into<String>, args: Vec<Expression>) -> Expression {
172 Expression::HostCall {
173 function_name: function_name.into(),
174 args,
175 }
176 }
177
178 pub fn list_contains(list: Expression, element: Expression) -> Expression {
185 Expression::ListContains {
186 list: Box::new(list),
187 element: Box::new(element),
188 }
189 }
190
191 pub fn string_equals(left: Expression, right: Expression) -> Expression {
197 Self::host_call("hstringEquals", vec![left, right])
198 }
199
200 pub fn ranges_overlap(
204 start1: Expression,
205 end1: Expression,
206 start2: Expression,
207 end2: Expression,
208 ) -> Expression {
209 Self::and(Self::lt(start1, end2), Self::lt(start2, end1))
210 }
211
212 pub fn if_then_else(
216 condition: Expression,
217 then_branch: Expression,
218 else_branch: Expression,
219 ) -> Expression {
220 Expression::IfThenElse {
221 condition: Box::new(condition),
222 then_branch: Box::new(then_branch),
223 else_branch: Box::new(else_branch),
224 }
225 }
226}
227
228pub trait FieldAccessExt {
232 fn get(self, class_name: &str, field_name: &str) -> Expression;
234}
235
236impl FieldAccessExt for Expression {
237 fn get(self, class_name: &str, field_name: &str) -> Expression {
238 Expression::FieldAccess {
239 object: Box::new(self),
240 class_name: class_name.into(),
241 field_name: field_name.into(),
242 }
243 }
244}
245
246#[cfg(test)]
247mod tests {
248 use super::*;
249
250 #[test]
251 fn test_int_literal() {
252 let expr = Expr::int(42);
253 assert_eq!(expr, Expression::IntLiteral { value: 42 });
254 }
255
256 #[test]
257 fn test_bool_literal() {
258 let expr = Expr::bool(true);
259 assert_eq!(expr, Expression::BoolLiteral { value: true });
260 }
261
262 #[test]
263 fn test_null() {
264 let expr = Expr::null();
265 assert_eq!(expr, Expression::Null);
266 }
267
268 #[test]
269 fn test_param() {
270 let expr = Expr::param(0);
271 assert_eq!(expr, Expression::Param { index: 0 });
272 }
273
274 #[test]
275 fn test_field_access_chaining() {
276 let expr = Expr::param(0).get("Employee", "name");
277
278 match expr {
279 Expression::FieldAccess {
280 object,
281 class_name,
282 field_name,
283 } => {
284 assert_eq!(class_name, "Employee");
285 assert_eq!(field_name, "name");
286 assert_eq!(*object, Expression::Param { index: 0 });
287 }
288 _ => panic!("Expected FieldAccess"),
289 }
290 }
291
292 #[test]
293 fn test_eq() {
294 let expr = Expr::eq(Expr::int(1), Expr::int(2));
295
296 match expr {
297 Expression::Eq { left, right } => {
298 assert_eq!(*left, Expression::IntLiteral { value: 1 });
299 assert_eq!(*right, Expression::IntLiteral { value: 2 });
300 }
301 _ => panic!("Expected Eq"),
302 }
303 }
304
305 #[test]
306 fn test_and() {
307 let expr = Expr::and(Expr::bool(true), Expr::bool(false));
308
309 match expr {
310 Expression::And { left, right } => {
311 assert_eq!(*left, Expression::BoolLiteral { value: true });
312 assert_eq!(*right, Expression::BoolLiteral { value: false });
313 }
314 _ => panic!("Expected And"),
315 }
316 }
317
318 #[test]
319 fn test_is_not_null() {
320 let expr = Expr::is_not_null(Expr::param(0));
321
322 match expr {
323 Expression::IsNotNull { operand } => {
324 assert_eq!(*operand, Expression::Param { index: 0 });
325 }
326 _ => panic!("Expected IsNotNull"),
327 }
328 }
329
330 #[test]
331 fn test_add() {
332 let expr = Expr::add(Expr::int(10), Expr::int(20));
333
334 match expr {
335 Expression::Add { left, right } => {
336 assert_eq!(*left, Expression::IntLiteral { value: 10 });
337 assert_eq!(*right, Expression::IntLiteral { value: 20 });
338 }
339 _ => panic!("Expected Add"),
340 }
341 }
342
343 #[test]
344 fn test_host_call() {
345 let expr = Expr::host_call("test_func", vec![Expr::int(1), Expr::int(2)]);
346
347 match expr {
348 Expression::HostCall {
349 function_name,
350 args,
351 } => {
352 assert_eq!(function_name, "test_func");
353 assert_eq!(args.len(), 2);
354 }
355 _ => panic!("Expected HostCall"),
356 }
357 }
358
359 #[test]
360 fn test_string_equals() {
361 let left = Expr::param(0).get("Employee", "skill");
362 let right = Expr::param(1).get("Shift", "requiredSkill");
363 let expr = Expr::string_equals(left, right);
364
365 match expr {
366 Expression::HostCall {
367 function_name,
368 args,
369 } => {
370 assert_eq!(function_name, "hstringEquals");
371 assert_eq!(args.len(), 2);
372 }
373 _ => panic!("Expected HostCall"),
374 }
375 }
376
377 #[test]
378 fn test_ranges_overlap() {
379 let expr = Expr::ranges_overlap(Expr::int(0), Expr::int(10), Expr::int(5), Expr::int(15));
380
381 match expr {
382 Expression::And { left, right } => {
383 match *left {
385 Expression::Lt { .. } => {}
386 _ => panic!("Expected Lt in left side"),
387 }
388 match *right {
390 Expression::Lt { .. } => {}
391 _ => panic!("Expected Lt in right side"),
392 }
393 }
394 _ => panic!("Expected And"),
395 }
396 }
397
398 #[test]
399 fn test_complex_predicate_builder() {
400 let shift = Expr::param(0);
402 let employee = shift.clone().get("Shift", "employee");
403
404 let predicate = Expr::and(
405 Expr::is_not_null(employee.clone()),
406 Expr::not(Expr::string_equals(
407 employee.get("Employee", "skill"),
408 shift.get("Shift", "requiredSkill"),
409 )),
410 );
411
412 match predicate {
414 Expression::And { .. } => {}
415 _ => panic!("Expected And at top level"),
416 }
417 }
418
419 #[test]
420 fn test_nested_field_access() {
421 let expr = Expr::param(0)
423 .get("Assignment", "shift")
424 .get("Shift", "employee")
425 .get("Employee", "name");
426
427 match expr {
428 Expression::FieldAccess {
429 class_name,
430 field_name,
431 object,
432 } => {
433 assert_eq!(class_name, "Employee");
434 assert_eq!(field_name, "name");
435
436 match *object {
437 Expression::FieldAccess { .. } => {}
438 _ => panic!("Expected nested FieldAccess"),
439 }
440 }
441 _ => panic!("Expected FieldAccess"),
442 }
443 }
444
445 #[test]
446 fn test_time_calculation() {
447 let expr = Expr::div(Expr::param(0).get("Shift", "start"), Expr::int(24));
449
450 match expr {
451 Expression::Div { left, right } => {
452 match *left {
453 Expression::FieldAccess { .. } => {}
454 _ => panic!("Expected FieldAccess"),
455 }
456 assert_eq!(*right, Expression::IntLiteral { value: 24 });
457 }
458 _ => panic!("Expected Div"),
459 }
460 }
461
462 #[test]
463 fn test_if_then_else() {
464 let expr = Expr::if_then_else(
466 Expr::gt(Expr::param(0), Expr::int(0)),
467 Expr::int(1),
468 Expr::int(0),
469 );
470
471 match expr {
472 Expression::IfThenElse {
473 condition,
474 then_branch,
475 else_branch,
476 } => {
477 match *condition {
478 Expression::Gt { .. } => {}
479 _ => panic!("Expected Gt"),
480 }
481 assert_eq!(*then_branch, Expression::IntLiteral { value: 1 });
482 assert_eq!(*else_branch, Expression::IntLiteral { value: 0 });
483 }
484 _ => panic!("Expected IfThenElse"),
485 }
486 }
487
488 #[test]
489 fn test_nested_if_then_else() {
490 let expr = Expr::if_then_else(
492 Expr::gt(Expr::param(0), Expr::int(0)),
493 Expr::if_then_else(
494 Expr::gt(Expr::param(0), Expr::int(10)),
495 Expr::int(2),
496 Expr::int(1),
497 ),
498 Expr::int(0),
499 );
500
501 match expr {
502 Expression::IfThenElse { then_branch, .. } => match *then_branch {
503 Expression::IfThenElse { .. } => {}
504 _ => panic!("Expected nested IfThenElse"),
505 },
506 _ => panic!("Expected IfThenElse"),
507 }
508 }
509
510 #[test]
511 fn test_list_contains() {
512 let list = Expr::param(0).get("Employee", "skills");
513 let element = Expr::param(1).get("Shift", "requiredSkill");
514 let expr = Expr::list_contains(list, element);
515
516 match expr {
517 Expression::ListContains { list, element } => {
518 match *list {
519 Expression::FieldAccess { field_name, .. } => {
520 assert_eq!(field_name, "skills");
521 }
522 _ => panic!("Expected FieldAccess for list"),
523 }
524 match *element {
525 Expression::FieldAccess { field_name, .. } => {
526 assert_eq!(field_name, "requiredSkill");
527 }
528 _ => panic!("Expected FieldAccess for element"),
529 }
530 }
531 _ => panic!("Expected ListContains"),
532 }
533 }
534}