Дозвольте мені вступити до цього, сказавши, що я абсолютно нічого не знаю про те, як працюють парсери. Сказавши це, рядок 296 з gram.y визначає наступні маркери, що представляють призначення в (YACC?) Парсері, який використовує R:
%token LEFT_ASSIGN EQ_ASSIGN RIGHT_ASSIGN LBB
Потім, у рядках 5140 - 5150 з gram.c , це виглядає як відповідний код С:
case '-':
if (nextchar('>')) {
if (nextchar('>')) {
yylval = install_and_save2("<<-", "->>");
return RIGHT_ASSIGN;
}
else {
yylval = install_and_save2("<-", "->");
return RIGHT_ASSIGN;
}
}
Нарешті, починаючи з рядка 5044 з gram.c , визначення install_and_save2:
static SEXP install_and_save2(char * text, char * savetext)
{
strcpy(yytext, savetext);
return install(text);
}
Тож знову ж таки, маючи нульовий досвід роботи з парсерами, здається, що ->і ->>перекладаються безпосередньо на <-і <<-, відповідно, на дуже низький рівень в процесі інтерпретації.
Ви підняли дуже хороший момент, запитуючи, як синтаксичний аналізатор "знає", щоб змінити аргументи, ->враховуючи те, що, ->здається, встановлено в таблиці символів R як <-- і, отже, мати змогу правильно інтерпретувати x -> yяк y <- xі ні x <- y . Найкраще, що я можу зробити, це надати подальші спекуляції, оскільки я продовжую натрапляти на "докази" на підтвердження своїх тверджень. Сподіваємось, якийсь милосердний експерт YACC спіткнеться щодо цього питання та надасть трохи розуміння; Однак я не збираюся затримувати дихання на цьому.
Повертаючись до рядків 383 та 384 з gram.y , це виглядає як ще деяка логіка синтаксичного аналізу, пов’язана з вищезазначеним LEFT_ASSIGNта RIGHT_ASSIGNсимволами:
| expr LEFT_ASSIGN expr { $$ = xxbinary($2,$1,$3); setId( $$, @$); }
| expr RIGHT_ASSIGN expr { $$ = xxbinary($2,$3,$1); setId( $$, @$); }
Незважаючи на те, що я не можу зробити голови чи хвости цього шаленого синтаксису, я помітив, що другий та третій аргументи xxbinaryзамінюються на WRT LEFT_ASSIGN( xxbinary($2,$1,$3)) та RIGHT_ASSIGN( xxbinary($2,$3,$1)).
Ось, що я уявляю собі в голові:
LEFT_ASSIGN Сценарій: y <- x
$2 є другим "аргументом" синтаксичного аналізатора у наведеному вище виразі, тобто <-
$1є першим; а самеy
$3 є третім; x
Отже, отриманий виклик (C?) Буде xxbinary(<-, y, x).
Застосовуючи цю логіку RIGHT_ASSIGN, тобто x -> y, в поєднанні з моєї попередньої гіпотезою про <-і ->отримувати місця,
$2отримує переклад з ->на<-
$1 є x
$3 є y
Але оскільки результат xxbinary($2,$3,$1)замість xxbinary($2,$1,$3), результат все одно xxbinary(<-, y, x) .
Будівництво від цього трохи далі, ми маємо визначення xxbinaryна лінії 3310 з 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;
}
На жаль, я не зміг знайти правильне визначення lang3(або його варіанти lang1, lang2тощо ...) у вихідному коді R, але я припускаю, що він використовується для оцінки спеціальних функцій (тобто символів) способом, який синхронізується з перекладач.
Оновлення
Я спробую відповісти на деякі Ваші додаткові запитання в коментарях, наскільки це можливо, враховуючи мої (дуже) обмежені знання щодо процесу аналізу.
1) Невже це єдиний об’єкт у R, який поводиться так ?? (Я маю на увазі цитату Джона Чемберса через книгу Хедлі: "Все, що існує, є об'єктом. Все, що відбувається, є викликом функції". Це явно лежить поза цим доменом - чи є щось подібне?
По-перше, я погоджуюсь, що це лежить поза цим доменом. Я вважаю, що цитата Chambers стосується середовища R, тобто процесів, що відбуваються після цієї фази розбору низького рівня. Проте я торкнусь цього трохи нижче, однак. У будь-якому випадку, єдиним іншим прикладом такої поведінки, який я міг знайти, є **оператор, який є синонімом більш поширеного оператора степенізації ^. Як і при правильному призначенні, **інтерпретатор, здається, не "розпізнається" як виклик функції тощо ...
R> `->`
R> `**`
Я знайшов це, бо це єдиний інший випадок, коли install_and_save2 використовується парсер С :
case '*':
if (nextchar('*')) {
yylval = install_and_save2("^", "**");
return '^';
} else
yylval = install_and_save("*");
return c;
2) Коли саме це відбувається? Я мав на увазі, що заміна (3 -> y) вже перевернула вираз; Я не міг зрозуміти з джерела, який замінник робить, що спричинило б YACC ...
Звичайно, я все ще тут припускаю, але так, я думаю, ми можемо спокійно припустити, що коли ви телефонуєте substitute(3 -> y), з точки зору функції заміщення , вираз завжди був y <- 3 ; наприклад, функція абсолютно не знає, що ви ввели 3 -> y. do_substitute, як і 99% функцій C, використовуваних R, обробляє лише SEXPаргументи - я вважаю, EXPRSXPу випадку 3 -> y(== y <- 3). Це те, про що я мав на увазі вище, коли я робив різницю між середовищем R та процесом синтаксичного аналізу. Я не думаю, що є щось, що спонукає синтаксичний аналізатор перейти в дію, але все, що ви вводите в інтерпретатор, аналізується. Я зробив трохидокладніше читаючи про генератор парсера YACC / Bison вчора ввечері, і, наскільки я розумію (він же не робить ставку на ферму на цьому), Bison використовує граматику, яку ви визначаєте (у .yфайлі), щоб створити парсер на C тобто функція C, яка виконує фактичний розбір вхідних даних. У свою чергу, все, що ви вводите в сеанс R, спочатку обробляється цією функцією синтаксичного розбору С, яка потім делегує відповідні дії, які потрібно вжити в середовищі R (я, до речі, використовую цей термін дуже вільно). На цьому етапі lhs -> rhsбуде переведено в rhs <- lhs, **в ^тощо ... Наприклад, це витяг з однієї з таблиць примітивних функцій у names.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}},
Ви помітите , що ->, ->>і **не визначені тут. Наскільки мені відомо, примітивні вирази R, такі як <-і [, тощо ... є найбільш тісною взаємодією, яку коли-небудь має середовище R з будь-яким кодом, що лежить в основі С. Я припускаю, що на цьому етапі процесу (починаючи з того, що ви вводите в інтерпретатор задані символи та натискаєте клавішу Enter, вгору через фактичну оцінку дійсного виразу R), парсер вже здійснив свою магію, саме тому Ви не можете отримати визначення функції для ->або **оточуючи їх зворотними позначками, як це зазвичай можна.