Saya mencoba membuat tata bahasa untuk mem-parsing beberapa rumus mirip-Excel yang telah saya buat, di mana karakter khusus di awal string menandakan sumber yang berbeda. Sebagai contoh, $
dapat menandakan sebuah string, sehingga " $This is text
" akan diperlakukan sebagai input string dalam program dan &
dapat menandakan suatu fungsi, sehingga &foo()
dapat diperlakukan sebagai panggilan ke fungsi internal foo
.
Masalah yang saya hadapi adalah bagaimana membangun tata bahasa dengan benar. Misalnya, ini adalah versi yang disederhanakan sebagai MWE:
grammar = r'''start: instruction
?instruction: simple
| func
STARTSYMBOL: "!"|"#"|"$"|"&"|"~"
SINGLESTR: (LETTER+|DIGIT+|"_"|" ")*
simple: STARTSYMBOL [SINGLESTR] (WORDSEP SINGLESTR)*
ARGSEP: ",," // argument separator
WORDSEP: "," // word separator
CONDSEP: ";;" // condition separator
STAR: "*"
func: STARTSYMBOL SINGLESTR "(" [simple|func] (ARGSEP simple|func)* ")"
%import common.LETTER
%import common.WORD
%import common.DIGIT
%ignore ARGSEP
%ignore WORDSEP
'''
parser = lark.Lark(grammar, parser='earley')
Jadi, dengan tata bahasa ini, hal-hal seperti: $This is a string
, &foo()
, &foo(#arg1)
, &foo($arg1,,#arg2)
dan &foo(!w1,w2,w3,,!w4,w5,w6)
semua diurai seperti yang diharapkan. Tetapi jika saya ingin menambahkan lebih banyak fleksibilitas ke simple
terminal saya , maka saya perlu mulai mengutak-atik SINGLESTR
definisi token yang tidak nyaman.
Apa yang sudah saya coba
Bagian yang tidak bisa saya lewati adalah jika saya ingin memiliki string termasuk tanda kurung (yang merupakan literal dari func
), maka saya tidak dapat menangani mereka dalam situasi saya saat ini.
- Jika saya menambahkan tanda kurung di
SINGLESTR
, maka saya mendapatkanExpected STARTSYMBOL
, karena semakin dicampur denganfunc
definisi dan berpikir bahwa argumen fungsi harus dilewati, yang masuk akal. - Jika saya mendefinisikan kembali tata bahasa untuk cadangan simbol ampersand hanya untuk fungsi dan menambahkan tanda kurung
SINGLESTR
, maka saya dapat mengurai string dengan tanda kurung, tetapi setiap fungsi yang saya coba parsing memberiExpected LPAR
.
Maksud saya adalah bahwa apa pun yang dimulai dengan a $
akan diurai sebagai SINGLESTR
token dan kemudian saya bisa menguraikan hal-hal seperti &foo($first arg (has) parentheses,,$second arg)
.
Solusi saya, untuk saat ini, adalah saya menggunakan kata 'escape' seperti LEFTPAR dan RIGHTPAR di string saya dan saya telah menulis fungsi pembantu untuk mengubahnya menjadi tanda kurung ketika saya memproses pohon. Jadi, $This is a LEFTPARtestRIGHTPAR
hasilkan pohon yang benar dan ketika saya memprosesnya, maka ini diterjemahkan This is a (test)
.
Untuk merumuskan pertanyaan umum: Dapatkah saya mendefinisikan tata bahasa saya sedemikian rupa sehingga beberapa karakter yang khusus untuk tata bahasa diperlakukan sebagai karakter normal dalam beberapa situasi dan sebagai istimewa dalam kasus lain?
EDIT 1
Berdasarkan komentar dari jbndlr
saya merevisi tata bahasa saya untuk membuat mode individual berdasarkan simbol awal:
grammar = r'''start: instruction
?instruction: simple
| func
SINGLESTR: (LETTER+|DIGIT+|"_"|" ") (LETTER+|DIGIT+|"_"|" "|"("|")")*
FUNCNAME: (LETTER+) (LETTER+|DIGIT+|"_")* // no parentheses allowed in the func name
DB: "!" SINGLESTR (WORDSEP SINGLESTR)*
TEXT: "$" SINGLESTR
MD: "#" SINGLESTR
simple: TEXT|DB|MD
ARGSEP: ",," // argument separator
WORDSEP: "," // word separator
CONDSEP: ";;" // condition separator
STAR: "*"
func: "&" FUNCNAME "(" [simple|func] (ARGSEP simple|func)* ")"
%import common.LETTER
%import common.WORD
%import common.DIGIT
%ignore ARGSEP
%ignore WORDSEP
'''
Ini jatuh (agak) di bawah test case kedua saya. Saya bisa menguraikan semua simple
jenis string (TEXT, MD atau token DB yang dapat berisi tanda kurung) dan fungsi yang kosong; misalnya, &foo()
atau &foo(&bar())
parse dengan benar. Saat saya menempatkan argumen dalam suatu fungsi (tidak peduli jenis apa), saya mendapatkan UnexpectedEOF Error: Expected ampersand, RPAR or ARGSEP
. Sebagai bukti konsep, jika saya menghapus tanda kurung dari definisi SINGLESTR di tata bahasa baru di atas, maka semuanya berfungsi sebagaimana mestinya, tapi saya kembali ke titik awal.
sumber
STARTSYMBOL
) dan Anda menambahkan pemisah dan tanda kurung di mana harus jelas; Saya tidak melihat ambiguitas di sini. Anda masih harus membagiSTARTSYMBOL
daftar menjadi beberapa item agar dapat dibedakan.Jawaban:
Keluaran:
Saya harap itu yang Anda cari.
Itu gila beberapa hari. Saya mencoba burung dan gagal. Saya juga mencoba
persimonious
danpyparsing
. Semua parser yang berbeda ini semua memiliki masalah yang sama dengan token 'argumen' yang menggunakan tanda kurung yang tepat yang merupakan bagian dari fungsi, akhirnya gagal karena tanda kurung fungsi tidak ditutup.Caranya adalah dengan mencari tahu bagaimana Anda mendefinisikan tanda kurung yang benar yang "tidak istimewa". Lihat persamaan reguler
MIDTEXTRPAR
dalam kode di atas. Saya mendefinisikannya sebagai tanda kurung benar yang tidak diikuti oleh pemisahan argumen atau pada akhir string. Saya melakukannya dengan menggunakan ekstensi ekspresi reguler(?!...)
yang hanya cocok jika tidak diikuti...
tetapi tidak menggunakan karakter. Untungnya itu bahkan memungkinkan pencocokan ujung string di dalam ekstensi ekspresi reguler khusus ini.EDIT:
Metode yang disebutkan di atas hanya berfungsi jika Anda tidak memiliki argumen yang diakhiri dengan a), karena kemudian ekspresi reguler MIDTEXTRPAR tidak akan menangkap itu) dan akan berpikir bahwa itu adalah akhir dari fungsi walaupun ada lebih banyak argumen untuk diproses. Juga, mungkin ada ambiguitas seperti ... asdf) ,, ..., itu mungkin merupakan akhir dari deklarasi fungsi di dalam argumen, atau 'teks-seperti') di dalam argumen dan deklarasi fungsi berjalan.
Masalah ini terkait dengan fakta bahwa apa yang Anda uraikan dalam pertanyaan Anda bukan tata bahasa bebas konteks ( https://en.wikipedia.org/wiki/Context-free_grammar ) yang parser seperti lark ada. Sebaliknya itu adalah tata bahasa yang sensitif terhadap konteks ( https://en.wikipedia.org/wiki/Context-sensitive_grammar ).
Alasan untuk itu menjadi tata bahasa yang sensitif konteks adalah karena Anda perlu parser untuk 'mengingat' bahwa itu bersarang di dalam suatu fungsi, dan berapa banyak tingkat sarang yang ada, dan memiliki memori ini tersedia dalam sintaks tata bahasa dengan cara tertentu.
EDIT2:
Lihat juga parser berikut yang peka konteks, dan tampaknya menyelesaikan masalah, tetapi memiliki kompleksitas waktu yang eksponensial dalam jumlah fungsi bersarang, karena mencoba mengurai semua hambatan fungsi yang mungkin sampai menemukan yang berfungsi. Saya percaya itu harus memiliki kompleksitas eksponensial karena tidak bebas konteks.
sumber
&
misalnya.Masalahnya adalah argumen fungsi dilampirkan dalam tanda kurung di mana salah satu argumen dapat berisi tanda kurung.
Salah satu solusi yang mungkin adalah menggunakan backspace \ before (atau) ketika itu adalah bagian dari String
Solusi serupa digunakan oleh C, untuk memasukkan tanda kutip ganda (") sebagai bagian dari konstanta string di mana konstanta string terlampir dalam tanda kutip ganda.
Output adalah
sumber