Parser normal seperti yang diajarkan pada umumnya memiliki tahap lexer sebelum parser menyentuh input. Lexer (juga "pemindai" atau "tokenizer") memotong input menjadi token kecil yang dianotasi dengan suatu tipe. Ini memungkinkan parser utama untuk menggunakan token sebagai elemen terminal daripada harus memperlakukan setiap karakter sebagai terminal, yang mengarah pada peningkatan efisiensi yang nyata. Secara khusus, lexer juga dapat menghapus semua komentar dan ruang putih. Namun, fase tokenizer terpisah berarti bahwa kata kunci juga tidak dapat digunakan sebagai pengidentifikasi (kecuali bahasa tersebut mendukung stropping yang agak tidak disukai, atau mengawali semua pengidentifikasi dengan sigil seperti $foo
).
Mengapa? Mari kita asumsikan kita memiliki tokenizer sederhana yang memahami token berikut:
FOR = 'for'
LPAREN = '('
RPAREN = ')'
IN = 'in'
IDENT = /\w+/
COLON = ':'
SEMICOLON = ';'
Tokenizer akan selalu cocok dengan token terpanjang, dan lebih suka kata kunci daripada pengidentifikasi. Jadi interesting
akan digambarkan sebagai IDENT:interesting
, tetapi in
akan digambarkan sebagai IN
, tidak pernah sama IDENT:interesting
. Seperti cuplikan kode
for(var in expression)
akan diterjemahkan ke aliran token
FOR LPAREN IDENT:var IN IDENT:expression RPAREN
Sejauh ini, itu berhasil. Tetapi variabel apa pun in
akan lexed sebagai kata kunci IN
daripada variabel, yang akan memecahkan kode. Lexer tidak menyimpan status apa pun di antara token, dan tidak dapat mengetahui bahwa in
biasanya merupakan variabel kecuali saat kita berada dalam for for loop. Juga, kode berikut ini harus legal:
for(in in expression)
Yang pertama in
akan menjadi pengidentifikasi, yang kedua akan menjadi kata kunci.
Ada dua reaksi terhadap masalah ini:
Kata kunci kontekstual membingungkan, mari kita gunakan kembali kata kunci.
Java memiliki banyak kata yang dilindungi undang-undang, beberapa di antaranya tidak digunakan kecuali menyediakan pesan kesalahan yang lebih bermanfaat bagi pemrogram yang beralih ke Java dari C ++. Menambahkan kata kunci baru akan memecah kode. Menambahkan kata kunci kontekstual membingungkan pembaca kode kecuali mereka memiliki penyorotan sintaksis yang baik, dan membuat alat sulit untuk diimplementasikan karena mereka harus menggunakan teknik parsing yang lebih maju (lihat di bawah).
Saat kami ingin memperluas bahasa, satu-satunya pendekatan yang masuk akal adalah menggunakan simbol yang sebelumnya tidak sah dalam bahasa tersebut. Secara khusus, ini tidak bisa menjadi pengidentifikasi. Dengan sintaks foreach loop, Java menggunakan kembali :
kata kunci yang ada dengan makna baru. Dengan lambdas, Java menambahkan ->
kata kunci yang sebelumnya tidak dapat terjadi dalam program hukum apa pun ( -->
masih akan lexed sebagai '--' '>'
yang legal, dan ->
mungkin sebelumnya telah lexed sebagai '-', '>'
, tetapi urutan itu akan ditolak oleh parser).
Kata kunci kontekstual menyederhanakan bahasa, mari kita terapkan
Lexers sangat berguna. Tetapi alih-alih menjalankan lexer sebelum parser, kita dapat menjalankannya bersama-sama dengan parser. Parser bottom-up selalu tahu set tipe token yang akan diterima di lokasi tertentu. Parser kemudian dapat meminta lexer untuk mencocokkan salah satu dari tipe ini pada posisi saat ini. Dalam untuk-setiap loop, parser akan berada pada posisi yang ditunjukkan oleh ·
dalam tata bahasa (disederhanakan) setelah variabel ditemukan:
for_loop = for_loop_cstyle | for_each_loop
for_loop_cstyle = 'for' '(' declaration · ';' expression ';' expression ')'
for_each_loop = 'for' '(' declaration · 'in' expression ')'
Pada posisi itu, token hukum adalah SEMICOLON
atau IN
, tetapi tidak IDENT
. Kata kunci in
akan sepenuhnya ambigu.
Dalam contoh khusus ini, parser top-down tidak akan memiliki masalah karena kita dapat menulis ulang tata bahasa di atas
for_loop = 'for' '(' declaration · for_loop_rest ')'
for_loop_rest = · ';' expression ';' expression
for_loop_rest = · 'in' expression
dan semua token yang diperlukan untuk keputusan dapat dilihat tanpa mundur.
Pertimbangkan kegunaan
Java selalu cenderung pada kesederhanaan semantik dan sintaksis. Misalnya, bahasa tidak mendukung kelebihan operator karena akan membuat kode jauh lebih rumit. Jadi ketika memutuskan antara in
dan :
untuk setiap sintaks loop, kita harus mempertimbangkan mana yang kurang membingungkan dan lebih jelas bagi pengguna. Kasus ekstrim mungkin
for (in in in in())
for (in in : in())
(Catatan: Java memiliki ruang nama terpisah untuk nama jenis, variabel, dan metode. Saya pikir ini adalah kesalahan, sebagian besar. Ini tidak berarti desain bahasa kemudian harus menambahkan lebih banyak kesalahan.)
Alternatif mana yang memberikan pemisahan visual yang lebih jelas antara variabel iterasi dan koleksi iterated? Alternatif mana yang bisa dikenali lebih cepat ketika Anda melihat kode? Saya telah menemukan bahwa memisahkan simbol lebih baik daripada serangkaian kata ketika datang ke kriteria ini. Bahasa lain memiliki nilai yang berbeda. Misalnya Python menguraikan banyak operator dalam bahasa Inggris sehingga mereka dapat dibaca secara alami dan mudah dimengerti, tetapi properti yang sama dapat membuatnya sangat sulit untuk memahami sepotong Python secara sekilas.