Tiếng AnhTiếng PhápTiếng Tây Ban Nha

Biểu tượng yêu thích OnWorks

perlinterp - Trực tuyến trên đám mây

Chạy perlinterp trong nhà cung cấp dịch vụ lưu trữ miễn phí OnWorks trên Ubuntu Online, Fedora Online, trình mô phỏng trực tuyến Windows hoặc trình mô phỏng trực tuyến MAC OS

Đây là lệnh perlinterp có thể chạy trong nhà cung cấp dịch vụ lưu trữ miễn phí OnWorks bằng cách sử dụng một trong nhiều máy trạm trực tuyến miễn phí của chúng tôi như Ubuntu Online, Fedora Online, trình mô phỏng trực tuyến Windows hoặc trình mô phỏng trực tuyến MAC OS

CHƯƠNG TRÌNH:

TÊN


perlinterp - Tổng quan về trình thông dịch Perl

MÔ TẢ


Tài liệu này cung cấp cái nhìn tổng quan về cách hoạt động của trình thông dịch Perl ở cấp độ C
mã, cùng với các con trỏ tới các tệp mã nguồn C có liên quan.

Yếu tố OF CÁC THÔNG DỊCH VIÊN


Công việc của trình thông dịch có hai giai đoạn chính: biên dịch mã thành phần bên trong.
biểu diễn hoặc mã byte và sau đó thực thi nó. "Mã biên dịch" trong perlguts giải thích
chính xác giai đoạn biên dịch diễn ra như thế nào.

Đây là bản phân tích ngắn gọn về hoạt động của Perl:

Khởi động
Hành động bắt đầu trong perlmain.c. (hoặc miniperlmain.c đối với miniperl) Đây là cấp độ rất cao
mã, đủ để vừa trên một màn hình và nó giống với mã được tìm thấy trong perlembed; hầu hết
của hành động thực tế diễn ra trong perl.c

perlmain.c được tạo bởi "ExtUtils::Miniperl" từ miniperlmain.c vào lúc rảnh rỗi, vì vậy bạn
nên tạo Perl để làm theo điều này.

Đầu tiên, perlmain.c phân bổ một số bộ nhớ và xây dựng một trình thông dịch Perl, dọc theo đó
dòng:

1 PERL_SYS_INIT3(&argc,&argv,&env);
2
3 nếu (!PL_do_undump) {
4 my_perl = Perl_alloc();
5 nếu (!my_perl)
6 ra(1);
7 perl_construct(my_perl);
8 PL_perl_dest_level = 0;
9}

Dòng 1 là macro và định nghĩa của nó phụ thuộc vào hệ điều hành của bạn. Dòng 3
tham chiếu "PL_do_undump", một biến toàn cục - tất cả các biến toàn cục trong Perl đều bắt đầu bằng
"PL_". Điều này cho bạn biết liệu chương trình đang chạy có được tạo bằng cờ "-u" hay không
sang perl và sau đó không hoàn thành, có nghĩa là nó sẽ sai trong bất kỳ bối cảnh lành mạnh nào.

Dòng 4 gọi một hàm trong perl.c để cấp phát bộ nhớ cho trình thông dịch Perl. Nó khá là
chức năng đơn giản, và cốt lõi của nó trông như thế này:

my_perl = (PerlInterpreter*)PerlMem_malloc(sizeof(PerlInterpreter));

Ở đây bạn thấy một ví dụ về sự trừu tượng hóa hệ thống của Perl mà chúng ta sẽ thấy sau:
"PerlMem_malloc" là "malloc" của hệ thống của bạn hoặc "malloc" của Perl như được định nghĩa trong
malloc.c nếu bạn đã chọn tùy chọn đó tại thời điểm định cấu hình.

Tiếp theo, ở dòng 7, chúng ta xây dựng trình thông dịch bằng perl_construct, cũng trong perl.c; điều này
thiết lập tất cả các biến đặc biệt mà Perl cần, các ngăn xếp, v.v.

Bây giờ chúng ta chuyển cho Perl các tùy chọn dòng lệnh và yêu cầu nó thực hiện:

exitstatus = perl_parse(my_perl, xs_init, argc, argv, (char **)NULL);
nếu (! trạng thái thoát)
perl_run(my_perl);

trạng thái thoát = perl_destroy(my_perl);

perl_free(my_perl);

"Perl_parse" thực sự là một trình bao bọc xung quanh "S_parse_body", như được định nghĩa trong perl.c, Mà
xử lý các tùy chọn dòng lệnh, thiết lập mọi mô-đun XS được liên kết tĩnh, mở
chương trình và gọi "yyparse" để phân tích nó.

Phân tích cú pháp
Mục đích của giai đoạn này là lấy nguồn Perl và biến nó thành cây op. Chúng ta sẽ thấy
sau này một trong số đó sẽ trông như thế nào. Nói đúng ra, có ba điều đang diễn ra ở đây.

"yyparse", trình phân tích cú pháp, tồn tại trong perly.c, mặc dù tốt hơn hết bạn nên đọc bản gốc
Đầu vào YACC vào perly.y. (Vâng, Virginia, đó is một ngữ pháp YACC cho Perl!) Công việc của
trình phân tích cú pháp là lấy mã của bạn và "hiểu" nó, chia nó thành các câu, quyết định
toán hạng nào đi với toán tử nào, v.v.

Trình phân tích cú pháp được hỗ trợ đắc lực bởi từ vựng, nó sẽ chia thông tin đầu vào của bạn thành mã thông báo và
quyết định mỗi mã thông báo là loại gì: tên biến, toán tử, từ trần,
chương trình con, một hàm cốt lõi, v.v. Điểm chính của mục nhập từ vựng là "yylex",
và điều đó cũng như các thói quen liên quan của nó có thể được tìm thấy trong toke.c. Perl không giống những thứ khác
ngôn ngữ máy tính; đôi khi nó rất nhạy cảm với ngữ cảnh nên có thể khó giải quyết
loại mã thông báo nào đó là gì hoặc mã thông báo kết thúc ở đâu. Như vậy, có rất nhiều
tương tác giữa trình mã thông báo và trình phân tích cú pháp, điều này có thể trở nên khá đáng sợ nếu bạn
không quen với nó.

Khi bộ phân tích cú pháp hiểu được một chương trình Perl, nó sẽ xây dựng một cây các thao tác cho
thông dịch viên để thực hiện trong quá trình thực thi. Các thói quen xây dựng và liên kết với nhau
các hoạt động khác nhau sẽ được tìm thấy trong op.c, và sẽ được kiểm tra sau.

Tối ưu hóa
Bây giờ giai đoạn phân tích cú pháp đã hoàn tất và cây đã hoàn thành thể hiện các thao tác
trình thông dịch Perl cần thực hiện để thực thi chương trình của chúng ta. Tiếp theo, Perl thực hiện chạy thử
trên cây để tìm kiếm sự tối ưu: các biểu thức không đổi như "3 + 4" sẽ được
được tính toán ngay bây giờ và trình tối ưu hóa cũng sẽ xem liệu có thể thay thế nhiều thao tác nào không
với một cái duy nhất. Ví dụ: để tìm nạp biến $foo, thay vì lấy toàn cầu
*foo và nhìn vào thành phần vô hướng, trình tối ưu hóa sẽ điều khiển cây op để sử dụng một
hàm tra cứu trực tiếp đại lượng vô hướng được đề cập. Trình tối ưu hóa chính là "nhìn trộm" trong
op.cvà nhiều hoạt động có chức năng tối ưu hóa riêng.

Chạy
Bây giờ chúng ta cuối cùng đã sẵn sàng: chúng ta đã biên dịch mã byte Perl và tất cả những gì còn lại phải làm
được chạy nó. Việc thực thi thực tế được thực hiện bởi hàm "runops_standard" trong chạy.c; hơn
cụ thể là nó được thực hiện bởi ba dòng trông có vẻ ngây thơ sau:

while ((PL_op = PL_op->op_ppaddr(aTHX))) {
PERL_ASYNC_CHECK();
}

Bạn có thể cảm thấy thoải mái hơn với phiên bản Perl đó:

PERL_ASYNC_CHECK() trong khi $Perl::op = &{$Perl::op->{function}};

Vâng, có lẽ là không. Dù sao đi nữa, mỗi op đều chứa một con trỏ hàm, nó quy định
chức năng sẽ thực sự thực hiện hoạt động. Hàm này sẽ trả về lần tiếp theo
op theo trình tự - điều này cho phép những thứ như "if" chọn op tiếp theo một cách linh hoạt
trong thời gian chạy. "PERL_ASYNC_CHECK" đảm bảo rằng những thứ như tín hiệu bị gián đoạn
thực hiện nếu được yêu cầu.

Các hàm thực tế được gọi được gọi là mã PP và chúng nằm giữa bốn tệp:
pp_hot.c chứa mã "nóng", thường được sử dụng nhiều nhất và được tối ưu hóa cao, pp_sys.c
chứa tất cả các chức năng dành riêng cho hệ thống, pp_ctl.c chứa các chức năng
thực hiện các cấu trúc điều khiển ("if", "while" và tương tự) và pp.c chứa mọi thứ
khác. Nếu bạn muốn, đây là mã C cho các hàm và toán tử có sẵn của Perl.

Lưu ý rằng mỗi hàm "pp_" dự kiến ​​sẽ trả về một con trỏ tới op tiếp theo. Cuộc gọi đến
Perl subs (và các khối eval) được xử lý trong cùng một vòng lặp runops và không tiêu tốn
thêm không gian trên ngăn xếp C. Ví dụ: "pp_entersub" và "pp_entertry" chỉ cần nhấn một
Cấu trúc khối "CxSUB" hoặc "CxEVAL" trên ngăn xếp ngữ cảnh chứa địa chỉ của
op theo sau cuộc gọi phụ hoặc eval. Sau đó, họ trả về op đầu tiên của phụ hoặc eval đó
khối, và do đó việc thực thi khối con hoặc khối đó vẫn tiếp tục. Sau đó, "pp_leavesub" hoặc
"pp_leavetry" op bật "CxSUB" hoặc "CxEVAL", truy xuất op trả về từ nó và
trả lại nó.

Ngoại lệ bàn giao
Việc xử lý ngoại lệ của Perl (tức là "chết", v.v.) được xây dựng dựa trên cấp độ thấp
Các hàm thư viện C "setjmp()"/"longjmp()". Về cơ bản, chúng cung cấp một cách để nắm bắt
các thanh ghi PC và SP hiện tại và sau đó khôi phục chúng; tức là "longjmp()" tiếp tục ở
điểm trong mã nơi "setjmp()" trước đó đã được thực hiện, với bất kỳ điều gì tiếp theo trên C
ngăn xếp bị mất. Đây là lý do tại sao mã phải luôn lưu giá trị bằng cách sử dụng "SAVE_FOO" thay vì
trong các biến tự động.

Lõi Perl bao bọc "setjmp()" v.v. trong macro "JMPENV_PUSH" và "JMPENV_JUMP". Các
quy tắc cơ bản của các ngoại lệ Perl là "thoát" và "chết" (trong trường hợp không có "eval") thực hiện
a JMPENV_JUMP(2), trong khi "die" trong "eval" thực hiện JMPENV_JUMP(3).

Tại các điểm vào perl, chẳng hạn như "perl_parse()", "perl_run()" và "call_sv(cv, G_EVAL)"
mỗi người thực hiện một "JMPENV_PUSH", sau đó nhập một vòng lặp runops hoặc bất cứ điều gì và có thể xử lý
ngoại lệ trả về. Đối với trả về 2, việc dọn dẹp cuối cùng được thực hiện, chẳng hạn như bật ngăn xếp và
gọi khối "KIỂM TRA" hoặc "KẾT THÚC". Trong số những thứ khác, đây là cách dọn dẹp phạm vi vẫn
xảy ra trong quá trình "thoát".

Nếu một "die" có thể tìm thấy khối "CxEVAL" trên ngăn xếp ngữ cảnh thì ngăn xếp đó sẽ được đưa vào
mức đó và hoạt động quay trở lại trong khối đó được gán cho "PL_restartop"; sau đó một
JMPENV_JUMP(3) được thực hiện. Điều này thường chuyển quyền kiểm soát trở lại cho người bảo vệ. Trong trường hợp
của "Perl_run" và "call_sv", "PL_restartop" khác null sẽ kích hoạt việc truy cập lại vào runops
vòng. Đây là cách thông thường để xử lý "die" hoặc "croak" trong "eval".

Đôi khi các hoạt động được thực thi trong vòng lặp runops bên trong, chẳng hạn như buộc, sắp xếp hoặc quá tải
mã số. Trong trường hợp này, một cái gì đó như

sub FETCH { eval { die } }

sẽ gây ra một longjmp ngay cho người bảo vệ trong "Perl_run", bật cả hai vòng lặp runops,
điều đó rõ ràng là không chính xác. Một cách để tránh điều này là mã buộc phải thực hiện một
"JMPENV_PUSH" trước khi thực hiện "FETCH" trong vòng lặp runops bên trong, nhưng để đạt hiệu quả
lý do, thực tế Perl chỉ đặt cờ bằng cách sử dụng "CATCH_SET(TRUE)". "pp_require",
Các hoạt động "pp_entereval" và "pp_entertry" sẽ kiểm tra cờ này và nếu đúng, họ gọi "docatch",
thực hiện "JMPENV_PUSH" và bắt đầu cấp độ runops mới để thực thi mã, thay vì
thực hiện nó trên vòng lặp hiện tại.

Để tối ưu hóa hơn nữa, khi thoát khỏi khối eval trong "FETCH", việc thực thi
mã theo sau khối vẫn được tiếp tục ở vòng lặp bên trong. Khi có một ngoại lệ
được nâng lên, "docatch" so sánh mức "JMPENV" của "CxEVAL" với "PL_top_env" và nếu
chúng khác nhau, chỉ cần ném lại ngoại lệ. Bằng cách này, bất kỳ vòng lặp bên trong nào cũng sẽ được bật ra.

Đây là một ví dụ.

1: eval { tie @a, 'A' };
2: phụ A::TIEARRAY {
3: đánh giá { die };
4: chết;
5: }

Để chạy mã này, "perl_run" được gọi, mã này thực hiện "JMPENV_PUSH" rồi nhập runops
vòng. Vòng lặp này thực hiện các hoạt động eval và tie trên dòng 1, với eval đẩy "CxEVAL"
vào ngăn xếp bối cảnh.

"pp_tie" thực hiện "CATCH_SET(TRUE)", sau đó bắt đầu vòng lặp runops thứ hai để thực thi
nội dung của "TIEARRAY". Khi nó thực thi lệnh nhập trên dòng 3, "CATCH_GET" là đúng, vì vậy
"pp_entertry" gọi "docatch" thực hiện "JMPENV_PUSH" và bắt đầu vòng lặp runops thứ ba,
sau đó thực hiện die op. Tại thời điểm này, ngăn xếp cuộc gọi C trông như thế này:

Perl_pp_die
Perl_runops # vòng lặp thứ ba
S_docatch_body
S_docatch
Perl_pp_entertry
Perl_runops # vòng lặp thứ hai
S_call_body
Perl_call_sv
Perl_pp_tie
Perl_runops # vòng lặp đầu tiên
S_run_body
perl_run
chính

và các ngăn xếp ngữ cảnh và dữ liệu, như được hiển thị bởi "-Dstv", trông giống như:

Ngăn xếp 0: CHÍNH
CX 0: KHỐI =>
CX 1: EVAL => AV() PV("A"\0)
retop=rời đi
CỘNG ĐỒNG 1: PHÉP THUẬT
CX 0: PHỤ =>
retop=(null)
CX 1: EVAL => *
retop=trạng thái tiếp theo

Xúc xắc bật "CxEVAL" đầu tiên ra khỏi ngăn xếp ngữ cảnh, đặt "PL_restartop" từ nó, thực hiện một
JMPENV_JUMP(3) và điều khiển quay trở lại "docatch" trên cùng. Điều này sau đó bắt đầu một phần ba khác-
cấp độ runops cấp độ, thực thi các hoạt động trạng thái tiếp theo, dấu đẩy và điểm chết trên dòng 4. Tại
điểm mà "pp_die" thứ hai được gọi, ngăn xếp lệnh gọi C trông giống hệt như ở trên,
mặc dù chúng ta không còn ở trong sự đánh giá bên trong nữa; điều này là do sự tối ưu hóa
được đề cập trước đó. Tuy nhiên, ngăn xếp bối cảnh bây giờ trông như thế này, tức là với CxEVAL hàng đầu
bật lên:

Ngăn xếp 0: CHÍNH
CX 0: KHỐI =>
CX 1: EVAL => AV() PV("A"\0)
retop=rời đi
CỘNG ĐỒNG 1: PHÉP THUẬT
CX 0: PHỤ =>
retop=(null)

Xúc xắc ở dòng 4 sẽ đưa ngăn xếp bối cảnh trở lại CxEVAL, để nguyên như sau:

Ngăn xếp 0: CHÍNH
CX 0: KHỐI =>

Như thường lệ, "PL_restartop" được trích xuất từ ​​"CxEVAL" và JMPENV_JUMP(3) xong, cái nào
đưa ngăn xếp C trở lại docatch:

S_docatch
Perl_pp_entertry
Perl_runops # vòng lặp thứ hai
S_call_body
Perl_call_sv
Perl_pp_tie
Perl_runops # vòng lặp đầu tiên
S_run_body
perl_run
chính

Trong trường hợp này, do mức "JMPENV" được ghi trong "CxEVAL" khác với mức
cái hiện tại, "docach" chỉ thực hiện một JMPENV_JUMP(3) và ngăn xếp C thư giãn thành:

perl_run
chính

Bởi vì "PL_restartop" không phải là null nên "run_body" sẽ bắt đầu một vòng lặp runops mới và thực thi
tiếp tục.

NỘI BỘ BIẾN ĐỔI LOẠI
Bây giờ chắc hẳn bạn đã xem xét perlguts, nó cho bạn biết về cấu trúc bên trong của Perl.
các loại biến: SV, HV, AV và phần còn lại. Nếu chưa, hãy làm điều đó ngay bây giờ.

Các biến này không chỉ được sử dụng để biểu diễn các biến trong không gian Perl mà còn bất kỳ
các hằng số trong mã, cũng như một số cấu trúc hoàn toàn bên trong Perl. Biểu tượng
table, chẳng hạn, là một hàm băm Perl thông thường. Mã của bạn được biểu thị bằng SV vì nó
đọc vào trình phân tích cú pháp; bất kỳ tệp chương trình nào bạn gọi đều được mở thông qua các thẻ xử lý tệp Perl thông thường,
và như vậy.

Mô-đun Devel::Peek cốt lõi cho phép chúng tôi kiểm tra SV từ chương trình Perl. Hãy xem, vì
ví dụ, cách Perl xử lý hằng số "xin chào".

% perl -MDevel::Peek -e 'Dump("xin chào")'
1 SV = PV(0xa041450) tại 0xa04ecbc
2 REFCNT = 1
3 CỜ = (POK,CHỈ ĐỌC,pPOK)
4 PV = 0xa0484e0 "xin chào"\0
5 CUR = 5
6 LEN = 6

Việc đọc kết quả "Devel::Peek" cần một chút luyện tập, vì vậy chúng ta hãy đọc từng dòng một.

Dòng 1 cho chúng ta biết chúng ta đang xem một SV có địa chỉ 0xa04ecbc trong bộ nhớ. Bản thân SV
là những cấu trúc rất đơn giản nhưng chúng chứa con trỏ tới một cấu trúc phức tạp hơn. TRONG
trong trường hợp này, đó là PV, một cấu trúc chứa giá trị chuỗi, tại vị trí 0xa041450. Đường kẻ
2 là số tham chiếu; không có tài liệu tham khảo nào khác cho dữ liệu này, vì vậy nó là 1.

Dòng 3 là cờ cho SV này - bạn có thể sử dụng nó làm PV, đó là SV chỉ đọc (vì
nó là một hằng số) và dữ liệu là một PV nội bộ. Tiếp theo chúng ta có nội dung của
chuỗi, bắt đầu từ vị trí 0xa0484e0.

Dòng 5 cho chúng ta độ dài hiện tại của chuỗi - lưu ý rằng điều này có không bao gồm các
bộ kết thúc null. Dòng 6 không phải là độ dài của chuỗi mà là độ dài của chuỗi hiện tại
bộ đệm được phân bổ; khi chuỗi tăng lên, Perl sẽ tự động mở rộng dung lượng lưu trữ sẵn có
thông qua một quy trình có tên là "SvGROW".

Bạn có thể nhận được bất kỳ đại lượng nào từ C một cách rất dễ dàng; chỉ cần thêm "Sv" vào tên của
trường được hiển thị trong đoạn mã và bạn có macro sẽ trả về giá trị:
"SvCUR(sv)" trả về độ dài hiện tại của chuỗi, "SvREFCOUNT(sv)" trả về độ dài hiện tại của chuỗi
số tham chiếu, "SvPV(sv, len)" trả về chính chuỗi đó cùng với độ dài của nó, v.v.
Bạn có thể tìm thấy nhiều macro hơn để thao tác các thuộc tính này trong perlguts.

Hãy lấy một ví dụ về thao tác PV, từ "sv_catpvn", trong sv.c

1 khoảng trống
2 Perl_sv_catpvn(pTHX_ SV *sv, const char *ptr, STRLEN len)
3 {
4 STRLEN;
5 char *rác;

6 rác = SvPV_force(sv, tlen);
7 SvGROW(sv, tlen + len + 1);
8 nếu (ptr == rác)
9 ptr = SvPVX(sv);
10 Di chuyển(ptr,SvPVX(sv)+tlen,len,char);
11 SvCUR(sv) += len;
12 *SvEND(sv) = '\0';
13 (void)SvPOK_only_UTF8(sv); /* xác thực con trỏ */
14 SvTAINT(sv);
15}

Đây là hàm thêm một chuỗi "ptr" có độ dài "len" vào cuối PV
được lưu trữ trong "sv". Điều đầu tiên chúng ta làm ở dòng 6 là đảm bảo rằng SV PV hợp lệ,
bằng cách gọi macro "SvPV_force" để buộc PV. Là một tác dụng phụ, "tlen" được đặt thành
giá trị hiện tại của PV và bản thân PV được trả về "rác".

Ở dòng 7, chúng ta đảm bảo rằng SV sẽ có đủ chỗ để chứa chuỗi cũ,
chuỗi mới và dấu kết thúc null. Nếu "LEN" không đủ lớn, "SvGROW" sẽ
phân bổ lại không gian cho chúng tôi.

Bây giờ, nếu "rác" giống với chuỗi chúng ta đang cố thêm vào, chúng ta có thể lấy chuỗi đó
trực tiếp từ SV; "SvPVX" là địa chỉ của PV trong SV.

Dòng 10 thực hiện việc ghép nối thực tế: macro "Di chuyển" di chuyển một đoạn bộ nhớ xung quanh: chúng tôi
di chuyển chuỗi "ptr" đến cuối PV - đó là phần bắt đầu của PV cộng với hiện tại của nó
chiều dài. Chúng tôi đang di chuyển byte "len" thuộc loại "char". Sau khi làm như vậy, chúng ta cần nói với Perl
chúng tôi đã mở rộng chuỗi bằng cách thay đổi "CUR" để phản ánh độ dài mới. "SvEND" là macro
cho chúng ta biết phần cuối của chuỗi, vì vậy chuỗi đó phải là "\0".

Dòng 13 thao túng cờ; vì chúng tôi đã thay đổi PV nên mọi giá trị IV hoặc NV sẽ không còn nữa
còn hợp lệ: nếu chúng ta có "$a=10; $a.="6";" chúng tôi không muốn sử dụng IV cũ của 10.
"SvPOK_only_utf8" là phiên bản đặc biệt nhận biết UTF-8 của "SvPOK_only", một macro chuyển
tắt cờ IOK và NOK và bật POK. "SvTAINT" cuối cùng là một macro dùng để rửa
dữ liệu bị nhiễm độc nếu chế độ bị nhiễm độc được bật.

AV và HV phức tạp hơn, nhưng SV cho đến nay là loại biến phổ biến nhất.
Ném xung quanh. Sau khi đã thấy điều gì đó về cách chúng ta thao tác những thứ này, hãy tiếp tục và xem xét
cây op được xây dựng như thế nào.

OP CÂY


Đầu tiên, cây op là gì? Cây op là sự thể hiện được phân tích cú pháp của bạn
chương trình, như chúng ta đã thấy trong phần phân tích cú pháp và chính chuỗi các thao tác đó
Perl tiến hành thực thi chương trình của bạn, như chúng ta đã thấy trong phần "Đang chạy".

Op là một thao tác cơ bản mà Perl có thể thực hiện: tất cả các hàm dựng sẵn và
các toán tử là các op và có một loạt các op liên quan đến các khái niệm mà trình thông dịch
nhu cầu nội bộ - nhập và rời khỏi một khối, kết thúc một câu lệnh, tìm nạp một biến,
và như vậy.

Cây op được kết nối theo hai cách: bạn có thể tưởng tượng rằng có hai "tuyến đường" xuyên qua
nó, có hai thứ tự mà bạn có thể duyệt cây. Đầu tiên, thứ tự phân tích phản ánh cách thức
trình phân tích cú pháp hiểu mã và thứ hai, thứ tự thực thi cho Perl biết thứ tự thực hiện
các hoạt động trong.

Cách dễ nhất để kiểm tra cây op là dừng Perl sau khi nó phân tích xong và
bắt nó vứt cây đi. Đây chính xác là những gì trình biên dịch phụ trợ B::Terse,
B::Súc tích và B::Debug làm.

Chúng ta hãy xem Perl thấy "$a = $b + $c" như thế nào:

% Perl -MO=Terse -e '$a=$b+$c'
1 LISTOP (0x8179888) rời khỏi
2 OP (0x81798b0) nhập
3 COP (0x8179850) trạng thái tiếp theo
4 BINOP (0x8179828) được chỉ định
5 BINOP (0x8179800) thêm [1]
6 UNOP (0x81796e0) null [15]
7 SVOP (0x80fafe0) gvsv GV (0x80fa4cc) *b
8 UNOP (0x81797e0) null [15]
9 SVOP (0x8179700) gvsv GV (0x80efeb0) *c
10 UNOP (0x816b4f0) null [15]
11 SVOP (0x816dcf0) gvsv GV (0x80fa460) *a

Hãy bắt đầu ở giữa, ở dòng 4. Đây là BINOP, toán tử nhị phân, nằm ở
vị trí 0x8179828. Toán tử cụ thể được đề cập là "ssign" - phép gán vô hướng -
và bạn có thể tìm thấy mã triển khai nó trong hàm "pp_ssign" trong pp_hot.c. Các
một toán tử nhị phân, nó có hai con: toán tử cộng, cung cấp kết quả của "$b+$c",
ở trên cùng ở dòng 5 và phía bên trái ở dòng 10.

Dòng 10 là lệnh rỗng: điều này hoàn toàn không có tác dụng gì. Nó đang làm gì ở đó vậy? Nếu bạn thấy
nếu không, đó là dấu hiệu cho thấy nội dung nào đó đã được tối ưu hóa sau khi phân tích cú pháp. Như chúng ta
được đề cập trong phần "Tối ưu hóa", giai đoạn tối ưu hóa đôi khi chuyển đổi hai hoạt động thành
một, ví dụ như khi tìm nạp một biến vô hướng. Khi điều này xảy ra, thay vì viết lại
cây op và dọn dẹp các con trỏ lơ lửng, việc thay thế nó sẽ dễ dàng hơn
hoạt động dự phòng với lệnh null. Ban đầu, cái cây sẽ trông như thế này:

10 SVOP (0x816b4f0) rv2sv [15]
11 SVOP (0x816dcf0) gv GV (0x80fa460) *a

Tức là tìm mục nhập "a" từ bảng ký hiệu chính và sau đó xem biểu thức vô hướng
thành phần của nó: "gvsv" ("pp_gvsv" vào pp_hot.c) xảy ra để thực hiện cả hai điều này.

Vế bên phải, bắt đầu từ dòng 5 tương tự như những gì chúng ta vừa thấy: chúng ta có
"thêm" op ("pp_add" cũng có trong pp_hot.c) cộng hai "gvsv" lại với nhau.

Bây giờ, chuyện này là sao?

1 LISTOP (0x8179888) rời khỏi
2 OP (0x81798b0) nhập
3 COP (0x8179850) trạng thái tiếp theo

"vào" và "rời" là các hoạt động xác định phạm vi và công việc của họ là thực hiện mọi công việc dọn phòng mỗi ngày.
thời điểm bạn vào và rời khỏi một khối: các biến từ vựng được sắp xếp gọn gàng, các biến không được tham chiếu
bị phá hủy, vân vân. Mỗi chương trình sẽ có ba dòng đầu tiên: "rời" là một
list và các phần tử con của nó là tất cả các câu lệnh trong khối. Các câu lệnh được giới hạn bởi
"nextstate", do đó, một khối là tập hợp các hoạt động "trạng thái tiếp theo", với các hoạt động được thực hiện
cho mỗi câu lệnh là con của "trạng thái tiếp theo". "enter" là một op duy nhất
có chức năng như một điểm đánh dấu.

Đó là cách Perl phân tích chương trình, từ trên xuống dưới:

chương trình
|
Tuyên bố
|
=
/ \
/ \
$a +
/ \
$ b $ c

Tuy nhiên, không thể thực hiện các thao tác theo thứ tự sau: bạn phải tìm
ví dụ: các giá trị của $b và $c trước khi bạn cộng chúng lại với nhau. Vì vậy, chủ đề khác
chạy qua cây op là thứ tự thực hiện: mỗi op có một trường "op_next"
trỏ tới op tiếp theo sẽ được chạy, vì vậy việc làm theo các con trỏ này sẽ cho chúng ta biết Perl thực thi như thế nào
mật mã. Chúng ta có thể duyệt cây theo thứ tự này bằng cách sử dụng tùy chọn "exec" thành "B::Terse":

% Perl -MO=Terse,exec -e '$a=$b+$c'
1 OP (0x8179928) nhập
2 COP (0x81798c8) trạng thái tiếp theo
3 SVOP (0x81796c8) gvsv GV (0x80fa4d4) *b
4 SVOP (0x8179798) gvsv GV (0x80efeb0) *c
5 BINOP (0x8179878) thêm [1]
6 SVOP (0x816dd38) gvsv GV (0x80fa468) *a
7 chỉ định BINOP (0x81798a0)
8 LISTOP (0x8179900) rời khỏi

Điều này có lẽ có ý nghĩa hơn đối với con người: nhập một khối, bắt đầu một câu lệnh. Nhận được
các giá trị của $b và $c, rồi cộng chúng lại với nhau. Tìm $a và gán cái này cho cái kia. Sau đó
rời khỏi.

Cách Perl xây dựng các cây op này trong quá trình phân tích cú pháp có thể được làm sáng tỏ bằng cách
kiểm tra perly.y, ngữ pháp YACC. Hãy lấy mảnh chúng ta cần để xây dựng cái cây
cho "$a = $b + $c"

1 học kỳ: học kỳ ASSIGNOP
2 { $$ = newASSIGNOP(OPf_STACKED, $1, $2, $3); }
3 | thời hạn THÊM thời hạn
4 { $$ = newBINOP($2, 0, vô hướng($1), vô hướng($3)); }

Nếu bạn không quen đọc ngữ pháp BNF thì đây là cách nó hoạt động: Bạn được cho ăn một số
mọi thứ bằng mã thông báo, thường kết thúc bằng chữ in hoa. Ở đây, "ADDOP", được cung cấp
khi mã thông báo nhìn thấy dấu "+" trong mã của bạn. "ASSIGNOP" được cung cấp khi "=" được sử dụng cho
phân công. Đây là những "ký hiệu đầu cuối", bởi vì bạn không thể đơn giản hơn chúng.

Ngữ pháp, dòng một và ba của đoạn trích trên, cho bạn biết cách xây dựng thêm
các hình thức phức tạp. Những dạng phức tạp này, "ký hiệu không kết thúc" thường được đặt ở vị trí thấp hơn
trường hợp. "thuật ngữ" ở đây là ký hiệu không kết thúc, đại diện cho một biểu thức duy nhất.

Ngữ pháp cung cấp cho bạn quy tắc sau: bạn có thể đặt điều ở bên trái dấu hai chấm
nếu bạn nhìn thấy tất cả những thứ bên phải theo thứ tự. Điều này được gọi là "giảm" và
Mục đích của việc phân tích cú pháp là giảm hoàn toàn đầu vào. Có nhiều cách khác nhau để bạn có thể
thực hiện phép rút gọn, được phân tách bằng các thanh dọc: vì vậy, "thuật ngữ" theo sau là "=" theo sau là
"thuật ngữ" tạo thành một "thuật ngữ" và "thuật ngữ" theo sau là "+" theo sau là "thuật ngữ" cũng có thể tạo thành một thuật ngữ
"thuật ngữ".

Vì vậy, nếu bạn thấy hai thuật ngữ có dấu "=" hoặc "+" ở giữa chúng, bạn có thể biến chúng thành một
sự biểu lộ. Khi bạn làm điều này, bạn thực thi mã trong khối ở dòng tiếp theo: nếu bạn
thấy "=", bạn sẽ thực hiện mã ở dòng 2. Nếu bạn thấy "+", bạn sẽ thực hiện mã ở dòng 4. Đó là
mã này đóng góp cho cây op.

| thời hạn THÊM thời hạn
{ $$ = newBINOP($2, 0, vô hướng($1), vô hướng($3)); }

Điều này thực hiện là tạo ra một op nhị phân mới và cung cấp cho nó một số biến. Các
các biến tham chiếu đến mã thông báo: $1 là mã thông báo đầu tiên trong đầu vào, $2 là mã thông báo thứ hai, v.v.
bật - nghĩ đến các tham chiếu ngược biểu thức chính quy. $$ là op được trả về từ mức giảm này.
Vì vậy, chúng tôi gọi "newBINOP" để tạo toán tử nhị phân mới. Tham số đầu tiên cho "newBINOP",
một chức năng trong op.c, là loại op. Đó là một toán tử cộng, vì vậy chúng tôi muốn kiểu này là
"THÊM". Chúng ta có thể chỉ định điều này một cách trực tiếp, nhưng nó ở ngay đó dưới dạng mã thông báo thứ hai trong
đầu vào, vì vậy chúng tôi sử dụng $2. Tham số thứ hai là cờ của op: 0 có nghĩa là "không có gì đặc biệt".
Sau đó, những điều cần thêm: phía bên trái và bên phải của biểu thức của chúng ta, trong ngữ cảnh vô hướng.

CỨU


Khi Perl thực thi một cái gì đó như "addop", nó chuyển kết quả của nó sang op tiếp theo như thế nào?
Câu trả lời là thông qua việc sử dụng ngăn xếp. Perl có một số ngăn xếp để lưu trữ mọi thứ
hiện đang được thực hiện và chúng ta sẽ xem xét ba vấn đề quan trọng nhất ở đây.

Tranh luận ngăn xếp
Các đối số được chuyển tới mã PP và được trả về từ mã PP bằng cách sử dụng ngăn xếp đối số, "ST".
Cách thông thường để xử lý các đối số là lấy chúng ra khỏi ngăn xếp, xử lý chúng theo cách bạn muốn.
mong muốn và sau đó đẩy kết quả trở lại ngăn xếp. Ví dụ, đây là cách cosin
nhà điều hành làm việc:

Giá trị NV;
giá trị = POPn;
giá trị = Perl_cos(giá trị);
XPUSHn(giá trị);

Chúng ta sẽ thấy một ví dụ phức tạp hơn về điều này khi xem xét các macro của Perl bên dưới. "POPn" mang lại
bạn là NV (giá trị dấu phẩy động) của SV trên cùng trên ngăn xếp: $x trong "cos($x)". Sau đó chúng ta
tính cosin và đẩy kết quả trở lại dưới dạng NV. Chữ “X” trong “XPUSHn” có nghĩa là
ngăn xếp nên được mở rộng nếu cần - ở đây không cần thiết, vì chúng tôi biết
vẫn còn chỗ cho một mục nữa trên ngăn xếp vì chúng tôi vừa xóa một mục! "XPUSH*"
macro ít nhất đảm bảo an toàn.

Ngoài ra, bạn có thể trực tiếp xử lý ngăn xếp: "SP" cung cấp cho bạn phần tử đầu tiên trong
phần ngăn xếp của bạn và "TOP*" cung cấp cho bạn SV/IV/NV/etc hàng đầu. trên ngăn xếp. Vì thế,
ví dụ, để thực hiện phủ định đơn phương của một số nguyên:

SETi(-TOPi);

Chỉ cần đặt giá trị số nguyên của mục nhập ngăn xếp trên cùng thành số âm của nó.

Thao tác ngăn xếp đối số trong lõi hoàn toàn giống như trong XSUB - xem
perlxstut, perlxs và perlguts để có mô tả dài hơn về các macro được sử dụng trong ngăn xếp
Thao tác.

Đánh dấu ngăn xếp
Tôi nói "phần ngăn xếp của bạn" ở trên vì mã PP không nhất thiết phải lấy toàn bộ
xếp chồng lên chính nó: nếu hàm của bạn gọi một hàm khác, bạn sẽ chỉ muốn hiển thị
các đối số nhắm đến hàm được gọi và không (nhất thiết) để nó tự thực hiện
dữ liệu. Cách chúng tôi làm điều này là có một đáy ngăn xếp "ảo", tiếp xúc với từng
chức năng. Ngăn xếp đánh dấu giữ các dấu trang ở các vị trí trong ngăn xếp đối số mà mỗi người có thể sử dụng được
chức năng. Ví dụ: khi xử lý một biến bị ràng buộc, (trong nội bộ, thứ gì đó có "P"
magic) Perl phải gọi các phương thức để truy cập vào các biến bị ràng buộc. Tuy nhiên, chúng ta cần phải
tách các đối số được hiển thị cho phương thức với đối số được hiển thị cho bản gốc
chức năng - lưu trữ hoặc tìm nạp hoặc bất cứ thứ gì có thể. Đại khái đây là cách buộc "đẩy"
được thực thi; xem "av_push" trong av.c:

1 ĐẨY(SP);
2 MỞ RỘNG(SP,2);
3 PUSH(SvTIED_obj((SV*)av, mg));
4 PUSH(val);
5 TRỞ LẠI;
6 VÀO;
7 call_method("PUSH", G_SCALAR|G_DISCARD);
8 RỜI;

Hãy kiểm tra toàn bộ việc thực hiện, để thực hành:

1 ĐẨY(SP);

Đẩy trạng thái hiện tại của con trỏ ngăn xếp vào ngăn xếp đánh dấu. Điều này là như vậy khi
chúng tôi đã hoàn tất việc thêm các mục vào ngăn xếp đối số, Perl biết chúng tôi đã thêm bao nhiêu thứ
gần đây.

2 MỞ RỘNG(SP,2);
3 PUSH(SvTIED_obj((SV*)av, mg));
4 PUSH(val);

Chúng ta sẽ thêm hai mục nữa vào ngăn xếp đối số: khi bạn có một mảng gắn liền,
Chương trình con "PUSH" nhận đối tượng và giá trị được đẩy và đó chính xác là những gì
chúng ta có ở đây - đối tượng bị ràng buộc, được truy xuất bằng "SvTIED_obj" và giá trị SV "val".

5 TRỞ LẠI;

Tiếp theo, chúng ta yêu cầu Perl cập nhật con trỏ ngăn xếp toàn cục từ biến nội bộ của chúng ta: "dSP"
chỉ cung cấp cho chúng tôi một bản sao cục bộ chứ không phải một tài liệu tham khảo toàn cầu.

6 VÀO;
7 call_method("PUSH", G_SCALAR|G_DISCARD);
8 RỜI;

"ENTER" và "LEAVE" bản địa hóa một khối mã - chúng đảm bảo rằng tất cả các biến đều được
được dọn dẹp, mọi thứ đã được bản địa hóa sẽ được trả về giá trị trước đó, v.v.
Hãy coi chúng như "{" và "}" của khối Perl.

Để thực sự thực hiện lệnh gọi phương thức kỳ diệu, chúng ta phải gọi một chương trình con trong không gian Perl:
"call_method" đảm nhiệm việc đó và được mô tả trong Perlcall. Chúng tôi gọi là "Đẩy"
phương thức trong ngữ cảnh vô hướng và chúng tôi sẽ loại bỏ giá trị trả về của nó. Các call_method()
hàm loại bỏ phần tử trên cùng của ngăn xếp đánh dấu, do đó người gọi không có gì để làm
dọn dẹp.

Lưu ngăn xếp
C không có khái niệm về phạm vi cục bộ, vì vậy Perl cung cấp một khái niệm. Chúng tôi đã thấy "ENTER" và
"LEAVE" được dùng làm dấu ngoặc nhọn; ngăn xếp lưu thực hiện tương đương với C, cho
thí dụ:

{
địa phương $foo = 42;
...
}

Xem "Bản địa hóa các thay đổi" trong perlguts để biết cách sử dụng ngăn xếp lưu.

TRIỆU OF MACRO


Một điều bạn sẽ nhận thấy về nguồn Perl là nó chứa đầy các macro. Một số có
gọi việc sử dụng rộng rãi macro là điều khó hiểu nhất, những người khác lại thấy điều đó làm tăng thêm
trong trẻo. Hãy lấy một ví dụ, đoạn mã thực hiện toán tử cộng:

1 PP(pp_add)
2 {
3 dSP; DATARGET; tryAMAGICbin (thêm, opASSIGN);
4 {
5 dPOPTOPnnrl_ul;
6 BỘ(trái + phải);
7 TRỞ LẠI;
8}
9}

Mỗi dòng ở đây (tất nhiên là ngoài dấu ngoặc nhọn) đều chứa một macro. Bộ dòng đầu tiên
khai báo hàm như Perl mong đợi đối với mã PP; dòng 3 thiết lập biến
các khai báo cho ngăn xếp đối số và đích, giá trị trả về của thao tác.
Cuối cùng, nó thử xem liệu phép cộng có bị quá tải hay không; nếu vậy thì thích hợp
chương trình con được gọi.

Dòng 5 là một khai báo biến khác - tất cả các khai báo biến đều bắt đầu bằng "d" -
bật lên từ đầu đối số xếp chồng hai NV (do đó là "nn") và đặt chúng vào
các biến "phải" và "trái", do đó có "rl". Đây là hai toán hạng của phép cộng
nhà điều hành. Tiếp theo, chúng ta gọi "SETn" để đặt NV của giá trị trả về cho kết quả của phép cộng
hai giá trị. Việc này hoàn tất, chúng tôi trả về - macro "TRẢ LẠI" đảm bảo rằng giá trị trả về của chúng tôi
được xử lý đúng cách và chúng tôi chuyển toán tử tiếp theo để chạy trở lại vòng lặp chạy chính.

Hầu hết các macro này đều được giải thích bằng perlapi và một số macro quan trọng hơn là
cũng được giải thích trong perlxs. Đặc biệt chú ý đến "Bối cảnh và
PERL_IMPLICIT_CONTEXT" trong perlguts để biết thông tin về macro "[pad]THX_?".

THÊM NỮA ĐỌC HIỂU


Để biết thêm thông tin về nội bộ Perl, vui lòng xem các tài liệu được liệt kê tại "Nội bộ
và Giao diện ngôn ngữ C" trong perl.

Sử dụng perlinterp trực tuyến bằng dịch vụ onworks.net


Máy chủ & Máy trạm miễn phí

Tải xuống ứng dụng Windows & Linux

Lệnh Linux

Ad