Ini adalah perintah perlinterp yang dapat dijalankan di penyedia hosting gratis OnWorks menggunakan salah satu dari beberapa workstation online gratis kami seperti Ubuntu Online, Fedora Online, emulator online Windows atau emulator online MAC OS
PROGRAM:
NAMA
perlinterp - Ikhtisar penerjemah Perl
DESKRIPSI
Dokumen ini memberikan gambaran umum tentang bagaimana juru bahasa Perl bekerja pada tingkat C
kode, bersama dengan pointer ke file kode sumber C yang relevan.
UNSUR OF THE PENERJEMAH
Pekerjaan juru bahasa memiliki dua tahap utama: menyusun kode ke dalam internal
representasi, atau bytecode, dan kemudian mengeksekusinya. "Kode yang dikompilasi" di perlguts menjelaskan
persis bagaimana tahap kompilasi terjadi.
Berikut ini adalah rincian singkat dari operasi Perl:
startup
Aksi dimulai pada perlmain.c. (atau miniperlmain.c untuk miniperl) Ini adalah level yang sangat tinggi
kode, cukup untuk muat di satu layar, dan menyerupai kode yang ditemukan di perlembed; paling
dari tindakan nyata terjadi di perl.c
perlmain.c dihasilkan oleh "ExtUtils::Miniperl" dari miniperlmain.c pada waktu yang tepat, jadi kamu
harus membuat Perl mengikuti ini.
Pertama, perlmain.c mengalokasikan beberapa memori dan membangun juru bahasa Perl, bersama ini
baris:
1 PERL_SYS_INIT3(&argc,&argv,&env);
2
3 jika (!PL_do_undump) {
4 my_perl = perl_alloc();
5 jika (!my_perl)
6 keluar(1);
7 perl_construct(perl_saya);
8 PL_perl_destruct_level = 0;
9}
Baris 1 adalah makro, dan definisinya tergantung pada sistem operasi Anda. Baris 3
referensi "PL_do_undump", variabel global - semua variabel global di Perl dimulai dengan
"PL_". Ini memberi tahu Anda apakah program yang sedang berjalan dibuat dengan flag "-u"
untuk perl dan kemudian membatalkan pembuangan, yang berarti itu akan salah dalam konteks waras apa pun.
Baris 4 memanggil fungsi di perl.c untuk mengalokasikan memori untuk juru bahasa Perl. Ini cukup
fungsi sederhana, dan nyalinya terlihat seperti ini:
my_perl = (PerlInterpreter*)PerlMem_malloc(sizeof(PerlInterpreter));
Di sini Anda melihat contoh abstraksi sistem Perl, yang akan kita lihat nanti:
"PerlMem_malloc" adalah "malloc" sistem Anda, atau "malloc" milik Perl seperti yang didefinisikan dalam
malloc.c jika Anda memilih opsi itu pada waktu konfigurasi.
Selanjutnya, pada baris 7, kita membangun interpreter menggunakan perl_construct, juga dalam perl.c; ini
mengatur semua variabel khusus yang dibutuhkan Perl, tumpukan, dan sebagainya.
Sekarang kami memberikan Perl opsi baris perintah, dan menyuruhnya pergi:
status keluar = perl_parse(perl_saya, xs_init, argc, argv, (char **)NULL);
jika (! status keluar)
perl_run(perl_saya);
status keluar = perl_destruct(perl_saya);
perl_gratis(perl_saya);
"perl_parse" sebenarnya adalah pembungkus di sekitar "S_parse_body", seperti yang didefinisikan dalam perl.c, yang
memproses opsi baris perintah, menyiapkan modul XS yang terhubung secara statis, membuka
program dan memanggil "yyparse" untuk menguraikannya.
Penguraian
Tujuan dari tahap ini adalah untuk mengambil sumber Perl, dan mengubahnya menjadi pohon op. Kita lihat saja nanti
seperti apa salah satunya nanti. Sebenarnya, ada tiga hal yang terjadi di sini.
"yyparse", pengurai, tinggal di perly.c, meskipun Anda lebih baik membaca yang asli
masukan YACC perly.y. (Ya, Virginia, di sana is tata bahasa YACC untuk Perl!) Tugas dari
parser adalah mengambil kode Anda dan "memahaminya", membaginya menjadi kalimat, memutuskan
operan mana yang pergi dengan operator mana dan seterusnya.
Pengurai dibantu dengan baik oleh lexer, yang memotong input Anda menjadi token, dan
memutuskan jenis hal apa setiap token adalah: nama variabel, operator, bareword, a
subrutin, fungsi inti, dan sebagainya. Titik masuk utama ke lexer adalah "yylex",
dan itu dan rutinitas yang terkait dapat ditemukan di toke.c. Perl tidak seperti yang lain
bahasa komputer; kadang-kadang sangat sensitif terhadap konteks, bisa jadi sulit untuk diselesaikan
apa jenis token sesuatu, atau di mana token berakhir. Karena itu, ada banyak
interaksi antara tokeniser dan parser, yang bisa menjadi sangat menakutkan jika Anda
tidak terbiasa.
Saat parser memahami program Perl, parser membangun sebuah pohon operasi untuk
interpreter untuk melakukan selama eksekusi. Rutinitas yang membangun dan menghubungkan bersama-sama
berbagai operasi dapat ditemukan di op.c, dan akan diperiksa kemudian.
Optimasi
Sekarang tahap penguraian selesai, dan pohon yang telah selesai mewakili operasi yang
yang perlu dilakukan juru bahasa Perl untuk menjalankan program kita. Selanjutnya, Perl melakukan lari kering
di atas pohon mencari pengoptimalan: ekspresi konstan seperti "3 + 4" akan menjadi
dihitung sekarang, dan pengoptimal juga akan melihat apakah ada beberapa operasi yang dapat diganti
dengan satu. Misalnya, untuk mengambil variabel $foo, alih-alih mengambil glob
*foo dan melihat komponen skalar, pengoptimal mengutak-atik pohon op untuk menggunakan a
fungsi yang secara langsung mencari skalar yang bersangkutan. Pengoptimal utama adalah "mengintip" di
op.c, dan banyak operasi memiliki fungsi pengoptimalannya sendiri.
Running
Sekarang kami akhirnya siap untuk pergi: kami telah mengkompilasi kode byte Perl, dan semua yang tersisa untuk dilakukan
adalah menjalankannya. Eksekusi sebenarnya dilakukan oleh fungsi "runops_standard" di lari.c; lebih
secara khusus, ini dilakukan oleh tiga garis yang tampak polos ini:
while ((PL_op = PL_op->op_ppaddr(aTHX))) {
PERL_ASYNC_CHECK();
}
Anda mungkin lebih nyaman dengan versi Perl itu:
PERL_ASYNC_CHECK() while $Perl::op = &{$Perl::op->{function}};
Yah, mungkin tidak. Bagaimanapun, setiap op berisi pointer fungsi, yang menetapkan
fungsi yang benar-benar akan melakukan operasi. Fungsi ini akan mengembalikan yang berikutnya
op dalam urutan - ini memungkinkan untuk hal-hal seperti "jika" yang memilih op berikutnya secara dinamis
pada waktu berjalan. "PERL_ASYNC_CHECK" memastikan bahwa hal-hal seperti sinyal mengganggu
eksekusi jika diperlukan.
Fungsi sebenarnya yang disebut dikenal sebagai kode PP, dan mereka tersebar di antara empat file:
pp_panas.c berisi kode "panas", yang paling sering digunakan dan sangat dioptimalkan, pp_sys.c
berisi semua fungsi khusus sistem, pp_ctl.c berisi fungsi-fungsi yang
menerapkan struktur kontrol ("jika", "sementara" dan sejenisnya) dan hal.c berisi segalanya
lain. Ini adalah, jika Anda suka, kode C untuk fungsi dan operator bawaan Perl.
Perhatikan bahwa setiap fungsi "pp_" diharapkan mengembalikan pointer ke operasi berikutnya. Panggilan ke
subs perl (dan blok eval) ditangani dalam loop runops yang sama, dan tidak mengkonsumsi
ruang ekstra pada tumpukan C. Misalnya, "pp_entersub" dan "pp_entertry" cukup tekan
Blok struct "CxSUB" atau "CxEVAL" ke tumpukan konteks yang berisi alamat
op mengikuti sub panggilan atau eval. Mereka kemudian mengembalikan op pertama dari sub atau eval itu
blok, dan eksekusi berlanjut dari sub atau blok itu. Nanti, "pp_leavesub" atau
"pp_leavetry" op memunculkan "CxSUB" atau "CxEVAL", mengambil kembali op darinya, dan
mengembalikannya.
Pengecualian menyerahkan
Penyerahan pengecualian Perl (yaitu "mati" dll.) dibangun di atas level rendah
"setjmp()"/"longjmp()" fungsi C-library. Ini pada dasarnya menyediakan cara untuk menangkap
register PC dan SP saat ini dan kemudian memulihkannya; yaitu "longjmp()" berlanjut di
titik dalam kode di mana "setjmp()" sebelumnya telah dilakukan, dengan apa pun yang lebih jauh di atas C
tumpukan hilang. Inilah sebabnya mengapa kode harus selalu menyimpan nilai menggunakan "SAVE_FOO" daripada
dalam variabel otomatis.
Inti Perl membungkus "setjmp()" dll di makro "JMPENV_PUSH" dan "JMPENV_JUMP". Itu
aturan dasar pengecualian Perl adalah bahwa "keluar", dan "mati" (dengan tidak adanya "eval") tampil
a JMPENV_JUMP(2), sementara "mati" dalam "eval" melakukan a JMPENV_JUMP(3).
Pada titik masuk ke Perl, seperti "perl_parse()", "perl_run()" dan "call_sv(cv, G_EVAL)"
masing-masing melakukan "JMPENV_PUSH", lalu masukkan loop runops atau apa pun, dan tangani mungkin
pengecualian kembali. Untuk pengembalian 2 kali, pembersihan akhir dilakukan, seperti mengeluarkan tumpukan dan
memanggil blok "CHECK" atau "END". Antara lain, ini adalah bagaimana pembersihan ruang lingkup masih
terjadi selama "keluar".
Jika "mati" dapat menemukan blok "CxEVAL" pada tumpukan konteks, maka tumpukan tersebut akan muncul
level itu dan operasi kembali di blok itu ditugaskan ke "PL_restartop"; lalu a
JMPENV_JUMP(3) dilakukan. Ini biasanya melewati kontrol kembali ke penjaga. Dalam kasus ini
dari "perl_run" dan "call_sv", "PL_restartop" non-null memicu masuk kembali ke runops
lingkaran. Ini adalah cara normal untuk menangani "mati" atau "serak" dalam "eval".
Terkadang ops dieksekusi dalam loop runops bagian dalam, seperti tie, sort, atau overload
kode. Dalam hal ini, sesuatu seperti
sub FETCH { eval { mati } }
akan menyebabkan longjmp kembali ke penjaga di "perl_run", memunculkan kedua loop runops,
yang jelas tidak benar. Salah satu cara untuk menghindari ini adalah untuk kode dasi untuk melakukan a
"JMPENV_PUSH" sebelum menjalankan "FETCH" di loop runops bagian dalam, tetapi untuk efisiensi
alasan, Perl sebenarnya hanya menetapkan bendera, menggunakan "CATCH_SET(TRUE)". "pp_require",
Operasi "pp_entereval" dan "pp_entertry" memeriksa flag ini, dan jika benar, mereka memanggil "docatch",
yang melakukan "JMPENV_PUSH" dan memulai level runops baru untuk mengeksekusi kode, daripada
melakukannya pada loop saat ini.
Sebagai pengoptimalan lebih lanjut, saat keluar dari blok eval di "FETCH", eksekusi
kode yang mengikuti blok masih dijalankan di loop dalam. Ketika pengecualian adalah
dinaikkan, "docatch" membandingkan level "JMPENV" dari "CxEVAL" dengan "PL_top_env" dan jika
mereka berbeda, hanya melemparkan kembali pengecualian. Dengan cara ini setiap loop dalam akan muncul.
Berikut ini contoh.
1: eval { dasi @a, 'A' };
2: sub A::TIEARRAY {
3: eval { mati };
4: mati;
5: }
Untuk menjalankan kode ini, "perl_run" dipanggil, yang melakukan "JMPENV_PUSH" lalu memasuki runop
lingkaran. Loop ini menjalankan operasi eval dan tie pada baris 1, dengan eval mendorong "CxEVAL"
ke tumpukan konteks.
"pp_tie" melakukan "CATCH_SET(TRUE)", kemudian memulai loop runops kedua untuk mengeksekusi
tubuh "TIEARRAY". Ketika menjalankan operasi masuk pada baris 3, "CATCH_GET" benar, jadi
"pp_entertry" memanggil "docatch" yang melakukan "JMPENV_PUSH" dan memulai loop runops ketiga,
yang kemudian menjalankan operasi mati. Pada titik ini tumpukan panggilan C terlihat seperti ini:
Perl_pp_die
Perl_runops # putaran ketiga
S_docatch_body
S_docatch
Perl_pp_entertry
Perl_runops # putaran kedua
S_panggilan_tubuh
Perl_call_sv
Perl_pp_tie
Perl_runops # putaran pertama
S_run_body
perl_run
utama
dan konteks dan tumpukan data, seperti yang ditunjukkan oleh "-Dstv", terlihat seperti:
TUMPUKAN 0: UTAMA
CX 0: BLOK =>
CX 1: EVAL => AV() PV("A"\0)
retop = pergi
TUMPUKAN 1: AJAIB
CX 0: SUB =>
retop=(nol)
CX 1: EVAL => *
retop=status selanjutnya
Die mengeluarkan "CxEVAL" pertama dari tumpukan konteks, menetapkan "PL_restartop" darinya, melakukan
JMPENV_JUMP(3), dan kontrol kembali ke "docatch" teratas. Ini kemudian memulai sepertiga lagi-
level runops level, yang mengeksekusi state berikutnya, pushmark, dan die ops pada baris 4. Di
titik bahwa "pp_die" kedua dipanggil, tumpukan panggilan C terlihat persis seperti di atas,
meskipun kita tidak lagi berada dalam evaluasi batin; ini karena pengoptimalan
disebutkan sebelumnya. Namun, tumpukan konteks sekarang terlihat seperti ini, yaitu dengan CxEVAL . teratas
muncul:
TUMPUKAN 0: UTAMA
CX 0: BLOK =>
CX 1: EVAL => AV() PV("A"\0)
retop = pergi
TUMPUKAN 1: AJAIB
CX 0: SUB =>
retop=(nol)
Die pada baris 4 memunculkan tumpukan konteks kembali ke CxEVAL, meninggalkannya sebagai:
TUMPUKAN 0: UTAMA
CX 0: BLOK =>
Seperti biasa, "PL_restartop" diekstraksi dari "CxEVAL", dan a JMPENV_JUMP(3) selesai, yang
memunculkan tumpukan C kembali ke docatch:
S_docatch
Perl_pp_entertry
Perl_runops # putaran kedua
S_panggilan_tubuh
Perl_call_sv
Perl_pp_tie
Perl_runops # putaran pertama
S_run_body
perl_run
utama
Dalam hal ini, karena level "JMPENV" yang direkam dalam "CxEVAL" berbeda dari
yang saat ini, "docatch" hanya melakukan JMPENV_JUMP(3) dan tumpukan C terlepas ke:
perl_run
utama
Karena "PL_restartop" bukan nol, "run_body" memulai loop dan eksekusi runops baru
terus berlanjut.
INTERN VARIABEL JENIS
Anda seharusnya sudah melihat perlguts, yang memberi tahu Anda tentang internal Perl
tipe variabel: SV, HV, AV, dan lainnya. Jika tidak, lakukan itu sekarang.
Variabel-variabel ini digunakan tidak hanya untuk mewakili variabel Perl-space, tetapi juga semua
konstanta dalam kode, serta beberapa struktur yang sepenuhnya internal untuk Perl. Simbol
tabel, misalnya, adalah hash Perl biasa. Kode Anda diwakili oleh SV seperti itu
membaca ke dalam parser; semua file program yang Anda panggil dibuka melalui filehandle Perl biasa,
dan seterusnya.
Modul inti Devel::Peek memungkinkan kita memeriksa SV dari program Perl. Mari kita lihat, untuk
misalnya, bagaimana Perl memperlakukan konstanta "halo".
% perl -MDevel::Peek -e 'Dump("halo")'
1 SV = PV(0xa041450) di 0xa04ecbc
2 REFCNT = 1
3 BENDERA = (POK,READONLY,pPOK)
4 PV = 0xa0484e0 "halo"\0
5 kur = 5
6 LEN = 6
Membaca keluaran "Devel::Peek" membutuhkan sedikit latihan, jadi mari kita bahas baris demi baris.
Baris 1 memberitahu kita bahwa kita sedang melihat SV yang hidup di 0xa04ecbc dalam memori. SV sendiri
adalah struktur yang sangat sederhana, tetapi mengandung penunjuk ke struktur yang lebih kompleks. Di dalam
kasus ini, ini adalah PV, struktur yang menyimpan nilai string, di lokasi 0xa041450. Garis
2 adalah jumlah referensi; tidak ada referensi lain untuk data ini, jadi ini 1.
Baris 3 adalah flag untuk SV ini - boleh digunakan sebagai PV, ini adalah SV read-only (karena
itu adalah konstanta) dan datanya adalah PV internal. Selanjutnya kita sudah mendapatkan isinya
string, mulai dari lokasi 0xa0484e0.
Baris 5 memberi kita panjang string saat ini - perhatikan bahwa ini benar tidak termasuk
terminator nol. Baris 6 bukanlah panjang senar, melainkan panjang senar saat ini
buffer yang dialokasikan; saat string tumbuh, Perl secara otomatis memperluas penyimpanan yang tersedia
melalui rutinitas yang disebut "SvGROW".
Anda bisa mendapatkan jumlah ini dari C dengan sangat mudah; cukup tambahkan "Sv" ke nama
bidang yang ditampilkan dalam cuplikan, dan Anda memiliki makro yang akan mengembalikan nilai:
"SvCUR(sv)" mengembalikan panjang string saat ini, "SvREFCOUNT(sv)" mengembalikan
jumlah referensi, "SvPV(sv, len)" mengembalikan string itu sendiri dengan panjangnya, dan seterusnya.
Lebih banyak makro untuk memanipulasi properti ini dapat ditemukan di perlguts.
Mari kita ambil contoh memanipulasi PV, dari "sv_catpvn", di sv.c
1 kosong
2 Perl_sv_catpvn(pTHX_ SV *sv, const char *ptr, STRLEN len)
3 {
4 STRLEN;
5 karakter *sampah;
6 sampah = SvPV_force(sv, tlen);
7 SvGROW(sv, tlen + len + 1);
8 jika (ptr == sampah)
9 ptr = SvPVX(sv);
10 Gerakan(ptr,SvPVX(sv)+tlen,len,char);
11 SvCUR(sv) += len;
12 *SvEND(sv) = '\0';
13 (batal)SvPOK_only_UTF8(sv); /* validasi penunjuk */
14 SvTAINT(sv);
15}
Ini adalah fungsi yang menambahkan string, "ptr", dengan panjang "len" ke ujung PV
disimpan di "sv". Hal pertama yang kita lakukan pada baris 6 adalah memastikan bahwa SV memiliki PV yang valid,
dengan memanggil makro "SvPV_force" untuk memaksa PV. Sebagai efek samping, "tlen" disetel ke
nilai PV saat ini, dan PV itu sendiri dikembalikan ke "sampah".
Pada baris 7, kami memastikan bahwa SV akan memiliki cukup ruang untuk menampung string lama,
string baru dan terminator nol. Jika "LEN" tidak cukup besar, "SvGROW" akan
mengalokasikan kembali ruang untuk kita.
Sekarang, jika "sampah" sama dengan string yang kita coba tambahkan, kita bisa mengambil stringnya
langsung dari SV; "SvPVX" adalah alamat PV di SV.
Baris 10 melakukan catenation yang sebenarnya: makro "Pindahkan" memindahkan sebagian memori di sekitar: kita
pindahkan string "ptr" ke ujung PV - itulah awal PV ditambah arusnya
panjang. Kami memindahkan byte "len" dari tipe "char". Setelah melakukannya, kita perlu memberi tahu Perl
kami telah memperpanjang string, dengan mengubah "CUR" untuk mencerminkan panjang baru. "SvEND" adalah makro
yang memberi kita akhir dari string, sehingga harus berupa "\0".
Baris 13 memanipulasi bendera; karena kami telah mengubah PV, nilai IV atau NV apa pun tidak akan
lagi valid: jika kita memiliki "$a=10; $a.="6";" kami tidak ingin menggunakan IV lama 10.
"SvPOK_only_utf8" adalah versi khusus "SvPOK_only" yang sadar UTF-8, sebuah makro yang mengubah
mematikan bendera IOK dan NOK dan menyalakan POK. "SvTAINT" terakhir adalah makro yang dicuci
data tercemar jika mode noda diaktifkan.
AV dan HV lebih rumit, tetapi SV sejauh ini merupakan tipe variabel yang paling umum
dilemparkan ke sekeliling. Setelah melihat sesuatu tentang bagaimana kita memanipulasi ini, mari kita lanjutkan dan lihat
bagaimana pohon op dibangun.
OP POHON
Pertama, apa sih op tree itu? Pohon op adalah representasi yang diuraikan dari Anda
program, seperti yang kita lihat di bagian kami tentang penguraian, dan urutan operasi itulah yang
Perl menjalankan untuk menjalankan program Anda, seperti yang kita lihat di "Menjalankan".
Operasi adalah operasi dasar yang dapat dilakukan Perl: semua fungsi bawaan dan
operator adalah ops, dan ada serangkaian ops yang berhubungan dengan konsep interpreter
kebutuhan internal - memasuki dan meninggalkan blok, mengakhiri pernyataan, mengambil variabel,
dan seterusnya.
Pohon op terhubung dalam dua cara: Anda dapat membayangkan bahwa ada dua "rute" melalui
itu, dua perintah di mana Anda dapat melintasi pohon. Pertama, urutan parse mencerminkan bagaimana
parser memahami kodenya, dan kedua, perintah eksekusi memberi tahu Perl perintah apa yang harus dilakukan
operasi di.
Cara termudah untuk memeriksa pohon op adalah dengan menghentikan Perl setelah selesai diurai, dan
mendapatkannya untuk membuang pohon. Inilah tepatnya yang di-backend oleh compiler B::Terse,
B::Ringkas dan B::Debug lakukan.
Mari kita lihat bagaimana Perl melihat "$a = $b + $c":
% perl -MO=Terse -e '$a=$b+$c'
1 LISTOP (0x8179888) cuti
2 OP (0x81798b0) masuk
3 COP (0x8179850) selanjutnya
4 penetapan BINOP (0x8179828).
5 BINOP (0x8179800) tambahkan [1]
6 UNOP (0x81796e0) nol [15]
7 SVOP (0x80fafe0) gvsv GV (0x80fa4cc) *b
8 UNOP (0x81797e0) nol [15]
9 SVOP (0x8179700) gvsv GV (0x80efeb0) *c
10 UNOP (0x816b4f0) nol [15]
11 SVOP (0x816dcf0) gvsv GV (0x80fa460) *a
Mari kita mulai di tengah, di baris 4. Ini adalah BINOP, operator biner, yang ada di
lokasi 0x8179828. Operator spesifik yang dimaksud adalah "sassign" - penetapan skalar -
dan Anda dapat menemukan kode yang mengimplementasikannya dalam fungsi "pp_sassign" di pp_panas.c. Sebagai
operator biner, ia memiliki dua anak: operator add, memberikan hasil "$b+$c",
paling atas pada baris 5, dan sisi kiri pada baris 10.
Baris 10 adalah operasi nol: ini tidak melakukan apa-apa. Apa yang dilakukan di sana? Jika kamu melihat
null op, itu pertanda bahwa ada sesuatu yang telah dioptimalkan setelah diurai. Seperti yang kita
disebutkan dalam "Pengoptimalan", tahap pengoptimalan terkadang mengubah dua operasi menjadi
satu, misalnya saat mengambil variabel skalar. Ketika ini terjadi, alih-alih menulis ulang
pohon op dan membersihkan pointer yang menjuntai, lebih mudah hanya dengan mengganti
operasi yang berlebihan dengan null op. Awalnya, pohon itu akan terlihat seperti ini:
10 SVOP (0x816b4f0) rv2sv [15]
11 SVOP (0x816dcf0) gv GV (0x80fa460) *a
Yaitu, ambil entri "a" dari tabel simbol utama, lalu lihat skalar
komponennya: "gvsv" ("pp_gvsv" menjadi pp_panas.c) kebetulan melakukan kedua hal ini.
Sisi kanan, mulai dari baris 5 mirip dengan apa yang baru saja kita lihat: kita memiliki
"tambah" op ("pp_add" juga di pp_panas.c) tambahkan bersama dua "gvsv".
Sekarang, tentang apa ini?
1 LISTOP (0x8179888) cuti
2 OP (0x81798b0) masuk
3 COP (0x8179850) selanjutnya
"masuk" dan "keluar" adalah operasi pelingkupan, dan tugas mereka adalah melakukan pembersihan apa pun setiap
waktu Anda masuk dan keluar blok: variabel leksikal dirapikan, variabel tidak direferensikan
dimusnahkan, dan lain-lain. Setiap program akan memiliki tiga baris pertama: "keluar" adalah a
list, dan anak-anaknya adalah semua pernyataan di blok. Pernyataan dibatasi oleh
"nextstate", jadi blok adalah kumpulan operasi "nextstate", dengan operasi yang akan dilakukan
untuk setiap pernyataan menjadi anak-anak dari "nextstate". "masuk" adalah operasi tunggal yang
berfungsi sebagai penanda.
Begitulah cara Perl menguraikan program, dari atas ke bawah:
program
|
Pernyataan
|
=
/\
/\
$a+
/\
$b $c
Namun, tidak mungkin untuk melakukan operasi dalam urutan ini: Anda harus menemukan
nilai $b dan $c sebelum Anda menambahkannya, misalnya. Jadi, utas lainnya yang
berjalan melalui pohon op adalah urutan eksekusi: setiap op memiliki bidang "op_next" yang
menunjuk ke operasi berikutnya yang akan dijalankan, jadi mengikuti petunjuk ini memberi tahu kita bagaimana Perl dijalankan
Kode. Kita dapat melintasi pohon dalam urutan ini menggunakan opsi "exec" ke "B::Terse":
% perl -MO=Terse,exec -e '$a=$b+$c'
1 OP (0x8179928) masuk
2 COP (0x81798c8) selanjutnya
3 SVOP (0x81796c8) gvsv GV (0x80fa4d4) *b
4 SVOP (0x8179798) gvsv GV (0x80efeb0) *c
5 BINOP (0x8179878) tambahkan [1]
6 SVOP (0x816dd38) gvsv GV (0x80fa468) *a
7 penetapan BINOP (0x81798a0).
8 LISTOP (0x8179900) cuti
Ini mungkin lebih masuk akal bagi manusia: masukkan blok, mulai pernyataan. Ambil
nilai $b dan $c, dan menambahkan mereka bersama-sama. Temukan $a, dan tetapkan satu ke yang lain. Kemudian
meninggalkan.
Cara Perl membangun pohon op ini dalam proses penguraian dapat diuraikan oleh
memeriksa perly.y, tata bahasa YACC. Mari kita ambil bagian yang kita butuhkan untuk membangun pohon
untuk "$a = $b + $c"
1 istilah : istilah ASSIGNOP istilah
2 { $$ = ASSIGNOP baru(OPf_STACKED, $1, $2, $3); }
3 | istilah istilah ADDOP
4 { $$ = newBINOP($2, 0, skalar($1), skalar($3)); }
Jika Anda tidak terbiasa membaca tata bahasa BNF, beginilah cara kerjanya: Anda pasti
hal-hal oleh tokeniser, yang umumnya berakhir dengan huruf besar. Di sini, "ADDOP", disediakan
ketika tokeniser melihat "+" dalam kode Anda. "ASSIGNOP" disediakan ketika "=" digunakan untuk
menugaskan. Ini adalah "simbol terminal", karena Anda tidak bisa lebih sederhana dari mereka.
Tata bahasa, baris satu dan tiga dari cuplikan di atas, memberi tahu Anda cara membangun lebih banyak
bentuk yang kompleks. Bentuk kompleks ini, "simbol non-terminal" umumnya ditempatkan di bawah
kasus. "istilah" di sini adalah simbol non-terminal, yang mewakili ekspresi tunggal.
Tata bahasa memberi Anda aturan berikut: Anda dapat membuat benda di sebelah kiri titik dua
jika Anda melihat semua hal di sebelah kanan secara berurutan. Ini disebut "pengurangan", dan
tujuan parsing adalah untuk sepenuhnya mengurangi input. Ada beberapa cara berbeda yang bisa Anda lakukan
melakukan pengurangan, dipisahkan oleh garis vertikal: jadi, "istilah" diikuti oleh "=" diikuti oleh
"term" menjadi "term", dan "term" diikuti dengan "+" diikuti dengan "term" juga dapat membuat a
"ketentuan".
Jadi, jika Anda melihat dua istilah dengan "=" atau "+", di antaranya, Anda dapat mengubahnya menjadi satu
ekspresi. Ketika Anda melakukan ini, Anda mengeksekusi kode di blok pada baris berikutnya: jika Anda
lihat "=", Anda akan melakukan kode pada baris 2. Jika Anda melihat "+", Anda akan melakukan kode pada baris 4. Ini
kode ini yang berkontribusi pada pohon op.
| istilah istilah ADDOP
{ $$ = newBINOP($2, 0, skalar($1), skalar($3)); }
Apa yang dilakukan adalah membuat operasi biner baru, dan memberinya sejumlah variabel. Itu
variabel merujuk ke token: $1 adalah token pertama di input, $2 yang kedua, dan seterusnya
on - pikirkan referensi balik ekspresi reguler. $$ adalah op yang dikembalikan dari pengurangan ini.
Jadi, kami memanggil "newBINOP" untuk membuat operator biner baru. Parameter pertama untuk "newBINOP",
sebuah fungsi dalam op.c, adalah tipe operasi. Ini adalah operator tambahan, jadi kami ingin tipenya menjadi
"TAMBAHKAN". Kita bisa menentukan ini secara langsung, tapi itu ada di sana sebagai token kedua di
masukan, jadi kami menggunakan $2. Parameter kedua adalah flag op: 0 berarti "tidak ada yang istimewa".
Kemudian hal-hal yang ditambahkan: sisi kiri dan kanan ekspresi kita, dalam konteks skalar.
STACK
Ketika Perl mengeksekusi sesuatu seperti "addop", bagaimana cara meneruskan hasilnya ke operasi berikutnya?
Jawabannya adalah, melalui penggunaan tumpukan. Perl memiliki sejumlah tumpukan untuk menyimpan barang-barangnya
sedang dikerjakan, dan kita akan melihat tiga yang paling penting di sini.
Argumen tumpukan
Argumen diteruskan ke kode PP dan dikembalikan dari kode PP menggunakan tumpukan argumen, "ST".
Cara khas untuk menangani argumen adalah dengan mengeluarkannya dari tumpukan, menanganinya bagaimana Anda
inginkan, lalu dorong hasilnya kembali ke tumpukan. Ini adalah bagaimana, misalnya, kosinus
operator bekerja:
nilai NV;
nilai = POPn;
nilai = Perl_cos(nilai);
XPUSHn(nilai);
Kita akan melihat contoh yang lebih rumit dari ini ketika kita mempertimbangkan makro Perl di bawah ini. "POPn" memberi
Anda NV (nilai floating point) dari SV teratas di tumpukan: $x di "cos($x)". Lalu kita
menghitung kosinus, dan mendorong hasilnya kembali sebagai NV. "X" dalam "XPUSHn" berarti bahwa
tumpukan harus diperpanjang jika perlu - itu tidak perlu di sini, karena kami tahu
ada ruang untuk satu item lagi di tumpukan, karena kami baru saja menghapus satu! "XPUSH*"
makro setidaknya menjamin keamanan.
Atau, Anda dapat mengutak-atik tumpukan secara langsung: "SP" memberi Anda elemen pertama di
bagian Anda dari tumpukan, dan "TOP*" memberi Anda SV/IV/NV/dll teratas. di tumpukan. Jadi,
misalnya, untuk melakukan negasi unary dari bilangan bulat:
SETi(-TOPi);
Cukup atur nilai integer dari entri tumpukan teratas ke negasinya.
Manipulasi tumpukan argumen di inti persis sama dengan di XSUB - lihat
perlxstut, perlxs, dan perlguts untuk deskripsi makro yang lebih panjang yang digunakan dalam tumpukan
manipulasi.
Mark tumpukan
Saya mengatakan "bagian Anda dari tumpukan" di atas karena kode PP belum tentu mendapatkan keseluruhannya
tumpukan ke dirinya sendiri: jika fungsi Anda memanggil fungsi lain, Anda hanya ingin mengekspos
argumen yang ditujukan untuk fungsi yang dipanggil, dan tidak (harus) membiarkannya terjadi sendiri
data. Cara kami melakukan ini adalah dengan memiliki tumpukan terbawah "virtual", yang diekspos ke masing-masing
fungsi. Tumpukan tanda menyimpan bookmark ke lokasi di tumpukan argumen yang dapat digunakan oleh masing-masing
fungsi. Misalnya, ketika berhadapan dengan variabel terikat, (secara internal, sesuatu dengan "P"
magic) Perl harus memanggil metode untuk mengakses variabel terikat. Namun, kita perlu
pisahkan argumen yang diekspos ke metode dengan argumen yang diekspos ke aslinya
fungsi - menyimpan atau mengambil atau apa pun itu. Begini kira-kira cara "push" yang diikat
diimplementasikan; lihat "av_push" di av.c:
1 TANDA PUSH (SP);
2 MEMPERPANJANG(SP,2);
3 PUSH(SvTIED_obj((SV*)av, mg));
4 PUSH (val);
5 KEMBALI;
6 MASUKKAN;
7 call_method("PUSH", G_SCALAR|G_DISCARD);
8 TINGGALKAN;
Mari kita periksa seluruh implementasi, untuk latihan:
1 TANDA PUSH (SP);
Dorong status penunjuk tumpukan saat ini ke tumpukan tanda. Ini agar ketika
kami telah selesai menambahkan item ke tumpukan argumen, Perl tahu berapa banyak hal yang telah kami tambahkan
baru-baru ini.
2 MEMPERPANJANG(SP,2);
3 PUSH(SvTIED_obj((SV*)av, mg));
4 PUSH (val);
Kami akan menambahkan dua item lagi ke tumpukan argumen: ketika Anda memiliki array terikat, the
Subrutin "PUSH" menerima objek dan nilai yang akan didorong, dan itulah tepatnya
kita miliki di sini - objek terikat, diambil dengan "SvTIED_obj", dan nilainya, SV "val".
5 KEMBALI;
Selanjutnya kami memberi tahu Perl untuk memperbarui penunjuk tumpukan global dari variabel internal kami: "dSP"
hanya memberi kami salinan lokal, bukan referensi ke global.
6 MASUKKAN;
7 call_method("PUSH", G_SCALAR|G_DISCARD);
8 TINGGALKAN;
"ENTER" dan "LEAVE" melokalkan blok kode - mereka memastikan bahwa semua variabel
dirapikan, semua yang telah dilokalkan akan dikembalikan nilainya sebelumnya, dan seterusnya.
Pikirkan mereka sebagai "{" dan "}" dari blok Perl.
Untuk benar-benar melakukan pemanggilan metode ajaib, kita harus memanggil subrutin di ruang Perl:
"call_method" menangani itu, dan itu dijelaskan dalam perlcall. Kami menyebutnya "PUSH"
metode dalam konteks skalar, dan kita akan membuang nilai kembaliannya. Itu panggilan_metode()
fungsi menghapus elemen teratas dari tumpukan tanda, jadi tidak ada yang perlu dilakukan oleh penelepon
membersihkan.
Simpan tumpukan
C tidak memiliki konsep lingkup lokal, jadi Perl menyediakannya. Kami telah melihat bahwa "ENTER" dan
"LEAVE" digunakan sebagai scoping braces; save stack mengimplementasikan C yang setara dengan, for
contoh:
{
lokal $foo = 42;
...
}
Lihat "Melokalisasi perubahan" di perlguts untuk mengetahui cara menggunakan simpanan tumpukan.
JUTAAN OF MAKROS
Satu hal yang akan Anda perhatikan tentang sumber Perl adalah bahwa itu penuh dengan makro. Punya beberapa
menyebut penggunaan makro yang meluas sebagai hal yang paling sulit untuk dipahami, yang lain menganggapnya menambah
kejelasan. Mari kita ambil contoh, kode yang mengimplementasikan operator penjumlahan:
1 PP(pp_tambahkan)
2 {
3 dSP; TARGET; tryAMAGICbin(tambahkan,opASSIGN);
4 {
5 dPOPTOPnnrl_ul;
6 SETn( kiri + kanan );
7 KEMBALI;
8}
9}
Setiap baris di sini (terlepas dari kurung kurawal, tentu saja) berisi makro. Set baris pertama
up deklarasi fungsi seperti yang diharapkan Perl untuk kode PP; baris 3 mengatur variabel
deklarasi untuk tumpukan argumen dan target, nilai kembalian operasi.
Akhirnya, ia mencoba untuk melihat apakah operasi penambahan kelebihan beban; jika demikian, yang sesuai
subrutin disebut.
Baris 5 adalah deklarasi variabel lain - semua deklarasi variabel dimulai dengan "d" - yang
muncul dari atas argumen menumpuk dua NV (karenanya "nn") dan memasukkannya ke dalam
variabel "kanan" dan "kiri", maka "rl". Ini adalah dua operan untuk penambahan
operator. Selanjutnya, kita panggil "SETn" untuk mengatur NV dari nilai kembalian ke hasil penjumlahan
kedua nilai tersebut. Ini selesai, kami kembali - makro "KEMBALI" memastikan bahwa nilai pengembalian kami
ditangani dengan benar, dan kami melewati operator berikutnya untuk menjalankan kembali ke putaran utama.
Sebagian besar makro ini dijelaskan dalam perlapi, dan beberapa yang lebih penting adalah
dijelaskan dalam perlxs juga. Berikan perhatian khusus pada "Latar Belakang dan
PERL_IMPLICIT_CONTEXT" dalam perlguts untuk informasi tentang makro "[pad]THX_?".
LEBIH LANJUT BACAAN
Untuk informasi lebih lanjut tentang internal Perl, silakan lihat dokumen yang tercantum di "Internals
dan Antarmuka Bahasa C" di perl.
Gunakan perlinterp online menggunakan layanan onworks.net