antlr/antlr4 · error · ValueError
should be only one op per index
Error message
should be only one op per index
What it means
Final invariant check of the reduction pass: after merging, at most one rewrite operation may remain per token index. If two operations of different kinds (e.g. an insert and a delete at the same index, or two inserts at the same index of different op types that were not merged) survive, the output order would be ambiguous, so ANTLR raises this ValueError.
Source
Thrown at runtime/Python3/src/antlr4/TokenStreamRewriter.py:194
iop.text += prevIop.text
rewrites[prev_index] = None
elif prevIop.index == iop.index and type(prevIop) is TokenStreamRewriter.InsertAfterOp:
iop.text = prevIop.text + iop.text
rewrites[prev_index] = None
# look for replaces where iop.index is in range; error
prevReplaces = [op for op in rewrites[:i] if isinstance(op, TokenStreamRewriter.ReplaceOp)]
for rop in prevReplaces:
if iop.index == rop.index:
rop.text = iop.text + rop.text
rewrites[i] = None
continue
if all((iop.index >= rop.index, iop.index <= rop.last_index)):
raise ValueError("insert op {} within boundaries of previous {}".format(iop, rop))
reduced = {}
for i, op in enumerate(rewrites):
if op is None: continue
if reduced.get(op.index): raise ValueError('should be only one op per index')
reduced[op.index] = op
return reduced
class RewriteOperation(object):
__slots__ = ('tokens', 'index', 'text', 'instructionIndex')
def __init__(self, tokens, index, text=""):
"""
:type tokens: CommonTokenStream
:param tokens:
:param index:
:param text:
:return:
"""
self.tokens = tokens
self.index = index
self.text = textView on GitHub (pinned to 7d5770395b)
Solutions
- Deduplicate your edit list by index before calling the rewriter (keep one op per token index).
- Use one program per transformation pass and call getText(programName) after each pass so the program is rolled.
- Combine conflicting ops manually (insert text into the replacement string) instead of issuing both.
Example fix
# before rewriter.insertBefore(4, 'a') rewriter.deleteIndex(4) # two ops at index 4 # after rewriter.replace(4, 4, 'a') # single op: delete token 4 and emit 'a'
Defensive patterns
Strategy: validation
Validate before calling
seen = {}
for op in planned_ops: # (index, kind, text)
if op[0] in seen:
raise ValueError('duplicate rewrite at token index %d: %s vs %s' % (op[0], seen[op[0]], op))
seen[op[0]] = op Try / catch
try:
rewriter.getText()
except ValueError as e:
if 'only one op per index' in str(e):
# rebuild rewrites deduplicated by index
pass
else:
raise Prevention
- Deduplicate the edit list by token index before touching the rewriter
- One program per transformation pass, rendered immediately
- Combine an insert and a replace at the same index into a single replace with prepended text
When it happens
Trigger: Multiple operations that reduce to the same op.index without hitting any merge rule — for example a delete (replace with None) and an insertBefore at the same index, or overlapping op kinds at one index that the pairwise merges above do not cover.
Common situations: Accumulating edits from several listeners/visitors at the same token (common in formatter/transformer pipelines); replaying a recorded edit log twice on the same program.
Related errors
- replace: range invalid: {}..{}(size={})
- replace op boundaries of {} overlap with previous {}
- insert op {} within boundaries of previous {}
- Invalid state number.
- The object is read only.
AI-assisted analysis of antlr/antlr4@7d5770395b (2026-08-14).
Data as JSON: /api/errors/db4d78a69e71a965.
Report an issue: GitHub.