PEMBUATAN KOMPILER DENGAN METODE PARSING
BERUPA RECURSIVE DESCENT PARSER
Drs. Tedy Setiadi, M.T. dan Basit Adhi Prabowo
Program Studi Teknik Infomatika Universitas Ahmad Dahlan
Jl. Prof. Soepomo, S.H., Janturan, Yogyakarta
Abstrak
Komputer merupakan sebuah mesin digital yang diciptakan oleh manusia untuk melakukan proses-proses intelektual yang dapat dilakukan secara mekanis oleh manusia. Manusia dapat melakukan interaksi secara efektif dengan menggunakan media bahasa. Manusia melakukan komunikasi dengan bahasa manusia, sedangkan komputer hanya menerima dan memahami bahasa mesin. Bahasa pemrograman menjembatani komunikasi antara manusia dengan komputer. Alat yang digunakan sebagai sarana untuk melakukan komunikasi dengan bahasa pemrograman adalah kompiler. Kompiler akan memeriksa sintaks dari kode sumber dengan parser, kemudian kode sumber diterjemahkan ke dalam bahasa mesin. Recursive descent parser adalah parser dengan metode top-down yang memiliki ciri secara rekursif menurunkan semua variabel dari awal sampai bertemu terminal dan tidak pernah mengambil token secara mundur (no back track). Recursive descent parser yang digunakan adalah LL(1), yaitu parser yang tidak mengandung rekursi kiri. Kata kunci: kompiler, recursive descent parser, bahasa pemrograman
Pendahuluan
Manusia dapat melakukan interaksi secara efektif dengan menggunakan media bahasa. Bahasa memungkinkan penyampaian gagasan dan pemikiran. Bahasa pemrograman adalah bahasa yang dibuat agar manusia dapat melakukan komunikasi dengan mesin. Bahasa pemrograman menjembatani antara pemikiran manusia yang sering tidak terstruktur dengan mesin yang memerlukan kepastian untuk melakukan eksekusi. Bahasa pemrograman harus memiliki konstruksi yang dapat merefleksikan gagasan dan independen dari komputer yang dipergunakan. Bahasa pemrograman seperti ini sering disebut dengan bahasa tingkat tinggi. Bahasa tingkat tinggi lebih mudah untuk dipelajari karena bahasa yang digunakan lebih dekat dengan bahasa manusia dan tidak membutuhkan latar belakang mengenai perangkat keras. Gagasan untuk perancangan bahasa pemrograman bisa berasal dari bahasa alami (natural language), matematika, dan bahasa pemrograman yang sudah ada. Konstruksi yang diturunkan dari bahasa alami berguna untuk kejelasan dan kemudahan pembacaan. Sebuah instruksi akan mengerjakan mirip dengan arti sesungguhnya dari instruksi tersebut dalam bahasa alami. Matematika telah banyak digunakan untuk aturan-aturan yang terdapat pada bahasa pemrograman, khususnya untuk ekspresi aritmatika dan notasi matematika. Notasi digunakan untuk mempersingkat kode program dan mempermudah pemrogram dalam pembuatan dan pembacaan ulang program. Bahasa pemrograman yang sudah ada dapat digunakan sebagai patokan dalam perancangan bahasa pemrograman. Namun, diperlukan ketelitian pada waktu menggunakannya karena bahasa yang sudah ada tersebut bisa saja memiliki suatu kesalahan yang cukup fatal. Bahasa yang digunakan sebagai referensi dapat juga lebih dari satu bahasa pemrograman karena setiap bahasa pemrograman memiliki kelebihan dan kekurangannya masing-masing.
Pengembangan sebuah kompilator merupakan pekerjaan yang tidak sederhana. Sebuah bahasa yang terlalu kompleks akan menyulitkan pembuatan kompilator untuk bahasa tersebut. Kebanyakan pemrogram menginginkan bahasa yang sederhana, tetapi kesederhanaan dapat pula berarti kekurangan di sisi lainnya. Kesederhanaan tidak dapat dicapai dengan keterbatasan struktur dan tidak dapat dicapai dengan generalitas
yang tidak terbatas. Kesederhanaan dapat dicapai dengan melakukan pembatasan tujuan, perhatian pada keterbacaan, pendefinisian yang baik, dan konsep yang sederhana.
Sintaks adalah susunan kalimat dan grammar[3]. Sintaks dianalisis oleh suatu mesin yang disebut dengan
parser. Parser bertugas menganalisis token yang dihasilkan pada proses scan sesuai dengan grammar. Recursive Descent Parser (RDP) adalah salah satu cara untuk mengaplikasikan bahasa bebas konteks untuk
melakukan analisis sintaksis suatu source code. Ciri dari RDP yang menonjol adalah secara rekursif menurunkan semua variabel dari awal sampai bertemu terminal dan tidak pernah mengambil token secara mundur (no back track). Ciri lain dari RDP adalah sangat bergantung pada algoritma scan dalam mengambil token.
RDP juga dapat dikombinasikan dengan Predictive Parser. Predictive Parser (PP) adalah parser yang menggunakan prediksi untuk mengoptimalkan kerja dari parser. Parser model ini juga akan mengecilkan terjadinya rekursi kiri atau salah interpretasi. Prinsip dari predictive parser adalah mengelompokkan produksi sesuai dengan pola yang ada sehingga aturan produksi tertentu sudah diprediksikan penurunannya.
Tinjauan Pustaka
I Made Wiryana[4] menyebutkan bahwa teknik kompilasi telah lama diberikan di lingkungan pendidikan tinggi bidang komputer di Indonesia. Pembahasan dalam mata kuliah ini biasanya berkisar pada teori otomata, teori kompilasi, teori grammar. Praktek teknik kompilasi pun telah diberikan di lingkungan laboratorium, walau biasanya masih terbatas pada demonstrasi hal teori, ataupun sekedar pengen alan kompiler yang ada atau banyak digunakan. Beberapa universitas telah mulai memperkenalkan penggunaan perangkat pembangun kompiler.
Firrar Udirartatmo[3] mengemukakan bahwa dalam lingkungan pemrograman komputer, bahasa pemrograman bertindak sebagai sarana komunikasi antara manusia dan permasalahannya dengan komputer yang dipakai untuk membantu memperoleh penyelesaian. Suatu solusi untuk suatu masalah akan menjadi lebih mudah bila bahasa pemrograman yang digunakan lebih dekat dengan permasalahan tersebut.
Teori
Sebuah kompiler umumnya memiliki dua tugas pokok, yaitu fungsi analisis dan fungsi sintesis. Tugas dari fungsi analisis adalah melakukan dekomposisi program sumber menjadi bagian-bagian dasarnya, sedangkan fungsi sintesis memiliki tugas melakukan pembangkitan dan optimasi program objek. Model sebuah kompiler dapat dilihat pada gambar 1.
Gambar 1. Model kompiler
Program sumber merupakan deretan simbol-simbol yang memuat konstruksi bahasa yang mendasar seperti nama variabel, label, konstanta, keyword, dan operator. Scanner membaca setiap karakter kode sumber dan
ANALYSIS SYNTHESIS Intermediate code Lexical Analyzer (Scanner) Syntactic Analyzer (Parser) Semantic Analyzer Intermediate code Generator Code Generator Code Optimizer Source Code Object Code Symbol Table
memisahkan teks ke dalam token-token. Token digunakan oleh parser untuk menganalisis sintaks sesuai dengan grammar yang telah dirancang. Selanjutnya, semantic analyzer menentukan maksud dari program sumber dan menentukan aksi yang harus dilakukan. Analisis semantik bisa menghasilkan intermediate
form dari program sumber yang berguna untuk memudahkan dalam pembuatan code generator dan code optimizer. Keluaran dari semantic analyzer diberikan ke code generator untuk ditranslasikan ke dalam
bahasa assembly atau ke dalam bahasa mesin. Keluaran dari code generator diberikan ke code optimizer. Proses ini bertujuan untuk menghasilkan program objek yang lebih efisien.
Di dalam perancangan sebuah parser dikenal dua buah notasi, yaitu BNF dan diagram sintaks. BNF adalah notasi yang dapat digunakan untuk memberikan definisi formal dari bahasa pemrograman. Diagram sintaks memberikan gambaran yang jelas mengenai BNF yang telah dirancang dalam bentuk grafis. Diagram sintaks menggunakan simbol pada gambar 2.
Gambar 2. Simbol yang digunakan dalam diagram sintaks
Di dalam proses kompilasi, analisis sintaksis menggunakan parser merupakan bagian front end dari model kompilasi. Karakteristik dari parser top-down hanya membutuhkan beberapa baris untuk mengakomodasi bahasa yang telah dirancang dan sangat cocok dengan BNF, simbol variabel direpresentasikan dengan sebuah prosedur, misalnya:
<exp> ::= <bool_term> { T_OR <bool_term> } <bool_term> ::= <bool_not_fact> { T_AND <bool_not_fact> } dapat diubah ke dalam algoritma:
procedure exp() bool_term()
while CToken = T_OR do scan() bool_term() endwhile endprocedure procedure bool_term() bool_not_fact()
while CToken = T_AND do scan()
bool_not_fact() endwhile
endprocedure
Penurunan secara rekursif dapat terlihat dari kedua algoritma. Prosedur bool_term dipanggil oleh prosedur exp. Di dalam prosedur bool_term juga terjadi pemanggilan prosedur, yaitu prosedur bool_not_fact. Pemanggilan prosedur tersebut terjadi berulang-ulang (rekursif) dan terjadi penurunan (descent). Rekursi (penurunan) terjadi sampai menemui simbol terminal (token). Masalah utama dalam penggunaan rekursi adalah rekursi yang tidak berhenti. Oleh karena itu, diperlukan kehati-hatian dalam pemakaian rekursi.
Predictive parser diperlukan untuk menekan atau menghilangkan kemungkinan terjadinya hal tersebut.
Pembahasan
Tahap-tahap penelitian yang dilakukan dalam penelitian ini adalah: 1. Pendefinisian Kebutuhan
Pendefinisian kebutuhan meliputi penentuan perangkat lunak, penentuan perangkat keras yang sesuai dengan perangkat lunak dan aplikasi yang hendak dibuat serta data-data yang diperlukan untuk membuat aplikasi. Aplikasi ini hanya membutuhkan 3 elemen dari elemen-elemen kompiler yang ada, yaitu parser, scanner dan code generator. Hal ini bisa dilakukan karena bahasa yang dirancang tidak memerlukan deklarasi variabel (memerlukan tabel informasi) dan optimasi kode (memerlukan kode antara), sedangkan analisis semantik sudah ada secara implisit di dalam parser.
2. Perancangan
Perancangan merupakan tahap awal dari suatu aplikasi program untuk menghasilkan sistem yang baik. Perancangan dibuat sebagai landasan implementasi untuk mempermudah pembuatan aplikasi. Perancangan meliputi perancangan parser (BNF dan diagram sintaks), perancangan scanner (FA) dan desain aplikasi.
: Simbol variabel
: Simbol terminal
a. Perancangan Parser menggunakan BNF dan Diagram Sintaks
Kode sumber pada kompiler memerlukan BNF dan diagram sintaks agar pembuat program mudah dalam membuat program. Adapun beberapa rancangan BNF dari kompiler yang dibuat adalah sebagai berikut:
<simple_exp> ::= <term> { <add_operator> <term> } <add_operator> ::= T_ADD | T_SUB
<term> ::= <signed_fact> { <mul_operator> <signed_fact> } <signed fact> ::= <add_operator> <fact> | <fact>
<mul_operator> ::= T_MUL | T_DIV
<fact> ::= T_LPARENT <exp> T_RPARENT | T_NUMERIC di mana: T_ADD = ’+’ T_SUB = ’-‘ T_MUL = ’*’ T_DIV = ’/‘ T_LPARENT = ’(‘ T_RPARENT = ’)’ T_ANGKA = ’0’..’9’
sedangkan beberapa rancangan berupa diagram sintaks dari kompiler yang dibuat dapat dilihat pada gambar 3, 4 dan 5.
Gambar 3. Diagram sintaks dari <simple_exp>
Gambar 4. Diagram sintaks dari <add_operator>
Gambar 5. Diagram sintaks dari <term> b. Perancangan Scanner menggunakan FA
Dalam memeriksa sintaks dari kode sumber, parser membutuhkan bantuan scanner untuk memberikan token dari sintaks. Berdasarkan BNF dan diagram sintaks yang telah dibuat, maka dapat dirancang sebuah scanner dengan rancangan seperti pada gambar 6.
Gambar 6. Rancangan scanner berupa Finite Automata
3. Implementasi
a. Scanner
Berikut ini adalah implementasi dari FA Scanner: Option Explicit 'inisialisasi token Enum Token T_NUMERIC = 0 T_ADD T_SUB T_MUL T_DIV T_RPARENT T_LPARENT T_EOF T_UNKNOWN End Enum 'inisialisasi Const TAB_ = 9 Const LINEFEED_ = 10 Const CARRIAGE_RETURN_ = 13 Const SPACE_ = 32 Const EOF_SIGN = "#" Private keySymbol() As Variant Dim lst As ListItem
Dim CTokenType As String
Sub Reset()
'reset posisi pita ke posisi awal
Index = 0 q0 T_ADD + -( ) 0..9 T_MUL T_SUB T_DIV T_LPARENT T_RPARENT T_TAMBAH * /
LineNumber = 1 CC = "" BOF = True EOF = True ReDim errorTrap(0) End Sub Sub getCh()
'pita maju satu karakter
If CC <> EOF_SIGN Then Index = Index + 1
CC = Mid(theRtf.Text, Index, 1)
If CC <> EOF_SIGN Then EOF = False Else EOF = True Else
EOF = True End If
End Sub
Sub Initialize(ByRef Rtb As Object)
‘inisialisasi awal
Set theRtf = Rtb isAnyError = False Reset
If Len(Rtb) <> 0 Then EOF = False
End Sub Sub skipComment() ‘mengabaikan komentar If CC = "{" Then Do getCh
Loop Until CC = "}" Or EOF If Not EOF Then
getCh Scan End If End If End Sub Sub Scan() 'kode scanner
Dim indexKey As Integer Dim isAllow As Boolean Identifier = ""
If BOF Then getCh BOF = False End If
While Not EOF Select Case CC
Case Chr(TAB_), Chr(LINEFEED_), Chr(SPACE_): getCh Case Chr(CARRIAGE_RETURN_) getCh LineNumber = LineNumber + 1 Case "#" CToken = T_EOF Case "0" To "9" Do Select Case CC Case "0" To "9"
isAllow = True
Identifier = Identifier & CC getCh
Case Else: isAllow = False End Select
Loop Until Not isAllow Or EOF CToken = T_NUMERIC CTokenType = "numerik" GoTo addToken
Case "+"
applyToken T_ADD, "operator penjumlahan" getCh
GoTo addToken Case "-"
applyToken T_SUB, "operator penjumlahan" getCh
GoTo addToken Case "*"
applyToken T_MUL, "operator perkalian" getCh
GoTo addToken Case "/"
applyToken T_DIV, "operator perkalian" getCh
GoTo addToken Case "("
applyToken T_LPARENT, "delimiter" getCh
GoTo addToken Case ")"
applyToken T_RPARENT, "delimiter" getCh GoTo addToken Case "{" skipComment Exit Sub Case "}" setError 2 getCh Scan Exit Sub Case Else setError 1
applyToken T_UNKNOWN, "tidak diketahui" getCh GoTo addToken End Select Wend CToken = T_EOF Exit Sub addToken: With frmUtama.ListView3 No = No + 1
Set lst = .ListItems.Add(, , No) lst.SubItems(1) = CTokenType lst.SubItems(2) = Identifier
End With
End Sub
Sub applyToken(ByVal theToken As Token, kindOfToken As String)
‘memasukkan informasi tentang token ke dalam tabel simbol
CToken = theToken
CTokenType = kindOfToken Identifier = CC
End Sub
b. Parser dan object code
Pembuatan object code dalam bentuk bahasa assembly disisipkan ke dalam implementasi notasi BNF untuk mempermudah pembuatan program. Object code yang disisipkan di tandai dengan
pemanggilan sub “writeAssemblyCode”. Berikut ini adalah implementasi dari BNF yang telah dibuat dan pembuatan object code:
Option Explicit
Sub simple_exp()
'<simple_exp> ::= <term> { <add_operator> <term> }
term
While add_operator
writeAssemblyCode "PUSH AX" Select Case CToken
Case T_ADD Scan term
writeAssemblyCode "POP tempAX" & gantiBaris & "ADD AX, tempAX" Case T_SUB
Scan term
writeAssemblyCode "POP tempAX" & gantiBaris & "balikAX2tempAX" & gantiBaris & "SUB AX, tempAX"
End Select Wend
End Sub
Function add_operator() As Boolean
'<add_operator> ::= T_ADD | T_SUB
If CToken = T_ADD Or CToken = T_SUB Then add_operator = True Else add_operator = False
End Function Sub term()
'<term> ::= <signed_fact> { <mul_operator> <signed_fact> }
signed_fact
While mul_operator
writeAssemblyCode "PUSH AX" Select Case CToken
Case T_MUL Scan signed_fact
writeAssemblyCode "POP tempAX" & gantiBaris & "MUL tempAX" Case T_DIV
Scan signed_fact
writeAssemblyCode "POP tempAX" & gantiBaris & "balikAX2tempAX" & gantiBaris & "DIV tempAX"
End Select Wend
End Sub
Sub signed_fact()
'<signed fact> ::= <add_operator> <fact> | <fact>
If add_operator Then
If CToken = T_SUB Then Scan fact writeAssemblyCode "negatifAX" Else fact End If End Sub
Function mul_operator() As Boolean
'<mul_operator> ::= T_MUL | T_DIV
If CToken = T_MUL Or CToken = T_DIV Then mul_operator = True Else mul_operator = False
End Function Sub fact()
'<fact> ::= T_LPARENT <exp> T_RPARENT | T_NUMERIC
If CToken = T_LPARENT Then Scan
simple_exp
Match T_RPARENT, 101 Else
If Not (CToken = T_NUMERIC) Then setError 102
Else
writeAssemblyCode "MOV AX, " & Identifier Scan
End If End If
End Sub
Sub startParsing(ByRef Rtb As Object)
'prosedur awal untuk memulai proses parsing
Initialize Rtb
writeAssemblyCode "negatifAX MACRO ;mengalikan AX dengan -1" & gantiBaris(True) & "MOV BX, -1" & gantiBaris & "MUL BX" & gantiBaris & "ENDM" & gantiBaris(False)
writeAssemblyCode " ;start definisi program exe" & gantiBaris & ".MODEL SMALL" & gantiBaris & ".STACK 200h" & gantiBaris & ".DATA" & gantiBaris & ".CODE" & gantiBaris & "program:" & gantiBaris & "MOV AX, CS ;segmen data" & gantiBaris & "MOV DS, AX ;pada exe DS tak otomatis terinisialisasi" & gantiBaris & " ;end definisi program exe" & gantiBaris & "JMP programUtama" & gantiBaris & " ;start deklarasi variabel serbaguna" & gantiBaris & "tempAX DW ?" & gantiBaris & "temp DW ?" & gantiBaris & "temporary DW ?" & gantiBaris & "lastFor DW ?" & gantiBaris & "T_ENTER EQU 0Dh ; konstanta karakter <Enter>" & gantiBaris & "Buffer DB 30,?,30 DUP(?)" & gantiBaris & "true_str DB 'true'" & gantiBaris & "false_str DB 'false'" & gantiBaris & " ;end deklarasi variabel serbaguna" & gantiBaris
writeAssemblyCode "programUtama:" Scan
simple_exp
writeAssemblyCode "end_program:" & gantiBaris & "MOV AX, 4C00h ;Servis kembali ke OS" & gantiBaris & "INT 21h" & gantiBaris & "END program"
End Sub
Sub writeAssemblyCode(theCode As String)
'menulis string ke dalam richTextBox sehingga kode assembly langsung dapat dilihat
frmUtama.RichTextBox2.Text = frmUtama.RichTextBox2.Text & theCode & gantiBaris
End Sub
'memeriksa token saat ini
If CToken = theToken Then Scan Else setError errNumIfNotMatch
End Sub
Function gantiBaris(Optional isPL As Variant) As String
'memberi nomor baris pada kode assembly sekaligus ganti baris
barisAssembly = barisAssembly + 1 If Not IsMissing(isPL) Then If isPL Then isPrcLn = True procedureLine = 0 Else isPrcLn = False End If End If
gantiBaris = vbTab & " ;; " & barisAssembly If isPrcLn Then
procedureLine = procedureLine + 1
gantiBaris = gantiBaris & " : " & procedureLine End If
gantiBaris = gantiBaris & vbCrLf
End Function
c. Tampilan Program
Pada program ini terdapat 3 bagian yang berisi teks, yaitu source code (tempat untuk mengetikkan baris program), tabel informasi dan assembly code. Program ini akan menghasilkan sebuah file aplikasi (*.exe) jika baris program sukses dikompilasi seperti pada gambar 7 dan program ini akan menghasilkan pesan kesalahan jika baris program tidak sukses dikompilasi seperti pada gambar 8.
Untuk menghasilkan sebuah file aplikasi diperlukan TASM (Turbo Assembly). Adapun kode untuk memanggil TASM adalah sebagai berikut:
Private Sub Command2_Click()
Dim dTaskID As Double, path As String, file As String RichTextBox2.SaveFile App.path & "\tempCode.asm", rtfText path = Chr(34) & App.path & "\runtasm.bat" & Chr(34) dTaskID = Shell(path, vbNormalFocus)
enableExecute False
End Sub
dimana file “runtasm.bat” dibuat melalui sub “buatFile” berikut ini:
Sub buatFile()
Dim nofile As Integer nofile = FreeFile()
If Exists(App.path & "\runtasm.bat") Then Kill App.path & "\runtasm.bat" Open App.path & "\runtasm.bat" For Output As #nofile
Print #nofile, Chr(34) & App.path & "\tasm" & Chr(34) & " tempCode.asm" Print #nofile, "tlink /t tempCode"
Print #nofile, "tlink tempCode" Print #nofile, "del *.map" Print #nofile, "del *.obj" Close #nofile
nofile = FreeFile()
If Exists(App.path & "\show.bat") Then Kill App.path & "\show.bat" Open App.path & "\show.bat" For Output As #nofile
Print #nofile, Chr(34) & "C:\WINDOWS\TCG_HELP\%1" & Chr(34) Close #nofile
End Sub
Kesimpulan
1. Pembuatan kompiler merupakan masalah yang kompleks karena di dalam kompiler sendiri terdapat beberapa proses yang memiliki karakteristik dan masalah masing-masing.
2. Recursive descent parser mudah untuk diimplementasikan tetapi memerlukan kehati-hatian dalam
perancangannya
Saran
1. Grammar dari bahasa pemrograman dapat diperluas dan ditambah dengan struktur yang lebih
kompleks, seperti: penambahan prosedur dan fungsi dengan parameter, floating point, string, array, I/O file, kontrol pemilihan case, tipe data user defined, struct, class dan sebagainya
2. Menambahkan error recovery dan error repair.
3.
Menambahkan optimasi sebelum diubah ke dalam object code atau optimasi object code yang dihasilkan.Daftar Pustaka
[1] Crenshaw, Jack W., Ph.D., 1988, LET'S BUILD A COMPILER!,
http://www.etekchalmers.se/~e8johan/compiler/
[2] Prabowo, Basit Adhi, 2006, Pembuatan Kompiler dengan Metode Recursive Descent Parser, Skripsi S1, Universitas Ahmad Dahlan Yogyakarta
[3] Utdirartatmo, Firrar, 2005, Teknik Kompilasi, Graha Ilmu, Yogyakarta [4] Wiryana, I Made, Compiler Construction, http://www.wiryana.pandu.org/