Ini adalah topik besar tetapi daripada mengabaikan Anda dengan sombong "baca buku, Nak", sebagai gantinya saya akan dengan senang hati memberi Anda petunjuk untuk membantu Anda membungkus kepala Anda di sekitarnya.
Kebanyakan kompiler dan / atau penerjemah bekerja seperti ini:
Tokenize : Memindai teks kode dan memecahnya menjadi daftar token.
Langkah ini bisa rumit karena Anda tidak bisa hanya membagi string pada spasi, Anda harus mengenali bahwa itu if (bar) foo += "a string";
adalah daftar 8 token: WORD, OPEN_PAREN, WORD, CLOSE_PAREN, WORD, ASIGNMENT_ADD, STRING_LITERAL, TERMINATOR. Seperti yang Anda lihat, hanya dengan memecah kode sumber pada spasi tidak akan berhasil, Anda harus membaca setiap karakter sebagai urutan, jadi jika Anda menemukan karakter alfanumerik Anda terus membaca karakter sampai Anda menekan karakter non-alfanumerik dan string yang Anda baca saja adalah KATA yang akan diklasifikasikan lebih lanjut nanti. Anda dapat memutuskan sendiri seberapa rinci tokenizer Anda: apakah menelan "a string"
sebagai satu token yang disebut STRING_LITERAL untuk diuraikan lebih lanjut nanti, atau apakah ia melihat"a string"
sebagai OPEN_QUOTE, UNPARSED_TEXT, CLOSE_QUOTE, atau apa pun, ini hanyalah salah satu dari banyak pilihan yang harus Anda putuskan sendiri saat Anda mengodekannya.
Lex : Jadi sekarang Anda memiliki daftar token. Anda mungkin menandai beberapa token dengan klasifikasi ambigu seperti WORD karena selama pass pertama Anda tidak menghabiskan terlalu banyak usaha untuk mencari tahu konteks dari setiap string karakter. Jadi sekarang bacalah daftar token sumber Anda lagi dan reklasifikasi masing-masing token ambigu dengan jenis token yang lebih spesifik berdasarkan kata kunci dalam bahasa Anda. Jadi Anda memiliki KATA seperti "jika", dan "jika" ada dalam daftar kata kunci khusus Anda yang disebut simbol JIKA sehingga Anda mengubah jenis simbol token itu dari WORD ke IF, dan setiap WORD yang tidak ada dalam daftar kata kunci khusus Anda , seperti WORD foo, adalah IDENTIFIER.
Parse : Jadi sekarang Anda membalikkan if (bar) foo += "a string";
daftar token lexed yang terlihat seperti ini: JIKA OPEN_PAREN IDENTIFER CLOSE_PAREN IDENTIFIER ASIGN_ADD STRING_LITERAL TERMINATOR. Langkah ini mengenali urutan token sebagai pernyataan. Ini penguraian. Anda melakukan ini menggunakan tata bahasa seperti:
PERNYATAAN: = ASIGN_EXPRESSION | IF_STATEMENT
IF_STATEMENT: = IF, PAREN_EXPRESSION, STATEMENT
ASIGN_EXPRESSION: = IDENTIFIER, ASIGN_OP, VALUE
PAREN_EXPRESSSION: = OPEN_PAREN, VALUE, CLOSE_PAREN
VALUE: = IDENTIFIER | STRING_LITERAL | PAREN_EXPRESSION
ASIGN_OP: = EQUAL | ASIGN_ADD | ASIGN_SUBTRACT | ASIGN_MULT
Produksi yang menggunakan "|" antara istilah berarti "cocok dengan semua ini", jika ada koma di antara istilah itu berarti "cocok dengan urutan istilah ini"
Bagaimana Anda menggunakan ini? Dimulai dengan token pertama, cobalah untuk mencocokkan urutan token Anda dengan produksi ini. Jadi pertama-tama Anda mencoba mencocokkan daftar token Anda dengan PERNYATAAN, jadi Anda membaca aturan PERNYATAAN dan itu mengatakan "PERNYATAAN adalah ASIGN_EXPRESSION atau IF_STATEMENT" sehingga Anda mencoba untuk mencocokkan ASIGN_EXPRESSION terlebih dahulu, sehingga Anda mencari aturan tata bahasa untuk ASIGN_EXPRESSION dan dikatakan "ASIGN_EXPRESSION adalah IDENTIFIER diikuti oleh ASIGN_OP diikuti oleh VALUE, jadi Anda mencari aturan tata bahasa untuk IDENTIFIER dan Anda melihat tidak ada tata bahasa untuk IDENTIFIER sehingga berarti IDENTIFIER" terminal "yang berarti itu tidak memerlukan lebih lanjut parsing untuk mencocokkannya sehingga Anda dapat mencoba untuk mencocokkannya langsung dengan token Anda. Tapi token sumber pertama Anda adalah IF, dan IF tidak sama dengan IDENTIFIER jadi pertandingan gagal. Apa sekarang? Anda kembali ke aturan PERNYATAAN dan mencoba mencocokkan istilah berikutnya: IF_STATEMENT. Anda mencari IF_STATEMENT, itu dimulai dengan IF, lookup IF, IF adalah terminal, bandingkan terminal dengan token pertama Anda, IF token cocok, terus mengagumkan, istilah berikutnya adalah PAREN_EXPRESSION, cari PAREN_EXPRESSION, itu bukan terminal, apa itu istilah pertama, PAREN_EXPRESSION dimulai dengan OPEN_PAREN, cari OPEN_PAREN, ini terminal, cocokkan OPEN_PAREN dengan token Anda berikutnya, cocok, .... dan seterusnya.
Cara termudah untuk mendekati langkah ini adalah Anda memiliki fungsi yang disebut parse () yang Anda berikan token kode sumber yang Anda coba cocokkan dan istilah tata bahasa yang Anda coba cocokkan dengannya. Jika istilah tata bahasa bukan terminal, maka Anda berulang: Anda memanggil parse () lagi lewat token sumber yang sama dan istilah pertama aturan tata bahasa ini. Inilah sebabnya mengapa ini disebut "parser keturunan rekursif" Fungsi parse () mengembalikan (atau mengubah) posisi Anda saat ini dalam membaca token sumber, itu pada dasarnya meneruskan token terakhir dalam urutan yang cocok, dan Anda melanjutkan panggilan berikutnya untuk parse () dari sana.
Setiap parse () cocok dengan produksi seperti ASIGN_EXPRESSION Anda membuat struktur yang mewakili potongan kode itu. Struktur ini berisi referensi ke token sumber asli. Anda mulai membangun daftar struktur ini. Kami akan menyebut seluruh struktur ini Pohon Sintaksis Abstrak (AST)
Kompilasi dan / atau Jalankan : Untuk produksi tertentu dalam tata bahasa Anda, Anda telah membuat fungsi-fungsi handler yang jika diberi struktur AST, ia akan mengkompilasi atau mengeksekusi potongan AST itu.
Jadi mari kita lihat bagian AST Anda yang bertipe ASIGN_ADD. Jadi sebagai penerjemah, Anda memiliki fungsi ASIGN_ADD_execute (). Fungsi ini dilewatkan sebagai bagian dari AST yang sesuai dengan parse tree foo += "a string"
, jadi fungsi ini melihat struktur itu dan tahu bahwa istilah pertama dalam struktur harus IDENTIFIER, dan istilah kedua adalah VALUE, jadi ASIGN_ADD_execute () meneruskan istilah VALUE ke fungsi VALUE_eval () yang mengembalikan objek yang mewakili nilai yang dievaluasi dalam memori, lalu ASIGN_ADD_execute () melakukan pencarian "foo" di tabel variabel Anda, dan menyimpan referensi ke apa pun yang dikembalikan oleh eval_value () fungsi.
Itu penerjemah. Kompiler sebaliknya akan memiliki fungsi handler menerjemahkan AST ke dalam kode byte atau kode mesin alih-alih menjalankannya.
Langkah 1 hingga 3, dan beberapa 4, dapat dibuat lebih mudah menggunakan alat-alat seperti Flex dan Bison. (alias. Lex dan Yacc) tetapi menulis sendiri juru bahasa dari awal mungkin merupakan latihan yang paling memberdayakan yang bisa dicapai oleh setiap programmer. Semua tantangan pemrograman lainnya tampak sepele setelah pertemuan ini.
Saran saya adalah mulai dari yang kecil: bahasa yang kecil, dengan tata bahasa yang kecil, dan coba parsing dan jalankan beberapa pernyataan sederhana, kemudian tumbuh dari sana.
Baca ini, dan semoga berhasil!
http://www.iro.umontreal.ca/~felipe/IFT2030-Automne2002/Complements/tinyc.c
http://en.wikipedia.org/wiki/Recursive_descent_parser
lex
,yacc
danbison
.