antlr/antlr4 · error · LexerNoViableAltException

LexerNoViableAltException('{}')

Error message

LexerNoViableAltException('{}')

What it means

LexerATNSimulator.failOrAccept() raises LexerNoViableAltException when the DFA could not reach any accept state for the input from startIndex (and the input is not the immediate-EOF case, which returns Token.EOF). It is the lexer-side analogue of a syntax error: no lexer rule alternative matches the remaining characters, so no token can be produced.

Source

Thrown at runtime/Python3/src/antlr4/atn/LexerATNSimulator.py:254

                # cause a failover from DFA later.
               self. addDFAEdge(s, t, self.ERROR)

            # stop when we can't match any more char
            return self.ERROR

        # Add an edge from s to target DFA found/created for reach
        return self.addDFAEdge(s, t, cfgs=reach)

    def failOrAccept(self, prevAccept:SimState , input:InputStream, reach:ATNConfigSet, t:int):
        if self.prevAccept.dfaState is not None:
            lexerActionExecutor = prevAccept.dfaState.lexerActionExecutor
            self.accept(input, lexerActionExecutor, self.startIndex, prevAccept.index, prevAccept.line, prevAccept.column)
            return prevAccept.dfaState.prediction
        else:
            # if no accept and EOF is first char, return EOF
            if t==Token.EOF and input.index==self.startIndex:
                return Token.EOF
            raise LexerNoViableAltException(self.recog, input, self.startIndex, reach)

    # Given a starting configuration set, figure out all ATN configurations
    #  we can reach upon input {@code t}. Parameter {@code reach} is a return
    #  parameter.
    def getReachableConfigSet(self, input:InputStream, closure:ATNConfigSet, reach:ATNConfigSet, t:int):
        # this is used to skip processing for configs which have a lower priority
        # than a config that already reached an accept state for the same rule
        skipAlt = ATN.INVALID_ALT_NUMBER
        for cfg in closure:
            currentAltReachedAcceptState = ( cfg.alt == skipAlt )
            if currentAltReachedAcceptState and cfg.passedThroughNonGreedyDecision:
                continue

            if LexerATNSimulator.debug:
                print("testing", self.getTokenName(t), "at",  str(cfg))

            for trans in cfg.state.transitions:          # for each transition
                target = self.getReachableTarget(trans, t)

View on GitHub (pinned to 7d5770395b)

Solutions

  1. Add a catch-all lexer rule (e.g. UNEXPECTED: . ;) or explicitly enumerate the stray characters so the lexer can always produce a token, then handle it in the parser/listener.
  2. Attach a custom error listener (lexer.removeErrorListeners(); lexer.addErrorListener(...)) to report line/column instead of crashing.
  3. For mode grammars, audit pushMode/popMode pairing so each mode covers all inputs it can receive.
  4. Sanitize/validate input encoding (strip BOM, normalize unicode punctuation) before lexing.

Example fix

// before (grammar)
ID: [a-zA-Z]+ ; INT: [0-9]+ ; WS: [ \t\r\n]+ -> skip ;  // '$' -> LexerNoViableAltException

// after (grammar)
ID: [a-zA-Z]+ ; INT: [0-9]+ ; WS: [ \t\r\n]+ -> skip ;
UNEXPECTED: . ;  // always matches one char; report in parser/listener
Defensive patterns

Strategy: try-catch

Validate before calling

# Grammar-level guard: guarantee the lexer can always match something
# UNEXPECTED: . ;   // add last in the lexer grammar

# Code-level guard: pre-scan input characters against a coverage set
allowed = set('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 \t\r\n')
if any(ch not in allowed for ch in text):
    raise ValueError('input contains characters the lexer grammar cannot match')

Try / catch

from antlr4.error.Errors import LexerNoViableAltException
class CollectingErrorListener(ErrorListener):
    def syntaxError(self, recognizer, offendingSymbol, line, column, msg, e):
        errors.append((line, column, msg))

lexer.removeErrorListeners()
lexer.addErrorListener(CollectingErrorListener())
tokens = lexer  # errors collected instead of raising; inspect lexer.symbolicNames for the offending text

Prevention

When it happens

Trigger: Input at the error position matches no lexer rule — an unrecognized character or sequence (e.g. '$' when the grammar has no rule covering it), or a mode-specific grammar receiving input not covered in the current lexer mode.

Common situations: Missing a catch-all rule like UNRECOGNIZED: . ; in the lexer; switching lexer modes (pushMode) and hitting characters only valid in another mode; UTF-8/BOM bytes or smart quotes in input when the grammar only covers ASCII; feeding the parser's lexer a stream of a different file type.

Related errors


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