tokuhirom's Blog

Java でちょっとしたパーサーを実装するときは ANTLR4 が便利

Java でちょっとしたDSLパーサーを実装するときはANTLR4 が最近もデファクトスタンダードなのかなあ。と思っています。

ANTLR4 はパーサージェネレーターです。BNF っぽい記法で書いたらいい感じに生成してくれます。手書きパーサーとかに比べると管理しやすい気がします。

gradle はコアプラグインに antlr プラグインがあるので簡単に利用できます。 https://docs.gradle.org/current/userguide/antlr_plugin.html

group 'com.example'

apply plugin: 'java'
apply plugin: 'antlr'

sourceCompatibility = 1.8

repositories {
    mavenCentral()
}

dependencies {
    antlr "org.antlr:antlr4:4.7"
    testCompile 'junit:junit:4.12'
}

とかして、src/main/antlr に文法定義ファイルを置くだけ。

四則演算なら以下のような感じ。

grammar Expr;

@header {
    package com.example;
}

prog: expr;
expr: term (('+'|'-') term)*;
term: factor (('*'|'/') factor)*;
factor: INT
    | '(' expr ')'
    ;
INT     : [0-9]+ ;

利用コードは以下のような感じ。

package com.example;

import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTreeWalker;

import java.io.IOException;
import java.util.List;

public class Test {
    public static void main(String[] args) throws IOException {
//        CharStreams.fromFile() とかもあるよ
        ExprLexer lexer = new ExprLexer(CharStreams.fromString("3+2*4"));
        CommonTokenStream stream = new CommonTokenStream(lexer);
        ExprParser exprParser = new ExprParser(stream);
        ExprParser.ExprContext expr = exprParser.expr();
        System.out.println("toInfoString : " + expr.toInfoString(exprParser));
        System.out.println("toString : " + expr.toString());
        System.out.println("toStringTree : " + expr.toStringTree());
        System.out.println("\n");

        // AST はこういう形で辿れる
        List<ExprParser.TermContext> term = expr.term();
        System.out.println(term.get(0).factor(0).INT());

       // ビジターパターンで処理することも可能
        ParseTreeWalker walker = new ParseTreeWalker();
        walker.walk(new ExprBaseListener(), expr);
    }
}

全体のコードはこちら。 https://github.com/tokuhirom/antlr-demo