Menciptakan Bahasa Dinamis Di Java: Part 1 – Membuat Front End Dengan ANTLR


Artikel ini merupakan bagian dari seri artikel tentang pembuatan bahasa dinamis BahasaKu yang berjalan di Java yang terdiri dari:

  1. Membuat Front End Dengan ANTLR
  2. Membuat Back End Dengan ASM
  3. Membuat Operasi Aritmatika
  4. Memanggil Java Libraries

Kode program untuk seri artikel ini dapat ditemukan di https://github.com/JockiHendry/BahasaKu

Hari ini ada yang berbeda dengan editor blog saya: WordPress.com kini mendukung Markdown. Bila sebelumnya saya harus mengetik dengan menggunakan syntax HTML, kini saya dapat menggunakan syntax Markdown yang lebih nyaman dipakai untuk menulis artikel pemograman. Informasi mengenai fasilitas terbaru di WordPress.com ini dapat dibaca di http://en.support.wordpress.com/markdown/. Saya akan merayakannya dengan menulis artikel mengenai pembuatan bahasa pemograman dinamis yang berjalan di Java.

Saat orang-orang berbicara tentang Java, biasanya yang ada dalam benak mereka adalah bahasa pemograman Java. Tapi Java tidak hanya sekedar bahasa pemograman, ia juga adalah nama untuk sebuah mesin virtual (Java Virtual Machine). Pada awalnya memang hanya bahasa pemograman Java yang dapat dijalankan di Java Virtual Machine (JVM). Akan tetapi seiring dengan tren polyglot programming, lahirlah bahasa-bahasa lain untuk JVM seperti Groovy, Scala, JRuby, Jython, dan sebagainya. Sebagai perbandingan, platform .NET juga memiliki banyak bahasa seperti C# dan VB. Salah satu perbedaan utamanya adalah kebanyakan bahasa pemograman untuk Java diciptakan oleh komunitas (dan bersifat open-source), bukan oleh pihak Oracle selaku pemilik Java.

Kenapa mengembangkan bahasa baru yang berjalan di JVM? Salah satu keuntungannya adalah sifat multi-platform Java. Hasil kompilasi bahasa dapat berjalan di seluruh platform yang didukung oleh Java. Selain itu, bahasa baru yang berjalan di JVM dapat mengakses library Java yang sudah ada (mulai dari fungsi matematika, kalender, database, GUI, dsb) tanpa harus membuat semuanya dari awal.

Sebagai latihan, saya akan membuat sebuah bahasa dengan tipe data dinamis yang berjalan di Java (JVM). Bahasa ini tidak akan memiliki fitur yang lengkap karena tujuan utamanya adalah sebagai bahan pembelajaran. Saya akan menyebutnya sebagai BahasaKu.

Tidak seperti pada bahasa pemograman Java dimana seluruh variabel harus dideklarasikan dengan tipe data yang statis, variabel pada bahasa pemograman Bahasaku tidak memiliki tipe data yang statis dan tidak perlu dideklarasikan terlebih dahulu. BahasaKu mendukung empat jenis tipe data, yaitu angka, String, boolean (dengan nilai berupa iya dan tidak), dan object.

Berikut ini adalah contoh hasil rancangan BahasaKu:

  1. Tipe data String harus diapit kutip tunggal seperti 'contoh'.
  2. Sebuah object dapat dibuat dengan keyword buat, misalnya buat javax.swing.JFrame().
  3. Operator yang didukung adalah <- untuk assignment, . (titik) untuk mengakses elemen dari sebuah object, dan operator aritmatika (+, -, *, /).
  4. BahasaKu memiliki operator # yang akan mengabaikan hasil kembalian pemanggilan method dan selalu mengembalikan object yang sedang aktif.
  5. Perintah tampilkan dapat dipakai untuk mencetak sebuah ekspresi ke console.
  6. Masing-masing perintah harus dalam satu baris (tidak perlu ada tanda titik koma).

Contoh kode program BahasaKu akan terlihat seperti berikut ini:

a <- 'Solid Snake'
tampilkan a
a <- 10
tampilkan a
b <- 20
c <- a + b
tampilkan c

Sampai disini, saya sudah berhasil merancang BahasaKu dengan fungsi yang sangat terbatas. Karena ini hanya sebuah latihan, saya tidak terlalu memikirkan kerancuan yang mungkin timbul pada grammar BahasaKu, seperti kombinasi syntax yang mungkin bermakna ganda. Ini dapat menimbulkan permasalahan sama seperti pada kesalahpahaman antar dua manusia karena kerancuan pada kata yang dipakai (sehingga beda persepsi).

Walaupun BahasaKu sudah selesai dirancang, ia tidak akan pernah bisa dipakai oleh programmer bila tidak ada compiler-nya. Sebuah compiler akan menerjemahkan teks yang ditulis oleh programmer menjadi instruksi yang dapat dimengerti oleh mesin. Compiler BahasaKu memiliki beberapa tugas yang secara garis besar adalah:

  1. Lexical analysis: mengubah tulisan yang diketik programmer menjadi token.
  2. Syntactic analysis: memastikan setiap token sesuai dengan aturan bahasa (syntax atau grammar).
  3. Code generation: menghasilkan bytecode yang dapat dijalankan oleh JVM.

Proses lexical analysis dan syntactic analysis sering disebut sebagai front-end. Sementara itu, proses code generation sering disebut sebagai back-end. Tidak peduli apa target akhir dari kompilasi, proses front-end selalu sama. Oleh sebab itu, saya akan mulai dengan membuat front-end terlebih dahulu.

Front-end terdiri atas lexer dan parser. Tugas lexer adalah mengubah deretan huruf dan angka yang diketik oleh programmer menjadi token (sama seperti seorang anak kecil yang merangkai huruf untuk membentuk kata). Contoh token pada bahasa yang saya buat di atas adalah tampilkan (sebuah keyword), <- (sebuah operator), 'contoh' (sebuah String), dan sebagainya. Langkah berikutnya adalah tugas parser yang akan merangkai token tersebut dalam struktur data yang disebut syntax tree atau parse tree (sama seperti seorang anak kecil yang belajar membaca kalimat yang terdiri atas beberapa kata dengan grammar seperti subjek + predikat + object).

Saya akan mulai dengan membuat sebuah proyek baru di IntelliJ IDEA dengan nama BahasaKu di lokasi C:\BahasaKu seperti yang terlihat pada gambar berikut ini:

Membuat Proyek Baru

Membuat Proyek Baru

Agar proyek lebih mudah dikelola, saya akan menggunakan [Gradle] (http://www.gradle.org) yang telah terpasang di sistem saya. Saya kemudian membuat sebuah file build.gradle di proyek dengan isi seperti berikut ini:

apply plugin: 'java'

repositories {
   mavenCentral()
}

dependencies {
   compile group: 'org.antlr', name: 'antlr4', version: '4.1'
}

Pada Gradle script di atas, terlihat bahwa proyek membutuhkan library ANTLR. Gradle akan men-download library ini secara otomatis bagi saya (oleh sebab itu saya perlu terhubung ke internet). Apa itu ANTLR? ANTLR (ANother Tool For Language Recognition) adalah sebuah lexer dan parser generator dengan input berupa file grammar. Penggunaan ANTLR akan sangat mempermudah dan mempersingkat pembuatan front-end compiler. Contoh proyek yang memakai ANTLR adalah Groovy, Jython, Hibernate (untuk query HQL), dan Twitter (untuk search query).

Untuk menjalankan proyek ini, yang perlu saya lakukan adalah memilih menu View, Tool Windows, JetGradle. Setelah itu saya perlu melakukan asosiasi dengan build.gradle di proyek saat ini, lalu memilih tab Tasks, kemudian men-double click pada build. Gradle juga mungkin akan men-download file yang dibutuhkannya dari internet belum belum tersedia di repository lokal, seperti yang terlihat pada gambar berikut ini:

Men-build Proyek

Men-build Proyek

Berikutnya, saya akan membuat sebuah file grammar ANTLR (yang isinya masih kosong) dengan nama BahasaKu.g4 di lokasi src/main/resources seperti yang terlihat pada gambar berikut ini:

Membuat file grammar

Membuat file grammar

Saya juga akan membuat sebuah contoh program yang memakai bahasa pemograman BahasaKu di lokasi src/test/resources dengan nama Contoh.baku yang isinya seperti berikut ini:

a <- 10
b <- 20
c <- a + b
tampilkan c
tampilkan c * 2 + a * 2 - b * 2
nama <- 'Solid Snake'
tampilkan nama
a <- iya
tampilkan a
b <- tidak
tampilkan b
acak <- buat java.util.Random()
tampilkan acak.nextInt()
f <- buat javax.swing.JFrame()
f.setSize(100,100)#setDefaultCloseOperation(3)#setVisible(iya)

Saat ini saya sudah dapat langsung mengetik isi file grammar di file BahasaKu.g4. Akan tetapi, agar prosesnya menjadi lebih nyaman, saya akan menggunakan sebuah IDE terpisah khusus untuk ANTLR, yaitu ANTLRWorks 2.1 yang dapat di-download di http://tunnelvisionlabs.com/products/demo/antlrworks. Tool berbasis NetBeans Platform ini menyediakan syntax highlightning, code completion dan kemudahan pengujian file grammar.

setelah men-download dan men-extract ANTLRWorks 2.1, saya dapat menjalankannya dengan men-double click file antlrworks2.exe di folder antlrworks2\bin. Kemudian, saya memilih menu File, Open, dan men-browse file BahasaKu.g4 yang ada di lokasi src/main/resources. Saya kemudian mengisi file grammar tersebut sehingga terlihat seperti berikut ini:

grammar BahasaKu;

//
// Parser Rules
//

file
    : (statement NEWLINE)* statement NEWLINE?
    ;

statement
    : tampilkan 
    | expr
    | assignment     
    ;

tampilkan
    : TAMPILKAN expr
    ;

assignment
    : IDENTIFIER ASSIGNMENT expr  
    ;

exprList
    : expr (KOMA expr)* 
    ;

arguments
    : KURUNG_BUKA exprList? KURUNG_TUTUP
    ;

expr
    :   expr op=(TITIK|SELF) IDENTIFIER arguments       # PanggilMethod    
    |   BUAT qualifiedName arguments                    # BuatObject
    |   expr KALI expr                                  # Perkalian
    |   expr BAGI expr                                  # Pembagian
    |   expr TAMBAH expr                                # Penjumlahan
    |   expr KURANG expr                                # Pengurangan    
    |   ANGKA                                           # Angka                                 
    |   BOOLEAN_TRUE                                    # BooleanTrue
    |   BOOLEAN_FALSE                                   # BooleanFalse
    |   STRING                                          # String
    |   IDENTIFIER                                      # Identifier                                                  
    ;

qualifiedName
    : IDENTIFIER ('.' IDENTIFIER)* 
    ;


//
// Lexer Rules
//

TAMPILKAN: 'tampilkan' ;

BUAT: 'buat' ;

BOOLEAN_TRUE: 'iya' ;

BOOLEAN_FALSE: 'tidak' ;

ASSIGNMENT: '<-' ;

KOMA: ',' ;

TITIK: '.' ;

SELF: '#' ;

TAMBAH: '+' ;

KURANG: '-' ;

KALI: '*' ;

BAGI: '/' ;

KURUNG_BUKA: '(' ;

KURUNG_TUTUP: ')' ;

STRING: '\'' .*? '\'' ;

IDENTIFIER: [a-zA-Z$_] [a-zA-Z0-9$_]* ;   

ANGKA:  [0-9]+ ;

NEWLINE:    '\r'? '\n' ;

WS  :   [ \t\r\n]+ -> skip ;

File grammar di atas mewakili aturan yang telah saya buat untuk BahasaKu sebelumnya. Terlihat bahwa saya menggabungkan rule untuk lexer dan parser pada file yang sama. Mereka dapat dipisah ke file yang berbeda untuk meningkatkan reusability.

Pada ANTLR, urutan definisi rule adalah hal yang penting. Sebagai contoh, di rule expr, aturan untuk perkalian dan pembagian muncul lebih dahulu sebelum penjumlahan dan pengurangan. Hal ini menyebabkan di back-end nanti, perkalian dan pembagian akan dikerjakan terlebih dahulu. Begitu juga untuk lexer, token yang lebih terspesialisasi seperti 'tampilkan' dan 'buat' harus muncul lebih dahulu sebelum token identifier (yang mewakili nama variabel dan nama method).

Rule seperti expr (yang mewakili ekspresi) dapat memiliki banyak alternatif. Oleh sebab itu, saya menggunakan label seperti # PanggilMethod, # BuatObject, dsb supaya ANTLR menghasilkan method terpisah (dengan nama yang sama sepeti pada label) sehingga mempermudah saya dalam membuat back-end untuk masing-masing jenis ekspresi tersebut.

Berikutnya, saya akan menguji apakah definisi grammar BahasaKu.g4 sesuai dengan contoh program BahasaKu yang sudah saya buat sebelumnya. Untuk itu, saya memilih menu Run, Run in TestRig…. Pada dialog yang muncul, saya men-browse ke lokasi contoh file program Contoh.baku, seperti yang terlihat pada gambar berikut ini:

Menguji Grammar

Setelah itu, saya men-klik tombol Finish. ANTLR akan menampilkan parse tree inspector seperti yang terlihat pada gambar berikut ini:

Visualisasi Parse Tree

Visualisasi Parse Tree

Bila tidak ada tulisan berwarna merah, maka itu berarti ANTLR dapat membaca source code BahasaKu dengan baik. ANTLR akan menyimpan hasil parsing-nya dalam bentuk struktur data tree atau lebih dikenal sebagai parse tree. Nantinya, di back-end, saya akan menelusuri tree ini dengan memakai pola visitor.

Mengapa ANTLR dan para pembuat compiler lebih senang menyimpan hasil parsing dalam bentuk tree? Mengapa tidak dalam array atau list, misalnya? Hal ini karena di back-end nanti, untuk menerjemahkan syntax menjadi instruksi mesin (atau bytecode Java), dibutuhkan navigasi ‘vertikal’ yang sangat sulit dicapai bila tidak menggunakan tree.

Langkah berikutnya adalah menghasilkan kode program parser dan lexer berdasarkan file grammar yang telah diuji sebelumnya. Saya memulai dengan memilih menu Run, Generate Recognizer…. Saya mengisi dialog yang muncul dengan informasi seperti berikut ini (bila lokasi output directory belum ada, saya perlu membuatnya terlebih dahulu):

Men-generate visitor berdasarkan grammar

Men-generate visitor berdasarkan grammar

Setelah itu, saya men-klik tombol Next. Kali ini saya dapat memilih untuk menghasilkan listener, visitor, atau keduanya. listener lebih mudah dipakai tetapi kurang fleksibel karena tidak dapat mengubah alur eksekusi. Bila memakai visitor, developer dapat mengubah atau mengabaikan alur eksekusi secara bebas. Secara umum, listener lebih tepat dipakai untuk membaca file source dengan alur yang pasti seperti membuat program yang menerjemahkan XML menjadi JSON (dan sebaliknya). Saya lebih sehingga menggunakan visitor sehingga saya memberikan tanda centang pada checkbox tersebut. Tidak lupa saya juga mengisi package untuk file yang dihasilkan seperti pada gambar berikut ini:

Men-generate visitor berdasarkan grammar

Men-generate visitor berdasarkan grammar

Setelah itu, saya men-klik tombol Next dan Finish.

Kembali ke project di IntelliJ IDEA, saya akan menemukan file baru dihasilkan oleh ANTLR seperti yang terlihat pada gambar berikut ini:

Kode Program Yang Di-Generate

Kode Program Yang Di-Generate

Sampai disini, front-end sudah selesai dibuat dan saya bisa melanjutkan ke tahap back-end.

Salah satu hal yang diperhatikan bila memakai ANTLR adalah setiap kali saya mengubah file grammar BahasaKu.g4, maka saya harus men-generate ulang visitor yang ada. Pada langkah di atas, saya melakukannya melalui ANTLRWorks. Sebuah solusi yang lebih otomatis adalah dengan menambahkan script pada build.gradle sehingga file akan digenerate secara otomatis setiap kali proyek dijalankan. Gradle memiliki plugin bawaan untuk ANTLR, namun sayangnya hanya mendukung hingga ANTLR 2. Oleh sebab itu, saya akan mengubah isi build.gradle menjadi seperti berikut ini:

apply plugin: 'java'

ext {
    antlrPackage = 'com.wordpress.thesolidsnake.bahasaku.grammar'
    antlrTargetDir = "src/main/java/${antlrPackage.replace('.','/')}"
    antlrGrammar = 'src/main/resources/BahasaKu.g4'
}

repositories {
    mavenCentral()
}

dependencies {
    compile group: 'org.antlr', name: 'antlr4', version: '4.1'
}

task antlr4generate(type: JavaExec) {
    description 'Menghasilkan Visitors Dari ANTLR 4'
    main 'org.antlr.v4.Tool'
    classpath project.configurations.runtime
    args '-o', antlrTargetDir
    args '-no-listener'
    args '-visitor'
    args '-package', antlrPackage
    args antlrGrammar
}

compileJava {
    dependsOn antlr4generate
}

Sekarang, setiap kali saya men-compile file Java (menjalankan program), maka ANTLR akan menghasilkan file visitor terbaru untuk saya. Selain itu, saya juga dapat mengerjakan target antlr4generate secara manual seperti yang terlihat pada gambar berikut ini:

Men-generate Visitor ANTLR Langsung Dari IDE

Men-generate Visitor ANTLR Langsung Dari IDE

Perihal Solid Snake
I'm nothing...

4 Responses to Menciptakan Bahasa Dinamis Di Java: Part 1 – Membuat Front End Dengan ANTLR

  1. Ping-balik: Menciptakan Bahasa Dinamis Di Java: Part 4 – Memanggil Java Libraries | The Solid Snake

  2. Ping-balik: Menciptakan Bahasa Dinamis Di Java: Part 3 – Membuat Operasi Aritmatika | The Solid Snake

  3. Ping-balik: Menciptakan Bahasa Dinamis Di Java: Part 2 – Membuat Back End Dengan ASM | The Solid Snake

  4. Vania Choi mengatakan:

    saya mencoba menggunakan tutorial anda, namun ketika saya menjalankan command run in testrig, error muncul seperti ini
    “Compiling grammar files…
    warning: Supported source version ‘RELEASE_6’ from annotation processor ‘org.netbeans.modules.openide.modules.PatchedPublicProcessor’ less than -source ‘1.8’
    warning: Supported source version ‘RELEASE_6’ from annotation processor ‘org.netbeans.modules.openide.util.ServiceProviderProcessor’ less than -source ‘1.8’
    warning: Supported source version ‘RELEASE_6’ from annotation processor ‘org.netbeans.modules.openide.util.NamedServiceProcessor’ less than -source ‘1.8’
    warning: Supported source version ‘RELEASE_6’ from annotation processor ‘org.netbeans.modules.openide.util.NbBundleProcessor’ less than -source ‘1.8’
    4 warnings
    Arguments: [BahasaKu, file, -encoding, windows-1252, -tokens, -tree, -gui, D:\contohBahasaku\Contoh.baku]

Apa komentar Anda?

Please log in using one of these methods to post your comment:

Logo WordPress.com

You are commenting using your WordPress.com account. Logout / Ubah )

Gambar Twitter

You are commenting using your Twitter account. Logout / Ubah )

Foto Facebook

You are commenting using your Facebook account. Logout / Ubah )

Foto Google+

You are commenting using your Google+ account. Logout / Ubah )

Connecting to %s

%d blogger menyukai ini: