Motivation¶
ANTLR is an excellent parser generator supporting multiple programming languages,
and its Python target is convenient — you write a .g4 grammar, generate Python,
and consume the parse with a listener or visitor. But the generated parser runs in
pure Python, and for large inputs that is slow: the official antlr4-python3-runtime
is typically an order of magnitude slower than the C++ runtime, and a Python ParseTreeListener
over the parse pays a foreign-function crossing per tree node — which, for
documents with millions of nodes, dominates everything else.
The usual escape hatch is to switch your whole toolchain to the C++ (or Java) target: generate a C++ parser, compile it, and write your application logic in C++. That's a large step, and it throws away the convenience of staying in Python.
Antlrope takes a different path. It drives the official ANTLR4 C++
runtime's ATN interpreter directly from the serialized ATN that the stock
-Dlanguage=Python3 ANTLR tool already emits — so there is no per-grammar C++
codegen and nothing for you to compile. The parse runs in C++, and instead of one
Python call per node, a single bulk, filtered event stream crosses into Python in
one batch. You keep writing plain-Python listeners; you just get C++ parsing speed
under them (typically 10–20× the pure-Python runtime, and more when you
subscribe to only part of the grammar).
The goal is to make the fast path the easy path: if you can write an ANTLR grammar and a Python class, you shouldn't have to learn C++ or change your build to parse quickly.
The name¶
antlrope is a pun on antelope that extends ANTLR's antler imagery — where ope stands for Ordered Parse Events: the distinctive thing this runtime does is hand Python the parse as one depth-first-ordered stream of events (rule-enter, rule-exit, terminal, error) rather than a per-node parse-tree walk.
When the official runtime is the right choice¶
Antlrope does not replace antlr4-python3-runtime — it targets the
throughput case. Prefer the official runtime when your grammar strongly relies on semantic
predicates or embedded actions, when you need the retained parse tree (random
access, rewriting, re-walking) rather than a single streaming pass, or when the
input is small enough that per-node cost doesn't matter. See
Migrating and
Performance & limitations for the full boundary.