antlr/antlr4 · error · Exception

unterminated tag in pattern: %s

Error message

unterminated tag in pattern: %s

What it means

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.

Source

Thrown at runtime/Python3/src/antlr4/tree/ParseTreePatternMatcher.py:330

        stops = list()
        while p < n :
            if p == pattern.find(self.escape + self.start, p):
                p += len(self.escape) + len(self.start)
            elif p == pattern.find(self.escape + self.stop, p):
                p += len(self.escape) + len(self.stop)
            elif p == pattern.find(self.start, p):
                starts.append(p)
                p += len(self.start)
            elif p == pattern.find(self.stop, p):
                stops.append(p)
                p += len(self.stop)
            else:
                p += 1

        nt = len(starts)

        if nt > len(stops):
            raise Exception("unterminated tag in pattern: " + pattern)
        if nt < len(stops):
            raise Exception("missing start tag in pattern: " + pattern)

        for i in range(0, nt):
            if starts[i] >= stops[i]:
                raise Exception("tag delimiters out of order in pattern: " + pattern)

        # collect into chunks now
        if nt==0:
            chunks.append(TextChunk(pattern))

        if nt>0 and starts[0]>0: # copy text up to first tag into chunks
            text = pattern[0:starts[0]]
            chunks.add(TextChunk(text))

        for i in range(0, nt):
            # copy inside of <tag>
            tag = pattern[starts[i] + len(self.start) : stops[i]]

View on GitHub (pinned to 7d5770395b)

Solutions

  1. Close every tag: ensure each '<' has a matching '>'
  2. Escape literal '<' or '>' in the pattern with the configured escape sequence (default '\\')
  3. If matching angle-bracket-heavy text, change delimiters with setDelimiters to something unused

Example fix

# before
pattern = parser.compilePattern('<ID < <ID', 0)

# after: escape the literal '<'
pattern = parser.compilePattern('<ID> \\< <ID>', 0)
Defensive patterns

Strategy: validation

Validate before calling

start_d, stop_d = matcher.start, matcher.stop
if pattern.count(start_d) > pattern.count(stop_d):
    raise ValueError('unterminated tag in pattern')

Try / catch

try:
    p = matcher.compileTreePattern(pat, idx)
except Exception as e:
    if 'unterminated tag' in str(e):
        # fix unbalanced '<' or escape it
        ...

Prevention

When it happens

Trigger: Compiling a pattern like '<ID + <ID>' (missing '>') or one containing an unescaped literal '<' while '<' is the start delimiter.

Common situations: Matching text that legitimately contains '<' (generics, comparisons, HTML) without escaping it, or a truncated pattern string.

Related errors


AI-assisted analysis of antlr/antlr4@7d5770395b (2026-08-14). Data as JSON: /api/errors/5048f88431060aa7. Report an issue: GitHub.