Return the index pairs of matched braces in `pat` as a list of tuples. The dictionary's keys are the indices corresponding to opening braces. Initially, they are set to a value of `None`. Once a corresponding closing brace is found, the value is updated. All dictionary keys with a posit
(pat)
| 69 | |
| 70 | |
| 71 | def _parse_braces(pat): |
| 72 | """Return the index pairs of matched braces in `pat` as a list of tuples. |
| 73 | |
| 74 | The dictionary's keys are the indices corresponding to opening braces. Initially, |
| 75 | they are set to a value of `None`. Once a corresponding closing brace is found, |
| 76 | the value is updated. All dictionary keys with a positive integer value are valid pairs. |
| 77 | |
| 78 | We cannot rely on re.match("[^\\(\\\\)*]?{.*[^\\(\\\\)*]}") because, while it |
| 79 | handles unpaired braces and nested pairs of braces, it misses sequences |
| 80 | of paired braces. For example: "{foo,bar}{bar,baz}" would translate, incorrectly, to |
| 81 | "(foo|bar\\}\\{bar|baz)" instead of, correctly, to "(foo|bar)(bar|baz)". |
| 82 | |
| 83 | Therefore, this function parses left-to-right, tracking pairs with a LIFO |
| 84 | queue: pushing opening braces on and popping them off when finding a closing |
| 85 | brace. |
| 86 | """ |
| 87 | curly_q = LifoQueue() |
| 88 | pairs: dict[int, int] = dict() |
| 89 | |
| 90 | for idx, c in enumerate(pat): |
| 91 | if c == "{": |
| 92 | if idx == 0 or pat[idx - 1] != "\\": |
| 93 | # Opening brace is not escaped. |
| 94 | # Add to dict |
| 95 | pairs[idx] = None |
| 96 | # Add to queue |
| 97 | curly_q.put(idx) |
| 98 | if c == "}" and curly_q.qsize(): |
| 99 | # If queue is empty, then cannot close pair. |
| 100 | if idx > 0 and pat[idx - 1] != "\\": |
| 101 | # Closing brace is not escaped. |
| 102 | # Pop off the index of the corresponding opening brace, which |
| 103 | # provides the key in the dict of pairs, and set its value. |
| 104 | pairs[curly_q.get()] = idx |
| 105 | return [(opening, closing) for opening, closing in pairs.items() if closing is not None] |
| 106 | |
| 107 | |
| 108 | def _translate_alternatives(pat): |
no test coverage detected