ANTLR4 is a powerful parser generator toolkit that produces clean, maintainable lexer and parser code for multiple target languages. Its intuitive grammar syntax, built-in support for operator precedence, and flexible tree-walking APIs make it ideal for building domain-specific languages, configuration parsers, and language tooling.
Setup and Toolchain Configuration
To begin using ANTLR4 from the command line, download the runtime JAR and configure shell aliases:
Add to ~/.zshrc or ~/.bashrc
export CLASSPATH=".:/usr/local/lib/antlr/antlr-4.13.1-complete.jar:$CLASSPATH" alias antlr4='java -Xmx500M -cp "/usr/local/lib/antlr/antlr-4.13.1-complete.jar:$CLASSPATH" org.antlr.v4.Tool' alias grun='java -cp "/usr/local/lib/antlr/antlr-4.13.1-complete.jar:$CLASSPATH" org.antlr.v4.gui.TestRig'
</div>Grammar Structure Fundamentals
------------------------------
ANTLR4 grammars follow strict naming and casing conventions:
- Grammar name must match the filename (e.g., `Expr.g4` declares `grammar Expr;`)
- Parser rules start with lowercase letters
- Lexer rules (tokens) start with uppercase letters
- All lexer rules are automatically grouped after parser rules during compilation
- Rules are matched in declaration order—earlier definitions take precedence
- Literals use single quotes: `'if'`, `'=='`
- Alternatives use `|`; grouping uses parentheses; quantifiers `?`, `+`, `*` behave like regex
- Rule alternatives can be labeled with `# label` to generate distinct context classes
- No explicit start rule required—the first parser rule serves as default entry point
- Semicolons terminate all rules
- Comments: `// line` and `/* block */`
- Associativity defaults to left; override with `<assoc=right>`
- Direct left recursion is supported; indirect left recursion is not
- Use `fragment` to define reusable lexer subrules
### Example Grammar Snippet
<div class="code-block">```
program: statement+ ;
statement
: expression NEWLINE # showResult
| IDENTIFIER '=' expression NEWLINE # storeValue
| NEWLINE # skipEmpty
;
expression
: expression '^' expression # powerExpr
| expression ('*'|'/') expression # productExpr
| expression ('+'|'-') expression # sumExpr
| NUMBER # numberLiteral
| IDENTIFIER # variableRef
| '(' expression ')' # groupedExpr
;
IDENTIFIER : [a-zA-Z_][a-zA-Z_0-9]* ;
NUMBER : [0-9]+ ('.' [0-9]+)? | '.' [0-9]+ ;
NEWLINE : '\r'? '\n' ;
WS : [ \t\r\n]+ -> skip ;
Reusable lexer constructs simplify grammar maintenance:
// Keywords (case-sensitive) IF : 'if' ; ELSE : 'else' ; WHILE : 'while' ;
// Identifiers IDENTIFIER : LETTER (LETTER | DIGIT)* ; fragment LETTER : [a-zA-Z_] ; fragment DIGIT : [0-9] ;
// Numeric literals INTEGER : DIGIT+ ; FLOAT : DIGIT+ '.' DIGIT* | '.' DIGIT+ ; SCIENTIFIC : (INTEGER | FLOAT) [eE] [+-]? DIGIT+ ;
// Strings with escape handling STRING : '"' (~["\\r\n] | ESCAPE)* '"' ; fragment ESCAPE : '\' ([btnrf"'\] | UNICODE) ; fragment UNICODE : 'u' HEX HEX HEX HEX ; fragment HEX : [0-9a-fA-F] ;
// Comments LINE_COMMENT : '//' ~[\r\n]* -> skip ; BLOCK_COMMENT : '/' .? '*/' -> skip ;
</div>Code Generation Options
-----------------------
Control generated artifacts using CLI flags:
<div class="code-block">```
$ antlr4 -visitor -no-listener Expr.g4
$ ls
Expr.g4 ExprBaseVisitor.java ExprListener.java
Expr.tokens ExprLexer.java ExprParser.java
ExprBaseListener.java ExprLexer.tokens ExprVisitor.java
ExprLexer.java: Tokenizes input character streamsExprParser.java: Builds parse trees from token streamsExprVisitor.java&ExprBaseVisitor.java: Visitor pattern interfaces and stubsExprListener.java&ExprBaseListener.java: Listener pattern interfaces and stubsExpr.tokens: Token type constants mapping
Integrating Generated Code
A minimal driver program demonstrates the full pipeline:
public class ExpressionEvaluator { public static void main(String[] args) throws Exception { CharStream input = CharStreams.fromFileName("input.txt"); ExprLexer lexer = new ExprLexer(input); CommonTokenStream tokens = new CommonTokenStream(lexer); ExprParser parser = new ExprParser(tokens);
ParseTree tree = parser.program(); // entry rule
// Visualize structure
System.out.println("Parse tree (LISP format):");
System.out.println(tree.toStringTree(parser));
// Evaluate using visitor
EvalVisitor evaluator = new EvalVisitor();
evaluator.visit(tree);
// Or walk with listener
ParseTreeWalker walker = new ParseTreeWalker();
walker.walk(new SymbolCollector(), tree);
} }
</div>Visitor Pattern Deep Dive
-------------------------
The visitor pattern decouples algorithm implementation from data structure traversal. Each grammar rule labeled with `# label` yields a corresponding `visitLabel()` method in the generated visitor interface.
For example, given this rule:
`expr: expr '*' expr # multiply ;`The generated visitor includes:
`@Override public T visitMultiply(ExprParser.MultiplyContext ctx) { ... }`Within the visitor method, navigate child contexts using `ctx.expr(0)`, `ctx.ID()`, or `ctx.getText()`. Return values propagate up the call stack, enabling natural expression evaluation.
### Evaluation Visitor Implementation
<div class="code-block">```
import java.util.HashMap;
import java.util.Map;
public class EvalVisitor extends ExprBaseVisitor<double> {
private final Map<String, Double> memory = new HashMap<>();
@Override public Double visitStoreValue(ExprParser.StoreValueContext ctx) {
String name = ctx.IDENTIFIER().getText();
Double value = visit(ctx.expression());
memory.put(name, value);
return value;
}
@Override public Double visitShowResult(ExprParser.ShowResultContext ctx) {
Double result = visit(ctx.expression());
System.out.println("→ " + result);
return result;
}
@Override public Double visitNumberLiteral(ExprParser.NumberLiteralContext ctx) {
return Double.parseDouble(ctx.NUMBER().getText());
}
@Override public Double visitVariableRef(ExprParser.VariableRefContext ctx) {
String name = ctx.IDENTIFIER().getText();
return memory.getOrDefault(name, 0.0);
}
@Override public Double visitProductExpr(ExprParser.ProductExprContext ctx) {
Double left = visit(ctx.expression(0));
Double right = visit(ctx.expression(1));
return ctx.MULTIPLY() != null ? left * right : left / right;
}
@Override public Double visitSumExpr(ExprParser.SumExprContext ctx) {
Double left = visit(ctx.expression(0));
Double right = visit(ctx.expression(1));
return ctx.ADD() != null ? left + right : left - right;
}
}
</double>
Listeners operate via callback methods triggered during automatic tree traversal. The ParseTreeWalker calls enterXxx() before visiting a node and exitXxx() after visiting its children.
State must be managed externally—typically using ParseTreeProperty<T> to associate computed values with specific parse tree nodes:
public class SymbolCollector extends ExprBaseListener { private final ParseTreeProperty<Double> computedValues = new ParseTreeProperty<>(); private final Map<String, Double> symbolTable = new HashMap<>();
@Override public void exitStoreValue(ExprParser.StoreValueContext ctx) { String id = ctx.IDENTIFIER().getText(); Double val = computedValues.get(ctx.expression()); symbolTable.put(id, val); }
@Override public void exitNumberLiteral(ExprParser.NumberLiteralContext ctx) { computedValues.put(ctx, Double.parseDouble(ctx.NUMBER().getText())); }
@Override public void exitProductExpr(ExprParser.ProductExprContext ctx) { Double lhs = computedValues.get(ctx.expression(0)); Double rhs = computedValues.get(ctx.expression(1)); double result = ctx.MULTIPLY() != null ? lhs * rhs : lhs / rhs; computedValues.put(ctx, result); } }
</div>Visitor vs Listener: When to Use Which
--------------------------------------
**Use Visitor when:**
- You need fine-grained control over traversal order
- Implementing interpreters with conditional execution or loops
- Computing values bottom-up with natural return semantics
**Use Listener when:**
- Building ASTs or symbol tables
- Performing semantic analysis requiring multiple passes
- Processing large inputs where manual recursion management is error-prone
IDE Integration Tips
--------------------
In IntelliJ IDEA:
- Add `antlr-4.13.1-complete.jar` to project libraries
- Install the official *ANTLR v4 Grammar Plugin* for syntax highlighting and navigation
- Configure ANTLR tool location in *Settings → Languages & Frameworks → ANTLR*
- Enable auto-generation on grammar save via *Generate Parser* checkbox