antlr/antlr4 · error · ArgumentException

The ATN must be a lexer ATN.

Error message

The ATN must be a lexer ATN.

What it means

LexerInterpreter executes a serialized lexer ATN directly (used for interpreted, non-generated lexing). Its constructor validates that the supplied ATN has grammarType == ATNType.Lexer, because everything downstream — LexerATNSimulator, mode handling, action dispatch — assumes lexer ATN structure. Supplying a parser ATN would fail much later and confusingly, so the constructor rejects it up front.

Source

Thrown at runtime/CSharp/src/LexerInterpreter.cs:45

        [NotNull]
        private readonly IVocabulary vocabulary;

        protected DFA[] decisionToDFA;
        protected PredictionContextCache sharedContextCache = new PredictionContextCache();

        [Obsolete("Use constructor with channelNames argument")]
        public LexerInterpreter(string grammarFileName, IVocabulary vocabulary, IEnumerable<string> ruleNames, IEnumerable<string> modeNames, ATN atn, ICharStream input)
            : this(grammarFileName, vocabulary, ruleNames, Collections.EmptyList<string>(), modeNames, atn, input)
        {
        }

        public LexerInterpreter(string grammarFileName, IVocabulary vocabulary, IEnumerable<string> ruleNames, IEnumerable<string> channelNames, IEnumerable<string> modeNames, ATN atn, ICharStream input)
            : base(input)
        {
            if (atn.grammarType != ATNType.Lexer)
            {
                throw new ArgumentException("The ATN must be a lexer ATN.");
            }
            this.grammarFileName = grammarFileName;
            this.atn = atn;
            this.ruleNames = ruleNames.ToArray();
            this.channelNames = channelNames.ToArray();
            this.modeNames = modeNames.ToArray();
            this.vocabulary = vocabulary;
            this.decisionToDFA = new DFA[atn.NumberOfDecisions];
            for (int i = 0; i < decisionToDFA.Length; i++)
            {
                decisionToDFA[i] = new DFA(atn.GetDecisionState(i), i);
            }
            this.Interpreter = new LexerATNSimulator(this, atn, decisionToDFA, sharedContextCache);
        }

        public override ATN Atn
        {
            get

View on GitHub (pinned to 7d5770395b)

Solutions

  1. Check atn.grammarType == ATNType.Lexer before constructing LexerInterpreter (and == ATNType.Parser for ParserInterpreter)
  2. Store grammarType alongside serialized ATNs when you persist them, and validate on load
  3. In split grammars, keep the lexer and parser ATNs in separately named fields and never share variables between them

Example fix

// before
var li = new LexerInterpreter(g.name, g.vocab, g.ruleNames, g.modeNames, g.atn, input); // g.atn is the parser ATN

// after
var atn = new ATNDeserializer().Deserialize(g.atn);
if (atn.grammarType != ATNType.Lexer)
    throw new ArgumentException($"{g.name}: expected lexer ATN, got {atn.grammarType}");
var li = new LexerInterpreter(g.name, g.vocab, g.ruleNames, g.modeNames, atn, input);
Defensive patterns

Strategy: validation

Validate before calling

if (atn.grammarType != ATNType.Lexer)
    throw new ArgumentException($"Expected lexer ATN, got {atn.grammarType}", nameof(atn));

Type guard

static bool IsLexerAtn(ATN atn) => atn != null && atn.grammarType == ATNType.Lexer;

Prevention

When it happens

Trigger: Passing an ATN deserialized from a parser's serialized ATN constant into LexerInterpreter, or sharing one ATN object between parser and lexer interpreter paths by mistake. Also grammar-type mixups when loading ATNs from files/registries by name.

Common situations: Dynamic grammar interpretation tools that hold ATNs for both a parser and a lexer and pass the wrong one; caching ATNs by grammar name where parser and lexer share a name (split grammar 'MyGrammar' lexer/parser); loading serialized ATNs from a config store without recording their type.

Related errors


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