Dapatkah Anda menambahkan pernyataan baru (seperti print
, raise
, with
) untuk sintaks Python?
Katakan, untuk mengizinkan ..
mystatement "Something"
Atau,
new_if True:
print "example"
Tidak terlalu banyak jika Anda harus , tetapi jika memungkinkan (singkatnya memodifikasi kode interpreter python)
Jawaban:
Anda mungkin menemukan ini berguna - internal Python: menambahkan pernyataan baru ke Python , dikutip di sini:
Artikel ini adalah upaya untuk lebih memahami cara kerja front-end Python. Hanya membaca dokumentasi dan kode sumber mungkin sedikit membosankan, jadi saya melakukan pendekatan langsung di sini: Saya akan menambahkan
until
pernyataan ke Python.Semua pengkodean untuk artikel ini dilakukan pada cabang Py3k yang mutakhir di cermin repositori Python Mercurial .
The
until
pernyataanBeberapa bahasa, seperti Ruby, memiliki
until
pernyataan, yang merupakan pelengkap untukwhile
(until num == 0
setara denganwhile num != 0
). Di Ruby, saya bisa menulis:Dan itu akan mencetak:
Jadi, saya ingin menambahkan kemampuan yang mirip dengan Python. Artinya, mampu menulis:
Penyimpangan advokasi bahasa
Artikel ini tidak mencoba menyarankan penambahan
until
pernyataan ke Python. Meskipun menurut saya pernyataan seperti itu akan membuat beberapa kode lebih jelas, dan artikel ini menunjukkan betapa mudahnya menambahkannya, saya sepenuhnya menghormati filosofi minimalisme Python. Semua yang saya coba lakukan di sini, sungguh, adalah mendapatkan beberapa wawasan tentang cara kerja Python.Mengubah tata bahasa
Python menggunakan generator parser khusus bernama
pgen
. Ini adalah parser LL (1) yang mengubah kode sumber Python menjadi pohon parse. Input ke generator parser adalah fileGrammar/Grammar
[1] . Ini adalah file teks sederhana yang menentukan tata bahasa Python.[1] : Mulai saat ini, referensi ke file dalam sumber Python diberikan secara relatif ke akar dari pohon sumber, yang merupakan direktori tempat Anda menjalankan konfigurasi dan membuat untuk membangun Python.
Dua modifikasi harus dilakukan pada file tata bahasa. Yang pertama adalah menambahkan definisi untuk
until
pernyataan tersebut. Saya menemukan di manawhile
pernyataan itu didefinisikan (while_stmt
), dan ditambahkan diuntil_stmt
bawah [2] :[2] : Ini mendemonstrasikan teknik umum yang saya gunakan saat memodifikasi kode sumber yang tidak saya kenal: bekerja berdasarkan kesamaan . Prinsip ini tidak akan menyelesaikan semua masalah Anda, tetapi pasti dapat mempermudah prosesnya. Karena segala sesuatu yang harus diselesaikan
while
juga harus diselesaikanuntil
, itu berfungsi sebagai pedoman yang cukup baik.Perhatikan bahwa saya telah memutuskan untuk mengecualikan
else
klausa dari definisi sayauntil
, hanya untuk membuatnya sedikit berbeda (dan karena terus terang saya tidak menyukaielse
klausa loop dan tidak merasa klausa itu cocok dengan Zen of Python).Perubahan kedua adalah mengubah aturan untuk
compound_stmt
disertakanuntil_stmt
, seperti yang Anda lihat pada cuplikan di atas. Ini tepat setelahnyawhile_stmt
, lagi.Saat Anda menjalankan
make
setelah memodifikasiGrammar/Grammar
, perhatikan bahwapgen
program dijalankan untuk menghasilkan ulangInclude/graminit.h
danPython/graminit.c
, kemudian beberapa file akan dikompilasi ulang.Memodifikasi kode generasi AST
Setelah Python parser membuat pohon parse, pohon ini diubah menjadi AST, karena AST jauh lebih sederhana untuk dikerjakan pada tahap selanjutnya dari proses kompilasi.
Jadi, kita akan mengunjungi
Parser/Python.asdl
yang mendefinisikan struktur AST Python dan menambahkan simpul AST untukuntil
pernyataan baru kita , lagi tepat di bawahwhile
:Jika Anda sekarang menjalankan
make
, perhatikan bahwa sebelum mengkompilasi banyak file,Parser/asdl_c.py
dijalankan untuk menghasilkan kode C dari file definisi AST. Ini (sukaGrammar/Grammar
) adalah contoh lain dari kode sumber Python yang menggunakan bahasa mini (dengan kata lain, DSL) untuk menyederhanakan pemrograman. Perhatikan juga bahwa karenaParser/asdl_c.py
ini adalah skrip Python, ini adalah sejenis bootstrap - untuk membuat Python dari awal, Python harus tersedia.Saat
Parser/asdl_c.py
membuat kode untuk mengelola node AST yang baru kami definisikan (ke dalam fileInclude/Python-ast.h
danPython/Python-ast.c
), kami masih harus menulis kode yang mengubah node parse-tree yang relevan ke dalamnya dengan tangan. Ini dilakukan di filePython/ast.c
. Di sana, fungsi bernamaast_for_stmt
mengubah simpul pohon parse untuk pernyataan menjadi simpul AST. Sekali lagi, dipandu oleh teman lamawhile
kita, kita langsung terjun ke masalah besarswitch
untuk menangani pernyataan majemuk dan menambahkan klausa untukuntil_stmt
:Sekarang kita harus menerapkan
ast_for_until_stmt
. Ini dia:Sekali lagi, ini diberi kode sambil melihat dari dekat padanannya
ast_for_while_stmt
, dengan perbedaan karenauntil
saya telah memutuskan untuk tidak mendukungelse
klausul tersebut. Seperti yang diharapkan, AST dibuat secara rekursif, menggunakan fungsi pembuatan AST lainnya sepertiast_for_expr
untuk ekspresi kondisi danast_for_suite
untuk isiuntil
pernyataan. Akhirnya, simpul baru bernamaUntil
dikembalikan.Perhatikan bahwa kami mengakses simpul pohon parse
n
menggunakan beberapa makro sepertiNCH
danCHILD
. Ini layak untuk dipahami - kode mereka ada di dalamnyaInclude/node.h
.Pencernaan: Komposisi AST
Saya memilih untuk membuat jenis AST baru untuk
until
pernyataan tersebut, tetapi sebenarnya ini tidak perlu. Saya bisa menyimpan beberapa pekerjaan dan menerapkan fungsi baru menggunakan komposisi node AST yang ada, karena:Secara fungsional setara dengan:
Alih-alih membuat
Until
simpul diast_for_until_stmt
, saya bisa membuatNot
simpul denganWhile
simpul sebagai anak. Karena kompilator AST sudah mengetahui cara menangani node ini, langkah proses selanjutnya dapat dilewati.Mengompilasi AST menjadi bytecode
Langkah selanjutnya adalah menyusun AST menjadi bytecode Python. Kompilasi memiliki hasil perantara yang merupakan CFG (Control Flow Graph), tetapi karena kode yang sama menanganinya, saya akan mengabaikan detail ini untuk saat ini dan membiarkannya untuk artikel lain.
Kode yang akan kita lihat selanjutnya adalah
Python/compile.c
. Mengikuti petunjuk dariwhile
, kami menemukan fungsicompiler_visit_stmt
, yang bertanggung jawab untuk menyusun pernyataan menjadi bytecode. Kami menambahkan klausa untukUntil
:Jika Anda bertanya-tanya apa
Until_kind
itu, itu adalah konstanta (sebenarnya nilai_stmt_kind
enumerasi) yang secara otomatis dihasilkan dari file definisi AST keInclude/Python-ast.h
. Bagaimanapun, kami menyebutnyacompiler_until
yang, tentu saja, masih belum ada. Saya akan membahasnya sebentar.Jika Anda penasaran seperti saya, Anda akan melihat itu
compiler_visit_stmt
aneh. Tidak ada jumlahgrep
-ping dari pohon sumber yang mengungkapkan di mana ia dipanggil. Jika demikian, hanya satu opsi yang tersisa - C makro-fu. Memang, investigasi singkat membawa kita keVISIT
makro yang didefinisikan diPython/compile.c
:Ini digunakan untuk memohon
compiler_visit_stmt
dicompiler_body
. Kembali ke bisnis kami, namun ...Seperti yang dijanjikan, berikut ini
compiler_until
:Saya harus membuat pengakuan: kode ini tidak ditulis berdasarkan pemahaman mendalam tentang bytecode Python. Seperti artikel lainnya, itu dilakukan dengan meniru
compiler_while
fungsi kerabat . Dengan membacanya dengan hati-hati, bagaimanapun, dengan mengingat bahwa VM Python berbasis tumpukan, dan melihat sekilas ke dalam dokumentasidis
modul, yang memiliki daftar bytecode Python dengan deskripsi, mungkin untuk memahami apa yang terjadi.Itu saja, kita sudah selesai ... bukan?
Setelah membuat semua perubahan dan menjalankan
make
, kita dapat menjalankan Python yang baru dikompilasi dan mencobauntil
pernyataan baru kita :Voila, berhasil! Mari kita lihat bytecode yang dibuat untuk pernyataan baru dengan menggunakan
dis
modul sebagai berikut:Inilah hasilnya:
Operasi yang paling menarik adalah nomor 12: jika kondisinya benar, kita lompat ke setelah pengulangan. Ini adalah semantik yang benar untuk
until
. Jika lompatan tidak dijalankan, badan loop terus berjalan hingga melompat kembali ke kondisi pada operasi 35.Merasa senang dengan perubahan saya, saya kemudian mencoba menjalankan fungsi (mengeksekusi
myfoo(3)
) alih-alih menampilkan bytecode-nya. Hasilnya kurang menggembirakan:Whoa ... ini tidak bagus. Jadi apa yang salah?
Kasus tabel simbol yang hilang
Salah satu langkah yang dilakukan oleh compiler Python saat mengompilasi AST adalah membuat tabel simbol untuk kode yang dikompilasinya. Panggilan ke
PySymtable_Build
dalamPyAST_Compile
panggilan ke modul tabel simbol (Python/symtable.c
), yang menjalankan AST dengan cara yang mirip dengan fungsi pembuatan kode. Memiliki tabel simbol untuk setiap ruang lingkup membantu kompilator menemukan beberapa informasi kunci, seperti variabel mana yang global dan yang lokal untuk suatu cakupan.Untuk memperbaiki masalah ini, kita harus memodifikasi
symtable_visit_stmt
fungsi inPython/symtable.c
, menambahkan kode untuk menanganiuntil
pernyataan, setelah kode serupa untukwhile
pernyataan [3] :[3] : Omong-omong, tanpa kode ini ada peringatan compiler untuk
Python/symtable.c
. Kompilator memperhatikan bahwa nilaiUntil_kind
enumerasi tidak ditangani dalam pernyataan switchsymtable_visit_stmt
dan komplain. Selalu penting untuk memeriksa peringatan kompiler!Dan sekarang kita benar-benar selesai. Mengompilasi sumber setelah perubahan ini membuat eksekusi
myfoo(3)
pekerjaan seperti yang diharapkan.Kesimpulan
Dalam artikel ini saya telah mendemonstrasikan cara menambahkan pernyataan baru ke Python. Meskipun membutuhkan sedikit perubahan dalam kode kompiler Python, perubahan itu tidak sulit untuk diterapkan, karena saya menggunakan pernyataan yang serupa dan yang sudah ada sebagai pedoman.
Kompiler Python adalah perangkat lunak yang canggih, dan saya tidak mengklaim sebagai ahli di dalamnya. Namun, saya sangat tertarik dengan bagian dalam Python, dan khususnya bagian depannya. Oleh karena itu, saya menemukan latihan ini sebagai pendamping yang sangat berguna untuk studi teoritis dari prinsip-prinsip kompiler dan kode sumber. Ini akan berfungsi sebagai dasar untuk artikel mendatang yang akan membahas lebih dalam kompiler.
Referensi
Saya menggunakan beberapa referensi bagus untuk konstruksi artikel ini. Di sini mereka tanpa urutan tertentu:
sumber asli
sumber
until
adalahisa
/isan
seperti padaif something isa dict:
atauif something isan int:
Salah satu cara untuk melakukan hal-hal seperti ini adalah dengan memproses sumber dan memodifikasinya, menerjemahkan pernyataan Anda yang ditambahkan ke python. Ada berbagai masalah yang akan ditimbulkan oleh pendekatan ini, dan saya tidak akan merekomendasikannya untuk penggunaan umum, tetapi untuk eksperimen dengan bahasa, atau metaprogramming dengan tujuan khusus, terkadang dapat berguna.
Misalnya, katakanlah kita ingin memperkenalkan pernyataan "myprint", yang alih-alih mencetak ke layar malah mencatat ke file tertentu. yaitu:
akan sama dengan
Ada berbagai opsi tentang bagaimana melakukan penggantian, dari substitusi regex hingga menghasilkan AST, hingga menulis parser Anda sendiri tergantung pada seberapa dekat sintaks Anda dengan python yang ada. Pendekatan menengah yang baik adalah dengan menggunakan modul tokenizer. Ini memungkinkan Anda untuk menambahkan kata kunci baru, struktur kontrol, dll sambil menafsirkan sumber yang mirip dengan penerjemah python, sehingga menghindari kerusakan yang disebabkan oleh solusi regex mentah. Untuk "myprint" di atas, Anda dapat menulis kode transformasi berikut:
(Ini membuat myprint efektif menjadi kata kunci, jadi gunakan sebagai variabel di tempat lain kemungkinan besar akan menyebabkan masalah)
Masalahnya kemudian adalah bagaimana menggunakannya sehingga kode Anda dapat digunakan dari python. Salah satu caranya adalah dengan menulis fungsi impor Anda sendiri, dan menggunakannya untuk memuat kode yang ditulis dalam bahasa kustom Anda. yaitu:
Ini mengharuskan Anda menangani kode khusus Anda secara berbeda dari modul python normal. yaitu "
some_mod = myimport("some_mod.py")
" daripada "import some_mod
"Solusi lain yang cukup rapi (meskipun hacky) adalah membuat pengkodean khusus (Lihat PEP 263 ) seperti yang ditunjukkan resep ini . Anda dapat menerapkan ini sebagai:
Sekarang setelah kode ini dijalankan (mis. Anda dapat menempatkannya di .pythonrc atau site.py) kode apa pun yang dimulai dengan komentar "# coding: mylang" akan secara otomatis diterjemahkan melalui langkah preprocessing di atas. misalnya.
Peringatan:
Ada masalah dengan pendekatan praprosesor, karena Anda mungkin akan terbiasa jika Anda pernah bekerja dengan praprosesor C. Yang utama adalah debugging. Semua yang dilihat python adalah file yang diproses sebelumnya yang berarti bahwa teks yang dicetak di jejak tumpukan dll akan merujuk ke sana. Jika Anda telah melakukan terjemahan yang signifikan, ini mungkin sangat berbeda dari teks sumber Anda. Contoh di atas tidak mengubah nomor baris dll, jadi tidak akan terlalu berbeda, tetapi semakin banyak Anda mengubahnya, semakin sulit untuk mengetahuinya.
sumber
myimport
modul yang hanya berisiprint 1
karena itu hanya baris hasil kode=1 ... SyntaxError: invalid syntax
b=myimport("b.py")
", dan b.py yang hanya berisi "print 1
". Apakah ada kesalahan lain (pelacakan tumpukan dll)?import
menggunakan bawaan__import__
, jadi jika Anda menimpanya ( sebelum mengimpor modul yang memerlukan impor yang dimodifikasi), Anda tidak memerlukan yang terpisahmyimport
Ya, sampai batas tertentu itu mungkin. Ada sebuah modul di luar sana yang digunakan
sys.settrace()
untuk mengimplementasikangoto
dancomefrom
"kata kunci":sumber
Pendek berubah dan mengkompilasi ulang kode sumber (yang merupakan mungkin dengan open source), mengubah bahasa dasar tidak benar-benar mungkin.
Bahkan jika Anda mengkompilasi ulang sumbernya, itu tidak akan menjadi python, hanya versi peretasan Anda yang diubah yang Anda harus sangat berhati-hati untuk tidak memasukkan bug ke dalamnya.
Namun, saya tidak yakin mengapa Anda menginginkannya. Fitur berorientasi objek Python membuatnya cukup sederhana untuk mencapai hasil yang serupa dengan bahasa yang ada.
sumber
Jawaban umum: Anda perlu memproses file sumber Anda.
Jawaban yang lebih spesifik: instal EasyExtend , dan lakukan langkah-langkah berikut
i) Membuat bahasa baru (bahasa ekstensi)
Tanpa spesifikasi tambahan, banyak file akan dibuat di EasyExtend / langlets / mystmts /.
ii) Buka mystmts / parsedef / Grammar.ext dan tambahkan baris berikut
Ini cukup untuk menentukan sintaks pernyataan baru Anda. Small_stmt non-terminal adalah bagian dari tata bahasa Python dan itu adalah tempat di mana pernyataan baru dihubungkan. Pengurai sekarang akan mengenali pernyataan baru yaitu file sumber yang berisi itu akan diurai. Kompilator akan menolaknya karena masih harus diubah menjadi Python yang valid.
iii) Sekarang kita harus menambahkan semantik dari pernyataan tersebut. Untuk yang satu ini harus mengedit msytmts / langlet.py dan menambahkan pengunjung simpul my_stmt.
iv) cd ke langlets / mystmts dan ketik
Sekarang sesi akan dimulai dan pernyataan yang baru didefinisikan dapat digunakan:
Cukup beberapa langkah untuk sampai pada pernyataan yang sepele, bukan? Belum ada API yang memungkinkan seseorang mendefinisikan hal-hal sederhana tanpa harus peduli dengan tata bahasa. Tapi EE modulo sangat handal beberapa bug. Jadi hanya masalah waktu munculnya API yang memungkinkan pemrogram menentukan hal-hal yang mudah digunakan seperti operator infix atau pernyataan kecil menggunakan pemrograman OO yang nyaman. Untuk hal-hal yang lebih kompleks seperti menyematkan seluruh bahasa dengan Python dengan membuat langlet, tidak ada cara untuk menggunakan pendekatan tata bahasa lengkap.
sumber
Inilah cara yang sangat sederhana tapi jelek untuk menambahkan pernyataan baru, dalam mode interpretatif . Saya menggunakannya untuk perintah 1 huruf kecil untuk mengedit anotasi gen hanya menggunakan sys.displayhook, tetapi supaya saya bisa menjawab pertanyaan ini, saya menambahkan sys.excepthook untuk kesalahan sintaks juga. Yang terakhir ini benar-benar jelek, mengambil kode mentah dari buffer readline. Manfaatnya adalah, sangat mudah menambahkan pernyataan baru dengan cara ini.
sumber
Saya telah menemukan panduan untuk menambahkan pernyataan baru:
https://troeger.eu/files/teaching/pythonvm08lab.pdf
Pada dasarnya, untuk menambahkan pernyataan baru, Anda harus mengedit
Python/ast.c
(antara lain) dan mengkompilasi ulang biner python.Meskipun mungkin, jangan. Anda dapat mencapai hampir semuanya melalui fungsi dan kelas (yang tidak mengharuskan orang untuk mengkompilasi ulang python hanya untuk menjalankan skrip Anda ..)
sumber
Anda dapat melakukan ini menggunakan EasyExtend :
sumber
Ini tidak benar-benar menambahkan pernyataan baru ke sintaks bahasa, tetapi makro adalah alat yang ampuh: https://github.com/lihaoyi/macropy
sumber
Bukan tanpa memodifikasi juru bahasa. Saya tahu banyak bahasa dalam beberapa tahun terakhir telah digambarkan sebagai "dapat diperluas", tetapi tidak seperti yang Anda gambarkan. Anda memperluas Python dengan menambahkan fungsi dan kelas.
sumber
Ada bahasa berdasarkan python yang disebut Logix yang dengannya Anda BISA melakukan hal-hal seperti itu. Ini belum dalam pengembangan untuk sementara waktu, tetapi fitur yang Anda minta berfungsi dengan versi terbaru.
sumber
Beberapa hal dapat dilakukan dengan dekorator. Mari kita asumsikan, Python tidak memiliki
with
pernyataan. Kami kemudian dapat menerapkan perilaku serupa seperti ini:Ini adalah solusi yang sangat tidak bersih seperti yang dilakukan di sini. Terutama perilaku di mana dekorator memanggil fungsi dan set
_
keNone
tidak terduga. Untuk klarifikasi: Dekorator ini setara dengan tulisandan dekorator biasanya diharapkan untuk memodifikasi, bukan menjalankan, fungsi.
Saya menggunakan metode seperti itu sebelumnya dalam skrip di mana saya harus mengatur sementara direktori kerja untuk beberapa fungsi.
sumber
Sepuluh tahun yang lalu Anda tidak bisa, dan saya ragu itu berubah. Namun, tidak sulit untuk memodifikasi sintaks saat itu jika Anda siap untuk mengkompilasi ulang python, dan saya ragu itu juga berubah.
sumber