Как именно R parse `->`, оператор правого присваивания?

Итак, это своего рода тривиальный вопрос, но он подталкивает меня к тому, что я не могу ответить на него, и, возможно, ответ научит меня более подробной информации о том, как работает R.

В названии говорится все: как R parse ->, скрытая функция назначения справа?

Мои обычные трюки, чтобы погрузиться в это не удалось:

`->`

Ошибка: объект -> не найден

getAnywhere("->")

не найден объект с именем ->

И мы не можем назвать это напрямую:

`->`(3,x)

Ошибка: не удалось найти функцию "->"

Но, конечно, он работает:

(3 -> x) #assigns the value 3 to the name x
# [1] 3

Кажется, что R знает, как просто изменить аргументы, но я думал, что вышеупомянутые подходы, несомненно, взломали бы случай:

pryr::ast(3 -> y)
# \- ()
#   \- `<- #R interpreter clearly flipped things around
#   \- `y  #  (by the time it gets to `ast`, at least...)
#   \-  3  #  (note: this is because `substitute(3 -> y)` 
#          #   already returns the reversed version)

Сравните это с оператором регулярного присваивания:

`<-`
.Primitive("<-")

`<-`(x, 3) #assigns the value 3 to the name x, as expected

?"->", ?assignOps, а R Language Definition все просто упоминает об этом в качестве правильного оператора присваивания.

Но там явно что-то уникальное в том, как используется ->. Это не функция/оператор (как показывают вызовы getAnywhere и непосредственно на `->`), так что это? Это полностью в своем классе?

Есть ли что-нибудь, чему можно научиться, кроме того, -> полностью уникален в пределах языка R в том, как он интерпретируется и обрабатывается, запоминается и перемещается "?

Ответ 1

Позвольте мне предисловие к этому, сказав, что я абсолютно ничего не знаю о том, как работают парсеры. Сказав, что строка 296 графа .y определяет следующие токены для представления присваивания в синтаксическом анализаторе (YACC?) R:

%token      LEFT_ASSIGN EQ_ASSIGN RIGHT_ASSIGN LBB

Затем в строках с 5140 по 5150 грамм .c это выглядит как соответствующий код C:

case '-':
  if (nextchar('>')) {
    if (nextchar('>')) {
      yylval = install_and_save2("<<-", "->>");
      return RIGHT_ASSIGN;
    }
    else {
      yylval = install_and_save2("<-", "->");
      return RIGHT_ASSIGN;
    }
  }

Наконец, начиная с строки 5044 грамм .c, определение install_and_save2:

/* Get an R symbol, and set different yytext.  Used for translation of -> to <-. ->> to <<- */
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 графа .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 графа .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> `->`
#Error: object '->' not found
R> `**`
#Error: object '**' not found 

Я нашел это, потому что это единственный случай, когда install_and_save2 используется парсером C:

case '*':
  /* Replace ** by ^.  This has been here since 1998, but is
     undocumented (at least in the obvious places).  It is in
     the index of the Blue Book with a reference to p. 431, the
     help for 'Deprecated'.  S-PLUS 6.2 still allowed this, so
     presumably it was for compatibility with S. */
  if (nextchar('*')) {
    yylval = install_and_save2("^", "**");
    return '^';
  } else
    yylval = install_and_save("*");
return c;

2) Когда именно это происходит? Я имею в виду, что замена (3 → y) уже перевернул выражение; Я не мог понять из источника, что заменитель делает, что pinged YACC...

Конечно, я все еще размышляю здесь, но да, я думаю, мы можем смело предположить, что когда вы вызываете substitute(3 -> y), с точки зрения заменяющей функции, выражение всегда было y <- 3; например функция полностью не осознает, что вы набрали 3 -> y. do_substitute, как и 99% функций C, используемых R, обрабатывает только аргументы SEXP - EXPRSXP в случае 3 -> y (== y <- 3). Это то, о чем я говорил выше, когда я проводил различие между средой R и процессом синтаксического анализа. Я не думаю, что есть что-то, что специально запускает парсер spring в действие, а скорее все, которое вы вводите в интерпретатор, получает синтаксический анализ. Вчера я немного больше читал о генераторе парсера YACC/Bison, и, насколько я понимаю (он не поставил ферму на это), Bison использует указанную вами грамматику (в файле .y) для создания синтаксического анализатора в C - т.е. функции C, которая выполняет фактический синтаксический анализ ввода. В свою очередь, все, что вы вводите в сеансе R, сначала обрабатывается этой функцией синтаксического анализа C, которая затем делегирует соответствующее действие, которое необходимо предпринять в среде R (я использую этот термин очень слабо, кстати). На этом этапе lhs -> rhs будет переведена на rhs <- lhs, ** на ^ и т.д. Например, это выдержка из одной из таблиц примитивные функции в именах .c:

/* Language Related Constructs */

/* Primitives */
{"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 с любым базовым кодом C. То, что я предлагаю, заключается в том, что на этом этапе процесса (от ввода набора символов в интерпретатор и нажатия 'Enter', через фактическую оценку действительного выражения R), синтаксический анализатор уже работал над своей магией, поэтому вы не можете получить определение функции для -> или **, окружив их обратными окнами, как вы обычно можете.