ANTLR4 Runtime for Dart

From version 4.9 onwards antlr's dart generated code is null sound safety compatible and sets the minimum dart sdk version to 2.12.0.

First steps

1. Install ANTLR4

The getting started guide should get you started.

2. Install the Dart ANTLR runtime

Each target language for ANTLR has a runtime package for running parser generated by ANTLR4. The runtime provides a common set of tools for using your parser.

Install the runtime with the same version as the main ANTLR tool:

Add this to your package's pubspec.yaml file:

...
dependencies:
  antlr4: <ANTLR version>
...

3. Generate your parser

You use the ANTLR4 “tool” to generate a parser. These will reference the ANTLR runtime, installed above.

Suppose you're using a UNIX system and have set up an alias for the ANTLR4 tool as described in the getting started guide. To generate your Dart parser, run the following command:

antlr4 -Dlanguage=Dart MyGrammar.g4

For a full list of antlr4 tool options, please visit the tool documentation page.

Complete example

Suppose you're using the JSON grammar from https://github.com/antlr/grammars-v4/tree/master/json.

Then, invoke antlr4 -Dlanguage=Dart JSON.g4. The result of this is a collection of .dart including:

  • JsonLexer.dart
  • JsonParser.dart
  • JsonBaseListener.dart
  • JsonListener.dart (if you have not activated the -no-listener option)
  • JsonVisitor.dart (if you have activated the -visitor option)

We'll write a small main func to call the generated parser/lexer (assuming they are separate). This one writes out the encountered ParseTreeContext's:

import 'package:antlr4/antlr4.dart';
import 'package:my_project/JSONParser.dart';
import 'package:my_project/JSONLexer.dart';

class TreeShapeListener implements ParseTreeListener {
  @override
  void enterEveryRule(ParserRuleContext ctx) {
    print(ctx.text);
  }

  @override
  void exitEveryRule(ParserRuleContext node) {
  }

  @override
  void visitErrorNode(ErrorNode node) {
  }

  @override
  void visitTerminal(TerminalNode node) {
  }
}

void main(List<String> args) async {
  JSONLexer.checkVersion();
  JSONParser.checkVersion();
  final input = await InputStream.fromPath(args[0]);
  final lexer = JSONLexer(input);
  final tokens = CommonTokenStream(lexer);
  final parser = JSONParser(tokens);
  parser.addErrorListener(DiagnosticErrorListener());
  parser.buildParseTree = true;
  final tree = parser.json();
  ParseTreeWalker.DEFAULT.walk(TreeShapeListener(), tree);
}

Create a example.json file:

{"a":1}

Parse the input file:

dart bin/main.dart example.json

The expected output is:

{"a":1}
{"a":1}
{"a":1}
"a":1
1

Debug

We have some logs in place that can ease the debugging process, in order to turn these logs on you can enable the following environment declarations:

  • ANTLR_LEXER_DEBUG
  • ANTLR_LEXER_DFA_DEBUG
  • ANTLR_PARSER_DEBUG
  • ANTLR_PARSER_LIST_ATN_DECISIONS_DEBUG
  • ANTLR_PARSER_DFA_DEBUG
  • ANTLR_PARSER_RETRY_DEBUG

If you're using flutter, you can define these variables by adding an --dart-define arguments, eg. flutter run --dart-define LEXER_DEBUG=false