Дозвольте мені вступити до цього, сказавши, що я абсолютно нічого не знаю про те, як працюють парсери. Сказавши це, рядок 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), парсер вже здійснив свою магію, саме тому Ви не можете отримати визначення функції для ->
або **
оточуючи їх зворотними позначками, як це зазвичай можна.