{"record":{"id":"5048f88431060aa7","repo":"antlr/antlr4","slug":"unterminated-tag-in-pattern-s","errorCode":null,"errorMessage":"unterminated tag in pattern: %s","messagePattern":"unterminated tag in pattern: (.+?)","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"runtime/Python3/src/antlr4/tree/ParseTreePatternMatcher.py","lineNumber":330,"sourceCode":"        stops = list()\n        while p < n :\n            if p == pattern.find(self.escape + self.start, p):\n                p += len(self.escape) + len(self.start)\n            elif p == pattern.find(self.escape + self.stop, p):\n                p += len(self.escape) + len(self.stop)\n            elif p == pattern.find(self.start, p):\n                starts.append(p)\n                p += len(self.start)\n            elif p == pattern.find(self.stop, p):\n                stops.append(p)\n                p += len(self.stop)\n            else:\n                p += 1\n\n        nt = len(starts)\n\n        if nt > len(stops):\n            raise Exception(\"unterminated tag in pattern: \" + pattern)\n        if nt < len(stops):\n            raise Exception(\"missing start tag in pattern: \" + pattern)\n\n        for i in range(0, nt):\n            if starts[i] >= stops[i]:\n                raise Exception(\"tag delimiters out of order in pattern: \" + pattern)\n\n        # collect into chunks now\n        if nt==0:\n            chunks.append(TextChunk(pattern))\n\n        if nt>0 and starts[0]>0: # copy text up to first tag into chunks\n            text = pattern[0:starts[0]]\n            chunks.add(TextChunk(text))\n\n        for i in range(0, nt):\n            # copy inside of <tag>\n            tag = pattern[starts[i] + len(self.start) : stops[i]]","sourceCodeStart":312,"sourceCodeEnd":348,"githubUrl":"https://github.com/antlr/antlr4/blob/7d5770395bb7b02eb56e7c62662cb1d7c08f42a3/runtime/Python3/src/antlr4/tree/ParseTreePatternMatcher.py#L312-L348","documentation":"Raised by ParseTreePatternMatcher.split() when the pattern contains more start delimiters than stop delimiters, i.e. a tag was opened but never closed. The splitter pairs starts and stops positionally; an unmatched start means the rest of the pattern cannot be chunked.","triggerScenarios":"Compiling a pattern like '<ID + <ID>' (missing '>') or one containing an unescaped literal '<' while '<' is the start delimiter.","commonSituations":"Matching text that legitimately contains '<' (generics, comparisons, HTML) without escaping it, or a truncated pattern string.","solutions":["Close every tag: ensure each '<' has a matching '>'","Escape literal '<' or '>' in the pattern with the configured escape sequence (default '\\\\')","If matching angle-bracket-heavy text, change delimiters with setDelimiters to something unused"],"exampleFix":"# before\npattern = parser.compilePattern('<ID < <ID', 0)\n\n# after: escape the literal '<'\npattern = parser.compilePattern('<ID> \\\\< <ID>', 0)","handlingStrategy":"validation","validationCode":"start_d, stop_d = matcher.start, matcher.stop\nif pattern.count(start_d) > pattern.count(stop_d):\n    raise ValueError('unterminated tag in pattern')","typeGuard":null,"tryCatchPattern":"try:\n    p = matcher.compileTreePattern(pat, idx)\nexcept Exception as e:\n    if 'unterminated tag' in str(e):\n        # fix unbalanced '<' or escape it\n        ...","preventionTips":["Escape literal delimiters with the escape sequence","Balance '<' and '>' counts before compiling"],"tags":["antlr","python","parse-tree-matching","pattern-compile"],"backgroundTag":null,"analyzedSha":"7d5770395bb7b02eb56e7c62662cb1d7c08f42a3","analyzedAt":"2026-08-14T14:47:56.354Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}