antlr/antlr4 · error · LexerNoViableAltException

LexerNoViableAltException + symbol

Error message

LexerNoViableAltException + symbol

What it means

NewLexerNoViableAltException is panicked by LexerATNSimulator.failOrAccept() in the Go runtime when the lexer DFA cannot match any rule from the current input position and the input is not EOF at the start index. It means the character(s) at startIndex are not recognized by any lexer rule in the current mode.

Source

Thrown at runtime/Go/antlr/v4/lexer_atn_simulator.go:267

		return ATNSimulatorError
	}
	// Add an edge from s to target DFA found/created for reach
	return l.addDFAEdge(s, t, nil, reach)
}

func (l *LexerATNSimulator) failOrAccept(prevAccept *SimState, input CharStream, reach *ATNConfigSet, t int) int {
	if l.prevAccept.dfaState != nil {
		lexerActionExecutor := prevAccept.dfaState.lexerActionExecutor
		l.accept(input, lexerActionExecutor, l.startIndex, prevAccept.index, prevAccept.line, prevAccept.column)
		return prevAccept.dfaState.prediction
	}

	// if no accept and EOF is first char, return EOF
	if t == TokenEOF && input.Index() == l.startIndex {
		return TokenEOF
	}

	panic(NewLexerNoViableAltException(l.recog, input, l.startIndex, reach))
}

// getReachableConfigSet when given a starting configuration set, figures out all [ATN] configurations
// we can reach upon input t.
//
// Parameter reach is a return parameter.
func (l *LexerATNSimulator) getReachableConfigSet(input CharStream, closure *ATNConfigSet, reach *ATNConfigSet, t int) {
	// l is used to Skip processing for configs which have a lower priority
	// than a runtimeConfig that already reached an accept state for the same rule
	SkipAlt := ATNInvalidAltNumber

	for _, cfg := range closure.configs {
		currentAltReachedAcceptState := cfg.GetAlt() == SkipAlt
		if currentAltReachedAcceptState && cfg.passedThroughNonGreedyDecision {
			continue
		}

		if runtimeConfig.lexerATNSimulatorDebug {

View on GitHub (pinned to 7d5770395b)

Solutions

  1. Add a catch-all lexer rule such as ERROR : . ; (or skip/tokenize unknown characters) in the grammar
  2. Pre-validate or sanitize input before lexing
  3. Wrap lexing in a recover() that converts the panic to an error, and report the offending line/column from the exception

Example fix

// grammar before
// (no rule matches '@')

// grammar after: catch-all rule
// ERROR : . ;

// Go guard
func safeLex(l *antlr.Lexer, ts *antlr.CommonTokenStream) (err error) {
    defer func() {
        if r := recover(); r != nil {
            if e, ok := r.(*antlr.LexerNoViableAltException); ok {
                err = fmt.Errorf("bad input at line %d:%d", e.GetLine(), e.GetColumn())
                return
            }
            panic(r)
        }
    }()
    ts.Fill()
    return nil
}
Defensive patterns

Strategy: try-catch

Type guard

func isLexerNoViableAlt(r interface{}) bool {
    _, ok := r.(*antlr.LexerNoViableAltException)
    return ok
}

Try / catch

func safeFill(ts *antlr.CommonTokenStream) (err error) {
    defer func() {
        if r := recover(); r != nil {
            if e, ok := r.(*antlr.LexerNoViableAltException); ok {
                err = fmt.Errorf("lexer failed at line %d, col %d", e.GetLine(), e.GetColumn())
                return
            }
            panic(r)
        }
    }()
    ts.Fill()
    return nil
}

Prevention

When it happens

Trigger: Feeding input to the generated Go lexer containing a character with no matching lexer rule (e.g. '@' when no rule accepts it), or reaching a mode whose rules do not cover the next character. The Go runtime panics rather than throwing, so unrecovered it crashes the program.

Common situations: Missing catch-all lexer rule, unexpected unicode/whitespace in input, wrong mode after a pushMode, or feeding binary data to a text lexer.

Related errors


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