Cara yang sulit
Anda menginginkan parser keturunan rekursif .
Untuk mendapatkan prioritas, Anda perlu berpikir secara rekursif, misalnya, menggunakan string sampel Anda,
1+11*5
untuk melakukan ini secara manual, Anda harus membaca 1
, lalu melihat plus dan memulai "sesi" penguraian rekursif baru yang dimulai dengan 11
... dan pastikan untuk mengurai 11 * 5
ke dalam faktornya sendiri, menghasilkan pohon parse dengan 1 + (11 * 5)
.
Ini semua terasa sangat menyakitkan bahkan untuk mencoba menjelaskan, terutama dengan ketidakberdayaan yang ditambahkan dari C. Lihat, setelah mengurai 11, jika * sebenarnya adalah +, Anda harus mengabaikan upaya membuat istilah dan sebagai gantinya mengurai 11
dirinya sebagai faktor. Kepalaku sudah meledak. Itu mungkin dengan strategi rekursif yang layak, tetapi ada cara yang lebih baik ...
Cara yang mudah (benar)
Jika Anda menggunakan alat GPL seperti Bison, Anda mungkin tidak perlu khawatir tentang masalah perizinan karena kode C yang dihasilkan oleh bison tidak tercakup oleh GPL (IANAL tetapi saya cukup yakin alat GPL tidak memaksa GPL aktif kode / binari yang dihasilkan; misalnya Apple mengkompilasi kode seperti say, Aperture dengan GCC dan mereka menjualnya tanpa harus GPL kode tersebut).
Unduh Bison (atau yang setara, ANTLR, dll.).
Biasanya ada beberapa kode contoh yang bisa Anda gunakan untuk menjalankan bison dan mendapatkan kode C yang Anda inginkan yang mendemonstrasikan empat fungsi kalkulator ini:
http://www.gnu.org/software/bison/manual/html_node/Infix-Calc.html
Lihatlah kode yang dihasilkan, dan lihat bahwa ini tidak semudah kedengarannya. Juga, keuntungan menggunakan alat seperti Bison adalah 1) Anda belajar sesuatu (terutama jika Anda membaca buku Naga dan mempelajari tata bahasa), 2) Anda menghindari NIH mencoba menemukan kembali roda. Dengan alat pembuat parser yang sebenarnya, Anda sebenarnya memiliki harapan untuk meningkatkannya nanti, menunjukkan kepada orang lain bahwa Anda tahu bahwa parser adalah domain alat parsing.
Memperbarui:
Orang-orang di sini telah memberikan banyak nasihat yang masuk akal. Satu-satunya peringatan saya agar tidak melewatkan alat parsing atau hanya menggunakan algoritma Shunting Yard atau parser rekursif yang digulung tangan adalah bahwa bahasa mainan kecil 1 suatu hari nanti dapat berubah menjadi bahasa aktual besar dengan fungsi (sin, cos, log) dan variabel, kondisi dan untuk loop.
Flex / Bison mungkin berlebihan untuk penerjemah kecil dan sederhana, tetapi parser + evaluator dapat menyebabkan masalah saat perubahan perlu dilakukan atau fitur perlu ditambahkan. Situasi Anda akan berbeda dan Anda perlu menggunakan penilaian Anda; hanya saja, jangan menghukum orang lain atas dosa-dosa Anda [2] dan membangun alat yang kurang dari cukup.
Alat favorit saya untuk parsing
Alat terbaik di dunia untuk pekerjaan itu adalah pustaka Parsec (untuk pengurai yang layak rekursif) yang hadir dengan bahasa pemrograman Haskell. Ini terlihat sangat mirip dengan BNF , atau seperti alat khusus atau bahasa khusus domain untuk penguraian (kode contoh [3]), tetapi sebenarnya ini hanya pustaka biasa di Haskell, yang berarti bahwa ia mengkompilasi dalam langkah pembuatan yang sama dengan yang lain dari kode Haskell Anda, dan Anda dapat menulis kode Haskell arbitrer dan memanggilnya dalam parser Anda, dan Anda dapat mencampur dan mencocokkan semua pustaka lain dalam kode yang sama . (Ngomong-ngomong, menyematkan bahasa penguraian seperti ini dalam bahasa selain Haskell menghasilkan banyak sintaksis. Saya melakukan ini di C # dan berfungsi dengan cukup baik tetapi tidak begitu cantik dan ringkas.)
Catatan:
1 Richard Stallman berkata, dalam Why you should not use Tcl
Pelajaran utama dari Emacs adalah bahwa bahasa untuk ekstensi tidak boleh hanya menjadi "bahasa ekstensi". Ini harus menjadi bahasa pemrograman yang nyata, dirancang untuk menulis dan memelihara program yang substansial. Karena orang pasti ingin melakukan itu!
[2] Ya, saya selamanya takut menggunakan "bahasa" itu.
Juga perhatikan bahwa ketika saya mengirimkan entri ini, pratinjau benar, tetapi pengurai SO kurang dari cukup memakan tag jangkar dekat saya pada paragraf pertama , membuktikan bahwa parser bukanlah sesuatu yang bisa dianggap enteng karena jika Anda menggunakan regex dan satu kali meretas Anda mungkin akan mendapatkan kesalahan kecil dan halus .
[3] Cuplikan pengurai Haskell menggunakan Parsec: kalkulator empat fungsi yang dilengkapi dengan eksponen, tanda kurung, spasi untuk perkalian, dan konstanta (seperti pi dan e).
aexpr = expr `chainl1` toOp
expr = optChainl1 term addop (toScalar 0)
term = factor `chainl1` mulop
factor = sexpr `chainr1` powop
sexpr = parens aexpr
<|> scalar
<|> ident
powop = sym "^" >>= return . (B Pow)
<|> sym "^-" >>= return . (\x y -> B Pow x (B Sub (toScalar 0) y))
toOp = sym "->" >>= return . (B To)
mulop = sym "*" >>= return . (B Mul)
<|> sym "/" >>= return . (B Div)
<|> sym "%" >>= return . (B Mod)
<|> return . (B Mul)
addop = sym "+" >>= return . (B Add)
<|> sym "-" >>= return . (B Sub)
scalar = number >>= return . toScalar
ident = literal >>= return . Lit
parens p = do
lparen
result <- p
rparen
return result