@@ -660,7 +660,7 @@ Region::Region(std::string region_spec, int32_t cell_id)
660660 if (token == OP_UNION) {
661661 simple_ = false ;
662662 // Ensure intersections have precedence over unions
663- add_precedence ();
663+ enforce_precedence ();
664664 break ;
665665 }
666666 }
@@ -703,7 +703,7 @@ void Region::apply_demorgan(
703703// ! precedence than unions using parentheses.
704704// ==============================================================================
705705
706- int64_t Region::add_parentheses (int64_t start)
706+ void Region::add_parentheses (int64_t start)
707707{
708708 int32_t start_token = expression_[start];
709709 // Add left parenthesis and set new position to be after parenthesis
@@ -712,14 +712,6 @@ int64_t Region::add_parentheses(int64_t start)
712712 }
713713 expression_.insert (expression_.begin () + start - 1 , OP_LEFT_PAREN);
714714
715- // Keep track of return iterator distance. If we don't encounter a left
716- // parenthesis, we return an iterator corresponding to wherever the right
717- // parenthesis is inserted. If a left parenthesis is encountered, an iterator
718- // corresponding to the left parenthesis is returned. Also note that we keep
719- // track of a *distance* instead of an iterator because the underlying memory
720- // allocation may change.
721- std::size_t return_it_dist = 0 ;
722-
723715 // Add right parenthesis
724716 // While the start iterator is within the bounds of infix
725717 while (start + 1 < expression_.size ()) {
@@ -733,7 +725,6 @@ int64_t Region::add_parentheses(int64_t start)
733725 // in the region, when the operator is an intersection then include the
734726 // operator and next surface
735727 if (expression_[start] == OP_LEFT_PAREN) {
736- return_it_dist = start;
737728 int depth = 1 ;
738729 do {
739730 start++;
@@ -750,54 +741,73 @@ int64_t Region::add_parentheses(int64_t start)
750741 --start;
751742 }
752743 expression_.insert (expression_.begin () + start, OP_RIGHT_PAREN);
753- if (return_it_dist > 0 ) {
754- return return_it_dist;
755- } else {
756- return start - 1 ;
757- }
744+ return ;
758745 }
759746 }
760747 }
761- // If we get here a right parenthesis hasn't been placed,
762- // return iterator
748+ // If we get here a right parenthesis hasn't been placed
763749 expression_.push_back (OP_RIGHT_PAREN);
764- if (return_it_dist > 0 ) {
765- return return_it_dist;
766- } else {
767- return start - 1 ;
768- }
769750}
770751
752+ // ==============================================================================
753+ // ! Add parentheses to enforce operator precedence in region expressions
754+ // !
755+ // ! This function ensures that intersection operators have higher precedence
756+ // ! than union operators by adding parentheses where needed. For example:
757+ // ! "1 2 | 3" becomes "(1 2) | 3"
758+ // ! "1 | 2 3" becomes "1 | (2 3)"
759+ // !
760+ // ! The algorithm uses stacks to track the current operator type and its
761+ // ! position at each parenthesis depth level. When it encounters a different
762+ // ! operator at the same depth, it adds parentheses to group the
763+ // ! higher-precedence operations.
771764// ==============================================================================
772765
773- void Region::add_precedence ()
766+ void Region::enforce_precedence ()
774767{
775- int32_t current_op = 0 ;
776- std:: size_t current_dist = 0 ;
768+ // Stack tracking the operator type at each depth (0 = no operator seen yet)
769+ vector< int32_t > op_stack = { 0 } ;
777770
778- for (int64_t i = 0 ; i < expression_.size (); i++) {
771+ // Stack tracking where the operator sequence started at each depth
772+ vector<std::size_t > pos_stack = {0 };
773+
774+ for (int64_t i = 0 ; i < expression_.size (); ++i) {
779775 int32_t token = expression_[i];
780776
777+ if (token == OP_LEFT_PAREN) {
778+ // Entering a new parenthesis level - push new tracking state
779+ op_stack.push_back (0 );
780+ pos_stack.push_back (0 );
781+ continue ;
782+ } else if (token == OP_RIGHT_PAREN) {
783+ // Exiting a parenthesis level - pop tracking state (keep at least one)
784+ if (op_stack.size () > 1 ) {
785+ op_stack.pop_back ();
786+ pos_stack.pop_back ();
787+ }
788+ continue ;
789+ }
790+
781791 if (token == OP_UNION || token == OP_INTERSECTION) {
782- if (current_op == 0 ) {
783- // Set the current operator if is hasn't been set
784- current_op = token;
785- current_dist = i;
786- } else if (token != current_op) {
787- // If the current operator doesn't match the token, add parenthesis to
788- // assert precedence
789- if (current_op == OP_INTERSECTION) {
790- i = add_parentheses (current_dist);
792+ if (op_stack.back () == 0 ) {
793+ // First operator at this depth - record it and its position
794+ op_stack.back () = token;
795+ pos_stack.back () = i;
796+ } else if (token != op_stack.back ()) {
797+ // Encountered a different operator at the same depth - need to add
798+ // parentheses to enforce precedence. Intersection has higher
799+ // precedence, so we parenthesize the intersection terms.
800+ if (op_stack.back () == OP_INTERSECTION) {
801+ add_parentheses (pos_stack.back ());
791802 } else {
792- i = add_parentheses (i);
803+ add_parentheses (i);
793804 }
794- current_op = 0 ;
795- current_dist = 0 ;
805+
806+ // Restart the scan since we modified the expression
807+ i = -1 ; // Will be incremented to 0 by the for loop
808+ op_stack = {0 };
809+ pos_stack = {0 };
796810 }
797- } else if (token > OP_COMPLEMENT) {
798- // If the token is a parenthesis reset the current operator
799- current_op = 0 ;
800- current_dist = 0 ;
801811 }
802812 }
803813}
0 commit comments