1use crate::diagnostics::diagnostics_api::SymbolKind;
2use crate::errors::{FatalParseError, RecoverableParseError};
3use crate::parser::ParseContext;
4use crate::parser::atom::parse_atom;
5use crate::parser::comprehension::parse_quantifier_or_aggregate_expr;
6use crate::util::TypecheckingContext;
7use crate::util::named_children;
8use crate::{child, field, named_child};
9use conjure_cp_core::ast::{Atom, DeclarationKind, ReturnType, Typeable};
10use conjure_cp_core::ast::{Expression, Metadata, Moo};
11use conjure_cp_core::into_matrix_expr;
12use conjure_cp_core::{domain_int, matrix_expr, range};
13use tree_sitter::Node;
14use uniplate::Uniplate;
15
16pub fn parse_expression(
17 ctx: &mut ParseContext,
18 node: Node,
19) -> Result<Option<Expression>, FatalParseError> {
20 match node.kind() {
21 "atom" => parse_atom(ctx, &node),
22 "bool_expr" => parse_boolean_expression(ctx, &node),
23 "arithmetic_expr" => parse_arithmetic_expression(ctx, &node),
24 "comparison_expr" => parse_comparison_expression(ctx, &node),
25 "dominance_relation" => parse_dominance_relation(ctx, &node),
26 "all_diff_comparison" => parse_all_diff_comparison(ctx, &node),
27 _ => {
28 ctx.record_error(RecoverableParseError::new(
29 format!("Unexpected expression type: '{}'", node.kind()),
30 Some(node.range()),
31 ));
32 Ok(None)
33 }
34 }
35}
36
37fn parse_dominance_relation(
38 ctx: &mut ParseContext,
39 node: &Node,
40) -> Result<Option<Expression>, FatalParseError> {
41 if ctx.root.kind() == "dominance_relation" {
42 ctx.record_error(RecoverableParseError::new(
43 "Nested dominance relations are not allowed".to_string(),
44 Some(node.range()),
45 ));
46 return Ok(None);
47 }
48
49 let Some(inner_node) = field!(recover, ctx, node, "expression") else {
50 return Ok(None);
51 };
52
53 let mut inner_ctx = ParseContext {
57 source_code: ctx.source_code,
58 root: node,
59 symbols: ctx.symbols.clone(),
60 errors: ctx.errors,
61 source_map: &mut *ctx.source_map,
62 decl_spans: ctx.decl_spans,
63 typechecking_context: ctx.typechecking_context,
64 };
65
66 let Some(inner) = parse_expression(&mut inner_ctx, inner_node)? else {
67 return Ok(None);
68 };
69
70 Ok(Some(Expression::DominanceRelation(
71 Metadata::new(),
72 Moo::new(inner),
73 )))
74}
75
76#[derive(Clone, Copy, Debug, PartialEq, Eq)]
77enum ParetoDirection {
78 Minimising,
79 Maximising,
80}
81
82#[derive(Clone, Copy, Debug, PartialEq, Eq)]
83enum ReferenceRewriteAction {
84 LeaveAsIs,
85 ExpandValueLetting,
86 WrapInFromSolution,
87}
88
89pub fn parse_pareto_expression(
90 ctx: &mut ParseContext,
91 node: &Node,
92) -> Result<Option<Expression>, FatalParseError> {
93 if ctx.root.kind() != "dominance_relation" {
94 ctx.record_error(RecoverableParseError::new(
95 "pareto(...) only allowed inside dominance relations".to_string(),
96 Some(node.range()),
97 ));
98 return Ok(None);
99 }
100
101 let mut non_worsening = Vec::new();
102 let mut strict_improvements = Vec::new();
103 let components = field!(node, "components");
104
105 if components.kind() != "pareto_items" {
106 return Err(FatalParseError::internal_error(
107 format!("Unexpected pareto component list: '{}'", components.kind()),
108 Some(components.range()),
109 ));
110 }
111
112 for item_node in named_children(&components) {
113 let direction_node = field!(item_node, "direction");
114 let direction_str =
115 &ctx.source_code[direction_node.start_byte()..direction_node.end_byte()];
116 let direction = match direction_str {
117 "minimising" => ParetoDirection::Minimising,
118 "maximising" => ParetoDirection::Maximising,
119 _ => {
120 return Err(FatalParseError::internal_error(
121 format!("Unexpected pareto direction: '{direction_str}'"),
122 Some(direction_node.range()),
123 ));
124 }
125 };
126
127 let component_node = field!(item_node, "expression");
128 let Some(component_expr) = parse_pareto_component(ctx, &component_node)? else {
129 return Ok(None);
130 };
131 let Some((non_worse, strict)) =
132 build_pareto_constraints(ctx, &component_node, component_expr, direction)
133 else {
134 return Ok(None);
135 };
136 non_worsening.push(non_worse);
137 strict_improvements.push(strict);
138 }
139
140 let mut conjuncts = non_worsening;
141 conjuncts.push(combine_with_and_or(strict_improvements, true));
142
143 Ok(Some(combine_with_and_or(conjuncts, false)))
144}
145
146fn parse_pareto_component(
147 ctx: &mut ParseContext,
148 node: &Node,
149) -> Result<Option<Expression>, FatalParseError> {
150 let saved_context = ctx.typechecking_context;
151 ctx.typechecking_context = TypecheckingContext::Unknown;
152 let parsed = parse_expression(ctx, *node)?;
153 ctx.typechecking_context = saved_context;
154 Ok(parsed)
155}
156
157fn build_pareto_constraints(
158 ctx: &mut ParseContext,
159 node: &Node,
160 component: Expression,
161 direction: ParetoDirection,
162) -> Option<(Expression, Expression)> {
163 if component
164 .universe()
165 .iter()
166 .any(|expr| matches!(expr, Expression::FromSolution(_, _)))
167 {
168 ctx.record_error(RecoverableParseError::new(
169 "pareto(...) components cannot contain fromSolution(...) explicitly".to_string(),
170 Some(node.range()),
171 ));
172 return None;
173 }
174
175 let current = expand_value_lettings(&component);
176 let previous = lift_to_previous_solution(¤t);
177
178 match current.return_type() {
179 ReturnType::Int => Some(match direction {
180 ParetoDirection::Minimising => (
181 Expression::Leq(
182 Metadata::new(),
183 Moo::new(current.clone()),
184 Moo::new(previous.clone()),
185 ),
186 Expression::Lt(Metadata::new(), Moo::new(current), Moo::new(previous)),
187 ),
188 ParetoDirection::Maximising => (
189 Expression::Geq(
190 Metadata::new(),
191 Moo::new(current.clone()),
192 Moo::new(previous.clone()),
193 ),
194 Expression::Gt(Metadata::new(), Moo::new(current), Moo::new(previous)),
195 ),
196 }),
197 ReturnType::Bool => Some(match direction {
198 ParetoDirection::Minimising => (
199 Expression::Imply(
200 Metadata::new(),
201 Moo::new(current.clone()),
202 Moo::new(previous.clone()),
203 ),
204 combine_with_and_or(
205 vec![
206 Expression::Not(Metadata::new(), Moo::new(current)),
207 previous,
208 ],
209 false,
210 ),
211 ),
212 ParetoDirection::Maximising => (
213 Expression::Imply(
214 Metadata::new(),
215 Moo::new(previous.clone()),
216 Moo::new(current.clone()),
217 ),
218 combine_with_and_or(
219 vec![
220 current,
221 Expression::Not(Metadata::new(), Moo::new(previous)),
222 ],
223 false,
224 ),
225 ),
226 }),
227 found => {
228 ctx.record_error(RecoverableParseError::new(
229 format!(
230 "pareto(...) only supports int or bool components, found '{}'",
231 found
232 ),
233 Some(node.range()),
234 ));
235 None
236 }
237 }
238}
239
240fn expand_value_lettings(expr: &Expression) -> Expression {
241 rewrite_references(expr, false)
242}
243
244fn lift_to_previous_solution(expr: &Expression) -> Expression {
245 rewrite_references(expr, true)
246}
247
248fn rewrite_references(expr: &Expression, to_previous_solution: bool) -> Expression {
249 let mut lifted = expr.clone();
250
251 loop {
252 let next = lifted.rewrite(&|subexpr| match subexpr {
253 Expression::Atomic(_, Atom::Reference(ref reference)) => {
254 let action = {
255 let kind = reference.ptr.kind();
256 match &*kind {
257 DeclarationKind::Find(_) if to_previous_solution => {
258 ReferenceRewriteAction::WrapInFromSolution
259 }
260 DeclarationKind::Find(_) => ReferenceRewriteAction::LeaveAsIs,
261 DeclarationKind::ValueLetting(_, _)
262 | DeclarationKind::TemporaryValueLetting(_) => {
263 ReferenceRewriteAction::ExpandValueLetting
264 }
265 DeclarationKind::Given(_)
266 | DeclarationKind::Quantified(_)
267 | DeclarationKind::QuantifiedExpr(_)
268 | DeclarationKind::DomainLetting(_)
269 | DeclarationKind::RecordField(_)
270 | _ => ReferenceRewriteAction::LeaveAsIs,
271 }
272 };
273
274 match action {
275 ReferenceRewriteAction::LeaveAsIs => Some(subexpr),
276 ReferenceRewriteAction::ExpandValueLetting => reference.resolve_expression(),
277 ReferenceRewriteAction::WrapInFromSolution => Some(Expression::FromSolution(
278 Metadata::new(),
279 Moo::new(Atom::Reference(reference.clone())),
280 )),
281 }
282 }
283 _ => Some(subexpr),
284 });
285
286 if next == lifted {
287 return lifted;
288 }
289
290 lifted = next;
291 }
292}
293
294fn combine_with_and_or(exprs: Vec<Expression>, is_or: bool) -> Expression {
295 match exprs.len() {
296 0 => {
297 if is_or {
298 Expression::Or(Metadata::new(), Moo::new(into_matrix_expr![exprs]))
299 } else {
300 Expression::And(Metadata::new(), Moo::new(into_matrix_expr![exprs]))
301 }
302 }
303 1 => match exprs.into_iter().next() {
304 Some(expr) => expr,
305 None => unreachable!("vector length already checked"),
306 },
307 _ => {
308 if is_or {
309 Expression::Or(Metadata::new(), Moo::new(into_matrix_expr![exprs]))
310 } else {
311 Expression::And(Metadata::new(), Moo::new(into_matrix_expr![exprs]))
312 }
313 }
314 }
315}
316
317fn parse_arithmetic_expression(
318 ctx: &mut ParseContext,
319 node: &Node,
320) -> Result<Option<Expression>, FatalParseError> {
321 ctx.typechecking_context = TypecheckingContext::Arithmetic;
322 let Some(inner) = named_child!(recover, ctx, node) else {
323 return Ok(None);
324 };
325 match inner.kind() {
326 "atom" => parse_atom(ctx, &inner),
327 "negative_expr" | "abs_value" | "sub_arith_expr" | "factorial_expr" => {
328 parse_unary_expression(ctx, &inner)
329 }
330 "toInt_expr" => {
331 ctx.typechecking_context = TypecheckingContext::Unknown;
333 parse_unary_expression(ctx, &inner)
334 }
335 "exponent" | "product_expr" | "sum_expr" => parse_binary_expression(ctx, &inner),
336 "list_combining_expr_arith" => parse_list_combining_expression(ctx, &inner),
337 "aggregate_expr" => parse_quantifier_or_aggregate_expr(ctx, &inner),
338 _ => {
339 ctx.record_error(RecoverableParseError::new(
340 format!("Expected arithmetic expression, found: {}", inner.kind()),
341 Some(inner.range()),
342 ));
343 Ok(None)
344 }
345 }
346}
347
348fn parse_comparison_expression(
349 ctx: &mut ParseContext,
350 node: &Node,
351) -> Result<Option<Expression>, FatalParseError> {
352 let Some(inner) = named_child!(recover, ctx, node) else {
353 return Ok(None);
354 };
355 match inner.kind() {
356 "arithmetic_comparison" => {
357 ctx.typechecking_context = TypecheckingContext::Arithmetic;
359 parse_binary_expression(ctx, &inner)
360 }
361 "lex_comparison" => {
362 ctx.typechecking_context = TypecheckingContext::Unknown;
364 parse_binary_expression(ctx, &inner)
365 }
366 "equality_comparison" => {
367 ctx.typechecking_context = TypecheckingContext::Unknown;
370 parse_binary_expression(ctx, &inner)
371 }
372 "set_comparison" => {
373 ctx.typechecking_context = TypecheckingContext::Set;
375 parse_binary_expression(ctx, &inner)
376 }
377 "all_diff_comparison" => {
378 ctx.typechecking_context = TypecheckingContext::Unknown;
380 parse_all_diff_comparison(ctx, &inner)
381 }
382 _ => {
383 ctx.record_error(RecoverableParseError::new(
384 format!("Expected comparison expression, found '{}'", inner.kind()),
385 Some(inner.range()),
386 ));
387 Ok(None)
388 }
389 }
390}
391
392fn parse_boolean_expression(
393 ctx: &mut ParseContext,
394 node: &Node,
395) -> Result<Option<Expression>, FatalParseError> {
396 ctx.typechecking_context = TypecheckingContext::Boolean;
397 let Some(inner) = named_child!(recover, ctx, node) else {
398 return Ok(None);
399 };
400 match inner.kind() {
401 "atom" => parse_atom(ctx, &inner),
402 "not_expr" | "sub_bool_expr" => parse_unary_expression(ctx, &inner),
403 "and_expr" | "or_expr" | "implication" | "iff_expr" => parse_binary_expression(ctx, &inner),
404 "list_combining_expr_bool" => parse_list_combining_expression(ctx, &inner),
405 "quantifier_expr" => parse_quantifier_or_aggregate_expr(ctx, &inner),
406 _ => {
407 ctx.record_error(RecoverableParseError::new(
408 format!("Expected boolean expression, found '{}'", inner.kind()),
409 Some(inner.range()),
410 ));
411 Ok(None)
412 }
413 }
414}
415
416fn parse_list_combining_expression(
417 ctx: &mut ParseContext,
418 node: &Node,
419) -> Result<Option<Expression>, FatalParseError> {
420 let Some(operator_node) = field!(recover, ctx, node, "operator") else {
421 return Ok(None);
422 };
423 let operator_str = &ctx.source_code[operator_node.start_byte()..operator_node.end_byte()];
424
425 let Some(arg_node) = field!(recover, ctx, node, "arg") else {
426 return Ok(None);
427 };
428 let Some(inner) = parse_atom(ctx, &arg_node)? else {
429 return Ok(None);
430 };
431
432 let expr = match operator_str {
433 "and" => Ok(Some(Expression::And(Metadata::new(), Moo::new(inner)))),
434 "or" => Ok(Some(Expression::Or(Metadata::new(), Moo::new(inner)))),
435 "sum" => Ok(Some(Expression::Sum(Metadata::new(), Moo::new(inner)))),
436 "product" => Ok(Some(Expression::Product(Metadata::new(), Moo::new(inner)))),
437 "min" => Ok(Some(Expression::Min(Metadata::new(), Moo::new(inner)))),
438 "max" => Ok(Some(Expression::Max(Metadata::new(), Moo::new(inner)))),
439 _ => {
440 ctx.record_error(RecoverableParseError::new(
441 format!("Invalid operator: '{operator_str}'"),
442 Some(operator_node.range()),
443 ));
444 Ok(None)
445 }
446 };
447
448 if expr.is_ok() {
449 ctx.add_span_and_doc_hover(
450 &operator_node,
451 operator_str,
452 SymbolKind::Function,
453 None,
454 None,
455 );
456 }
457
458 expr
459}
460
461fn parse_all_diff_comparison(
462 ctx: &mut ParseContext,
463 node: &Node,
464) -> Result<Option<Expression>, FatalParseError> {
465 let Some(arg_node) = field!(recover, ctx, node, "arg") else {
466 return Ok(None);
467 };
468 let Some(inner) = parse_expression(ctx, arg_node)? else {
469 return Ok(None);
470 };
471
472 let all_diff_keyword_node = child!(node, 0, "allDiff");
473 ctx.add_span_and_doc_hover(
474 &all_diff_keyword_node,
475 "allDiff",
476 SymbolKind::Function,
477 None,
478 None,
479 );
480 Ok(Some(Expression::AllDiff(Metadata::new(), Moo::new(inner))))
481}
482
483fn parse_unary_expression(
484 ctx: &mut ParseContext,
485 node: &Node,
486) -> Result<Option<Expression>, FatalParseError> {
487 let Some(expr_node) = field!(recover, ctx, node, "expression") else {
488 return Ok(None);
489 };
490 let Some(inner) = parse_expression(ctx, expr_node)? else {
491 return Ok(None);
492 };
493
494 match node.kind() {
495 "negative_expr" => Ok(Some(Expression::Neg(Metadata::new(), Moo::new(inner)))),
496 "abs_value" => Ok(Some(Expression::Abs(Metadata::new(), Moo::new(inner)))),
497 "not_expr" => Ok(Some(Expression::Not(Metadata::new(), Moo::new(inner)))),
498 "toInt_expr" => {
499 let to_int_keyword_node = child!(node, 0, "toInt");
500 ctx.add_span_and_doc_hover(
501 &to_int_keyword_node,
502 "toInt",
503 SymbolKind::Function,
504 None,
505 None,
506 );
507 Ok(Some(Expression::ToInt(Metadata::new(), Moo::new(inner))))
508 }
509 "factorial_expr" => {
510 if let Some(op_node) = (0..node.child_count())
512 .filter_map(|i| node.child(i.try_into().unwrap()))
513 .find(|c| matches!(c.kind(), "!" | "factorial"))
514 {
515 ctx.add_span_and_doc_hover(
516 &op_node,
517 "post_factorial",
518 SymbolKind::Function,
519 None,
520 None,
521 );
522 }
523
524 Ok(Some(Expression::Factorial(
525 Metadata::new(),
526 Moo::new(inner),
527 )))
528 }
529 "sub_bool_expr" | "sub_arith_expr" => Ok(Some(inner)),
530 _ => {
531 ctx.record_error(RecoverableParseError::new(
532 format!("Unrecognised unary operation: '{}'", node.kind()),
533 Some(node.range()),
534 ));
535 Ok(None)
536 }
537 }
538}
539
540pub fn parse_binary_expression(
541 ctx: &mut ParseContext,
542 node: &Node,
543) -> Result<Option<Expression>, FatalParseError> {
544 let Some(op_node) = field!(recover, ctx, node, "operator") else {
545 return Ok(None);
546 };
547 let op_str = &ctx.source_code[op_node.start_byte()..op_node.end_byte()];
548
549 let saved_ctx = ctx.typechecking_context;
550
551 if op_str == "in" {
553 ctx.typechecking_context = TypecheckingContext::Unknown
554 }
555
556 let Some(left_node) = field!(recover, ctx, node, "left") else {
558 return Ok(None);
559 };
560 let Some(left) = parse_expression(ctx, left_node)? else {
561 return Ok(None);
562 };
563
564 ctx.typechecking_context = saved_ctx;
566
567 let Some(right_node) = field!(recover, ctx, node, "right") else {
569 return Ok(None);
570 };
571 let Some(right) = parse_expression(ctx, right_node)? else {
572 return Ok(None);
573 };
574
575 let Some(op_node) = field!(recover, ctx, node, "operator") else {
576 return Ok(None);
577 };
578 let op_str = &ctx.source_code[op_node.start_byte()..op_node.end_byte()];
579
580 let mut doc_name = "";
581 let expr = match op_str {
582 "+" => {
587 doc_name = "L_Plus";
588 Ok(Some(Expression::Sum(
589 Metadata::new(),
590 Moo::new(matrix_expr![left, right; domain_int!(1..)]),
591 )))
592 }
593 "-" => {
594 doc_name = "L_Minus";
595 Ok(Some(Expression::Minus(
596 Metadata::new(),
597 Moo::new(left),
598 Moo::new(right),
599 )))
600 }
601 "*" => {
602 doc_name = "L_Times";
603 Ok(Some(Expression::Product(
604 Metadata::new(),
605 Moo::new(matrix_expr![left, right; domain_int!(1..)]),
606 )))
607 }
608 "/\\" => {
609 doc_name = "and";
610 Ok(Some(Expression::And(
611 Metadata::new(),
612 Moo::new(matrix_expr![left, right; domain_int!(1..)]),
613 )))
614 }
615 "\\/" => {
616 doc_name = "or";
618 Ok(Some(Expression::Or(
619 Metadata::new(),
620 Moo::new(matrix_expr![left, right; domain_int!(1..)]),
621 )))
622 }
623 "**" => {
624 doc_name = "L_Pow";
625 Ok(Some(Expression::UnsafePow(
626 Metadata::new(),
627 Moo::new(left),
628 Moo::new(right),
629 )))
630 }
631 "/" => {
632 doc_name = "L_Div";
634 Ok(Some(Expression::UnsafeDiv(
635 Metadata::new(),
636 Moo::new(left),
637 Moo::new(right),
638 )))
639 }
640 "%" => {
641 doc_name = "L_Mod";
643 Ok(Some(Expression::UnsafeMod(
644 Metadata::new(),
645 Moo::new(left),
646 Moo::new(right),
647 )))
648 }
649
650 "=" => {
651 doc_name = "L_Eq"; Ok(Some(Expression::Eq(
653 Metadata::new(),
654 Moo::new(left),
655 Moo::new(right),
656 )))
657 }
658 "!=" => {
659 doc_name = "L_Neq"; Ok(Some(Expression::Neq(
661 Metadata::new(),
662 Moo::new(left),
663 Moo::new(right),
664 )))
665 }
666 "<=" => {
667 doc_name = "L_Leq"; Ok(Some(Expression::Leq(
669 Metadata::new(),
670 Moo::new(left),
671 Moo::new(right),
672 )))
673 }
674 ">=" => {
675 doc_name = "L_Geq"; Ok(Some(Expression::Geq(
677 Metadata::new(),
678 Moo::new(left),
679 Moo::new(right),
680 )))
681 }
682 "<" => {
683 doc_name = "L_Lt"; Ok(Some(Expression::Lt(
685 Metadata::new(),
686 Moo::new(left),
687 Moo::new(right),
688 )))
689 }
690 ">" => {
691 doc_name = "L_Gt"; Ok(Some(Expression::Gt(
693 Metadata::new(),
694 Moo::new(left),
695 Moo::new(right),
696 )))
697 }
698
699 "->" => {
700 doc_name = "L_Imply"; Ok(Some(Expression::Imply(
702 Metadata::new(),
703 Moo::new(left),
704 Moo::new(right),
705 )))
706 }
707 "<->" => {
708 doc_name = "L_Iff"; Ok(Some(Expression::Iff(
710 Metadata::new(),
711 Moo::new(left),
712 Moo::new(right),
713 )))
714 }
715 "<lex" => {
716 doc_name = "L_LexLt"; Ok(Some(Expression::LexLt(
718 Metadata::new(),
719 Moo::new(left),
720 Moo::new(right),
721 )))
722 }
723 ">lex" => {
724 doc_name = "L_LexGt"; Ok(Some(Expression::LexGt(
726 Metadata::new(),
727 Moo::new(left),
728 Moo::new(right),
729 )))
730 }
731 "<=lex" => {
732 doc_name = "L_LexLeq"; Ok(Some(Expression::LexLeq(
734 Metadata::new(),
735 Moo::new(left),
736 Moo::new(right),
737 )))
738 }
739 ">=lex" => {
740 doc_name = "L_LexGeq"; Ok(Some(Expression::LexGeq(
742 Metadata::new(),
743 Moo::new(left),
744 Moo::new(right),
745 )))
746 }
747 "in" => {
748 doc_name = "L_in";
749 Ok(Some(Expression::In(
750 Metadata::new(),
751 Moo::new(left),
752 Moo::new(right),
753 )))
754 }
755 "subset" => {
756 doc_name = "L_subset";
757 Ok(Some(Expression::Subset(
758 Metadata::new(),
759 Moo::new(left),
760 Moo::new(right),
761 )))
762 }
763 "subsetEq" => {
764 doc_name = "L_subsetEq";
765 Ok(Some(Expression::SubsetEq(
766 Metadata::new(),
767 Moo::new(left),
768 Moo::new(right),
769 )))
770 }
771 "supset" => {
772 doc_name = "L_supset";
773 Ok(Some(Expression::Supset(
774 Metadata::new(),
775 Moo::new(left),
776 Moo::new(right),
777 )))
778 }
779 "supsetEq" => {
780 doc_name = "L_supsetEq";
781 Ok(Some(Expression::SupsetEq(
782 Metadata::new(),
783 Moo::new(left),
784 Moo::new(right),
785 )))
786 }
787 "union" => {
788 doc_name = "L_union";
789 Ok(Some(Expression::Union(
790 Metadata::new(),
791 Moo::new(left),
792 Moo::new(right),
793 )))
794 }
795 "intersect" => {
796 doc_name = "L_intersect";
797 Ok(Some(Expression::Intersect(
798 Metadata::new(),
799 Moo::new(left),
800 Moo::new(right),
801 )))
802 }
803 _ => {
804 ctx.record_error(RecoverableParseError::new(
805 format!("Invalid operator: '{op_str}'"),
806 Some(op_node.range()),
807 ));
808 Ok(None)
809 }
810 };
811
812 if expr.is_ok() {
813 ctx.add_span_and_doc_hover(&op_node, doc_name, SymbolKind::Function, None, None);
814 }
815
816 expr
817}