Menggunakan Makefile untuk ANTLR 4

ANTLR (ANother Tool for Language Recognition) merupakan sebuah alat untuk menghasilkan parser untuk membaca, memproses, mengeksekusi, atau menerjemahkan teks dengan struktur atau file biner. ANTLR ditulis oleh Terrence Parr, seorang profesor data science / computer science di Universitas San Francisco.

ANTLR dapat digunakan untuk membuat parser untuk domain-specific language (DSL) untuk menyelesaikan masalah-masalah spesifik. ANTLR bisa juga digunakan untuk menterjemahkan file konfigurasi sebuah perangkat lunak menjadi bentuk lain yang bisa dimengerti oleh program lain.

Jika ingin mempelajari ANTLR lebih lanjut, anda dapat membeli buku The Definitive ANTLR 4 Reference, yang ditulis oleh pencipta ANTLR, Terrence Parr. Post ini dibuat dengan tujuan untuk mendokumentasikan proses otomasi tugas-tugas pembuatan program berbasis bahasa yang dapat dilakukan dengan Make dan Makefile.

Menerjemahkan Array Bilangan Bulat Menjadi Unicode String dengan ANTLR 4

Pada bagian ini, salah satu use-case sederhana yang dapat diselesaikan dengan ANTLR 4 dikutip dari buku The Definitive ANTLR 4 Reference. Saya akan membuat parser untuk menerjemahkan array dari bilangan bulat dengan contoh {99, 10, 23} menjadi unicode string.

Berikut adalah grammar yang digunakan:

// file: ArrayInit.g4
grammar ArrayInit;

init    :   '{' value (',' value)* '}';
value   :   init
        |   INT
        ;
INT     :   [0-9]+;
WS      :   [ \t\r\n]+ -> skip;

Langkah-langkah yang diperlukan untuk membuat parser bersdasarkan grammar di atas adalah:

antlr4 ArrayInit.g4

Perintah di atas dibuat dengan asumsi berikut:

  • antlr4 adalah alias dari java -jar /usr/local/lib/antlr-4.10.1-complete.jar
  • Jar ANTLR 4 terdapat pada direktori /usr/local/lib.

Perintah di atas akan menghasilkan beberapa file Java berikut:

  • ArrayInitBaseListener.java
  • ArrayInitLexer.java
  • ArrayInitParser.java

Kemudian, kita buat kode aplikasi dengan menggunakan bahasa Java untuk berinteraksi dengan parser yang telah dibuat.

public class ShortToUnicodeString extends ArrayInitBaseListener {
    @Override
    public void enterInit(ArrayInitParser.InitContext ctx) {
        System.out.print('"');
    }

    @Override
    public void exitInit(ArrayInitParser.InitContext ctx) {
        System.out.print('"');
    }

    @Override
    public void enterValue(ArrayInitParser.ValueContext ctx) {
        int value = Integer.valueOf(ctx.INT().getText());
        System.out.printf("\\u%04x", value);
    }
}

import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.*;

public class Translate {
    public static void main(String[] args) throws Exception {
        ANTLRInputStream input = new ANTLRInputStream(System.in);
        ArrayInitLexer lexer = new ArrayInitLexer(input);
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        ArrayInitParser parser = new ArrayInitParser(tokens);
        ParseTree tree = parser.init();

        ParseTreeWalker walker = new ParseTreeWalker();
        walker.walk(new ShortToUnicodeString(), tree);

        System.out.println();
    }
}

Kemudian, compile semua file Java dengan perintah berikut:

javac ArrayInit*.java Translate.java 

Perintah di atas dibuat dengan asumsi sebagai berikut:

  • Current working directory serta lokasi jar ANTLR 4 telah tercatat pada environment variable CLASSPATH.
  • Jika anda menggunakan IDE seperti IntelliJ Idea atau semacamnya, pastikan IDE tersebut dapat menemukan jar ANTLR 4.

Kemudian, input untuk program Translate akan dituangkan ke dalam sebuah file dengan nama t.input.

{99, 10, 23}

Sehingga kita bisa mengeksekusi program dengan perintah berikut:

java Test < t.input

Berikut adalah keluaran dari program tersebut:

"\u0063\u000a\u0017"

Yay! Program yang kita buat berhasil mengubah array dari bilangan bulat menjadi unicode string!

Jika kita tinjau kembali, berikut adalah tugas-tugas yang telah dilakukan:

  1. Membuat grammar
  2. Menghasilkan parser, lexer, dan listener dalam bahasa pemrograman Java dengan menggunakan tool ANTLR 4.
  3. Membuat kode aplikasi yang berinteraksi dengan parser yang telah dibuat.
  4. Mengkompilasi kode-kode Java menjadi file class yang dapat dijalankan dengan CLI java.
  5. Menjalankan program Translate dengan perintah java Translate < t.input.

Semua tugas-tugas yang telah disebutkan akan dilakukan secara berulang-ulang dalam proses pengembangan sebuah aplikasi dengan menggunakan ANTLR 4. Namun, hanya tugas nomor 2 dan 4 yang dapat diotomasi. Tugas nomor 1 sudah jelas membutuhkan pengetahuan tentang grammar yang akan dibaca dan diproses oleh aplikasi yang akan dbuat. Tugas nomor 3 akan sangat spesifik terhadap masalah yang akan diselesaikan serta aplikasi yang akan dihasilkan. Sedangkan tugas nomor 2 dan 4 tidak peduli dengan seperti apa grammar yang akan dibuat atau seperti apa parser yang dihasilkan dan seperti apa isi dari program yang dibuat.

Membuat Makefile untuk Otomasi Tugas-Tugas Terkait ANTLR 4

Untuk menyelamatkan kita dari mengetik perintah antlr4 dan javac secara berulang-ulang, kita bisa membuat Makefile dan menggunakan make untuk mengeksekusi resep-resep pada Makefile tersebut.

Pertama, kita akan membuat target generate untuk memanggil tool ANTLR 4 untuk menghasilkan parser, lexer, dan listener. Kemudian, kita akan membuat target yang akan mengubah file-file kode Java menjadi file .class dengan memanggil javac.

ANTLR4 = java -jar /usr/local/lib/antlr-4.10.1-complete.jar

generate: ArrayInit.g4
    $(ANTLR4) $^ -o generated

Jika kita menjalankan make atau make generate, maka tugas nomor 2 akan dilakukan, dan semua file akan disimpan pada folder generated.

Kemudian, kita buat dua variabel tambahan:

  • sources untuk menyimpan semua file kode yang akan dicompile oleh javac.
  • classes untuk menyimpan nama-nama dari class yang akan dihasilkan oleh javac.
# ...
# baris setelah variabel ANTLR4
sources := $(wildcard generated/*.java) $(wildcard *.java)
classes := $(patsubst %.java,%.class,$(sources))

# ...
# baris setelah target generate
$(classes): $(sources)
    javac $^

Kemudian, kita akan membuat target build untuk menjalankan tugas nomor 4.

# ...
# baris setelah variabel classes

build: $(sources) $(classes)

Berikut adalah Makefile utuh yang telah dibuat:

ANTLR4 = java -jar /usr/local/lib/antlr-4.10.1-complete.jar
sources := $(wildcard generated/*.java) $(wildcard *.java)
classes := $(patsubst %.java,%.class,$(sources))

build: $(sources) $(classes)

generate: ArrayInit.g4
	$(ANTLR4) $^ -o generated

$(classes): $(sources)
	javac $^

Dengan ini, jika kita mengubah grammar dan ingin menghasilkan kode Java baru, kita bisa menjalankan target generate

make generate

Perintah tersebut akan menghasilkan kode-kode Java yang dapat ditemukan pada direktori generated. Kemudian, kita dapat mengkompilasi kode-kode Java tersebut, termasuk kode aplikasi yang kita buat dengan menjalankan make atau make build.

make

Setelah itu kita dapat menjalankan aplikasi yang telah dibuat.

Keuntungan-keuntungan yang didapat dengan menggunakan Make dan Makefile adalah:

  • Kita tidak perlu mengetik baris yang panjang untuk memanggil tool ANTLR 4.
  • Kita tidak perlu mengingat semua file-file java yang telah dibuat untuk menghasilkan aplikasi.

Namun, jika anda menggunakan IDE utuk Java, anda mungkin tidak perlu melakukan ini semua. Anda dapat bergantung pada fitur-fitur lengkap yang telah disediakan oleh IDE yang anda gunakan.