Hãy để tôi mở đầu điều này bằng cách nói rằng tôi hoàn toàn không biết gì về cách hoạt động của trình phân tích cú pháp. Phải nói rằng, dòng 296 của gram.y xác định các mã thông báo sau để đại diện cho phép gán trong trình phân tích cú pháp (YACC?) R sử dụng:
%token LEFT_ASSIGN EQ_ASSIGN RIGHT_ASSIGN LBB
Sau đó, trên các dòng 5140 đến 5150 của gam.c , mã này trông giống như mã C tương ứng:
case '-':
if (nextchar('>')) {
if (nextchar('>')) {
yylval = install_and_save2("<<-", "->>");
return RIGHT_ASSIGN;
}
else {
yylval = install_and_save2("<-", "->");
return RIGHT_ASSIGN;
}
}
Cuối cùng, bắt đầu từ dòng 5044 của gram.c , định nghĩa của install_and_save2
:
static SEXP install_and_save2(char * text, char * savetext)
{
strcpy(yytext, savetext);
return install(text);
}
Vì vậy, một lần nữa, không có kinh nghiệm làm việc với trình phân tích cú pháp, có vẻ như ->
và ->>
được dịch trực tiếp sang <-
và <<-
tương ứng, ở mức rất thấp trong quá trình thông dịch.
Bạn đã đưa ra một điểm rất tốt khi hỏi làm thế nào trình phân tích cú pháp "biết" để đảo ngược các đối số thành ->
- coi điều đó ->
dường như được cài đặt vào bảng biểu tượng R là <-
- và do đó có thể diễn giải chính xác x -> y
là y <- x
và không x <- y
. Điều tốt nhất tôi có thể làm là cung cấp thêm suy đoán khi tôi tiếp tục tìm thấy "bằng chứng" để hỗ trợ tuyên bố của mình. Hy vọng rằng một số chuyên gia YACC nhân từ sẽ vấp phải câu hỏi này và cung cấp một chút hiểu biết; Tuy nhiên, tôi sẽ không nín thở về điều đó.
Quay lại dòng 383 và 384 của gram.y , điều này trông giống như một số logic phân tích cú pháp khác liên quan đến các ký hiệu LEFT_ASSIGN
và RIGHT_ASSIGN
ký hiệu đã nói ở trên :
| expr LEFT_ASSIGN expr { $$ = xxbinary($2,$1,$3); setId( $$, @$); }
| expr RIGHT_ASSIGN expr { $$ = xxbinary($2,$3,$1); setId( $$, @$); }
Mặc dù tôi không thể thực sự hiểu được cú pháp điên rồ này, nhưng tôi đã nhận thấy rằng các đối số thứ hai và thứ ba xxbinary
được hoán đổi thành WRT LEFT_ASSIGN
( xxbinary($2,$1,$3)
) và RIGHT_ASSIGN
( xxbinary($2,$3,$1)
).
Đây là những gì tôi đang hình dung trong đầu:
LEFT_ASSIGN
Tình huống: y <- x
$2
là "đối số" thứ hai cho trình phân tích cú pháp trong biểu thức trên, tức là <-
$1
là người đầu tiên; cụ thể lày
$3
là thứ ba; x
Do đó, kết quả gọi (C?) Sẽ là xxbinary(<-, y, x)
.
Áp dụng logic này RIGHT_ASSIGN
, tức là x -> y
, kết hợp với phỏng đoán trước đó của tôi về <-
và ->
bị hoán đổi,
$2
được dịch từ ->
sang<-
$1
Là x
$3
Là y
Nhưng vì kết quả là xxbinary($2,$3,$1)
thay vì xxbinary($2,$1,$3)
, kết quả vẫn là xxbinary(<-, y, x)
.
Dựa trên điều này xa hơn một chút, chúng tôi có định nghĩa xxbinary
trên dòng 3310 của gram.c :
static SEXP xxbinary(SEXP n1, SEXP n2, SEXP n3)
{
SEXP ans;
if (GenerateCode)
PROTECT(ans = lang3(n1, n2, n3));
else
PROTECT(ans = R_NilValue);
UNPROTECT_PTR(n2);
UNPROTECT_PTR(n3);
return ans;
}
Đáng tiếc là tôi không thể tìm thấy một định nghĩa đúng lang3
(hoặc biến thể của nó lang1
, lang2
, vv ...) trong mã nguồn R, nhưng tôi giả định rằng nó được sử dụng để đánh giá chức năng đặc biệt (ví dụ ký tự) trong một cách mà được đồng bộ với thông dịch viên.
Nội dung cập nhật
Tôi sẽ cố gắng giải quyết một số câu hỏi bổ sung của bạn trong phần nhận xét tốt nhất có thể khi cung cấp kiến thức (rất) hạn chế của tôi về quy trình phân tích cú pháp.
1) Đây có thực sự là đối tượng duy nhất trong R hoạt động như thế này không ?? (Tôi đã ghi nhớ câu trích dẫn của John Chambers qua cuốn sách của Hadley: "Mọi thứ tồn tại đều là một đối tượng. Mọi thứ xảy ra đều là một lệnh gọi hàm." Điều này rõ ràng nằm ngoài miền đó - còn điều gì khác như thế này không?
Đầu tiên, tôi đồng ý rằng điều này nằm ngoài miền đó. Tôi tin rằng trích dẫn của Chambers liên quan đến Môi trường R, tức là các quá trình đều diễn ra sau giai đoạn phân tích cú pháp mức thấp này. Tuy nhiên, tôi sẽ đề cập đến vấn đề này nhiều hơn một chút bên dưới. Dù sao, ví dụ khác duy nhất về loại hành vi này mà tôi có thể tìm thấy là **
toán tử, là một từ đồng nghĩa với toán tử lũy thừa phổ biến hơn ^
. Đối với phép gán đúng, **
dường như không được trình thông dịch "công nhận" là một lệnh gọi hàm, v.v.:
R> `->`
R> `**`
Tôi tìm thấy điều này bởi vì đó là trường hợp duy nhất khác install_and_save2
được sử dụng bởi trình phân tích cú pháp C :
case '*':
if (nextchar('*')) {
yylval = install_and_save2("^", "**");
return '^';
} else
yylval = install_and_save("*");
return c;
2) Chính xác thì điều này xảy ra khi nào? Tôi ghi nhớ rằng phép thay thế (3 -> y) đã lật ngược biểu thức; Tôi không thể tìm ra từ nguồn thay thế nào có thể đã ping đến YACC ...
Tất nhiên tôi vẫn đang suy đoán ở đây, nhưng vâng, tôi nghĩ chúng ta có thể giả định một cách an toàn rằng khi bạn gọi substitute(3 -> y)
, từ quan điểm của hàm thay thế , biểu thức luôn là y <- 3
; ví dụ như hàm hoàn toàn không biết rằng bạn đã gõ 3 -> y
. do_substitute
, giống như 99% các hàm C được R sử dụng, chỉ xử lý các SEXP
đối số - tôi tin là an EXPRSXP
trong trường hợp 3 -> y
(== y <- 3
). Đây là những gì tôi đã ám chỉ ở trên khi tôi phân biệt giữa Môi trường R và quá trình phân tích cú pháp. Tôi không nghĩ rằng có bất kỳ điều gì đặc biệt kích hoạt trình phân tích cú pháp bắt đầu hoạt động - mà đúng hơn là mọi thứ bạn nhập vào trình thông dịch sẽ được phân tích cú pháp. Tôi đã làm một chút đọc thêm về trình phân tích cú pháp YACC / Bison tạo vào đêm qua và theo tôi hiểu (hay còn gọi là đừng đặt cược vào trang trại này), Bison sử dụng ngữ pháp mà bạn xác định (trong .y
(các) tệp) để tạo trình phân tích cú pháp trong C - tức là một hàm C thực hiện phân tích cú pháp thực tế của đầu vào. Đổi lại, mọi thứ bạn nhập vào trong một phiên R lần đầu tiên được xử lý bởi hàm phân tích cú pháp C này, sau đó sẽ ủy quyền cho hành động thích hợp được thực hiện trong Môi trường R (Nhân tiện, tôi đang sử dụng thuật ngữ này rất lỏng lẻo). Trong giai đoạn này, lhs -> rhs
sẽ được dịch sang rhs <- lhs
, **
sang ^
, v.v. Ví dụ, đây là đoạn trích từ một trong các bảng của các hàm nguyên thủy trong tên.c :
{"if", do_if, 0, 200, -1, {PP_IF, PREC_FN, 1}},
{"while", do_while, 0, 100, 2, {PP_WHILE, PREC_FN, 0}},
{"for", do_for, 0, 100, 3, {PP_FOR, PREC_FN, 0}},
{"repeat", do_repeat, 0, 100, 1, {PP_REPEAT, PREC_FN, 0}},
{"break", do_break, CTXT_BREAK, 0, 0, {PP_BREAK, PREC_FN, 0}},
{"next", do_break, CTXT_NEXT, 0, 0, {PP_NEXT, PREC_FN, 0}},
{"return", do_return, 0, 0, -1, {PP_RETURN, PREC_FN, 0}},
{"function", do_function, 0, 0, -1, {PP_FUNCTION,PREC_FN, 0}},
{"<-", do_set, 1, 100, -1, {PP_ASSIGN, PREC_LEFT, 1}},
{"=", do_set, 3, 100, -1, {PP_ASSIGN, PREC_EQ, 1}},
{"<<-", do_set, 2, 100, -1, {PP_ASSIGN2, PREC_LEFT, 1}},
{"{", do_begin, 0, 200, -1, {PP_CURLY, PREC_FN, 0}},
{"(", do_paren, 0, 1, 1, {PP_PAREN, PREC_FN, 0}},
Bạn sẽ nhận thấy rằng ->
, ->>
và **
không được định nghĩa ở đây. Theo như tôi biết, các biểu thức nguyên thủy R như <-
và [
, v.v. ... là tương tác gần nhất mà Môi trường R từng có với bất kỳ mã C cơ bản nào. Điều tôi gợi ý là ở giai đoạn này trong quá trình (từ việc bạn nhập một bộ ký tự vào trình thông dịch và nhấn 'Enter', thông qua đánh giá thực tế của một biểu thức R hợp lệ), trình phân tích cú pháp đã hoạt động kỳ diệu của nó, đó là lý do bạn không thể có được định nghĩa hàm cho ->
hoặc **
bằng cách bao quanh chúng bằng các dấu gạch ngược, như bạn thường làm.