Как именно R анализирует` ->', оператор присваивания справа?


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

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

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

`->`

ошибка: объект -> не нашел

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 все просто упоминают его попутно как правильный оператор присваивания.

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

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

1 75

1 ответ:

позвольте мне предварить это, сказав, что я абсолютно ничего не знаю о том, как работают Парсеры. Сказав это,линия 296 грамм.г определяет следующие маркеры для представления назначения в (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);
}

так опять же, имея нулевой опыт работы с парсерами, кажется, что -> и ->> переводятся непосредственно в <- и <<-, соответственно, при a очень низкий уровень в процессе интерпретации.


вы подняли очень хороший вопрос, спрашивая, как парсер "знает", чтобы изменить аргументы на -> - учитывая, что -> кажется, что он установлен в таблицу символов R как <- - и при этом уметь правильно интерпретировать x -> y как y <- x и неx <- y. Лучшее, что я могу сделать, это предоставить дальнейшие предположения, поскольку я продолжаю сталкиваться с "доказательствами" в поддержку моих утверждений. Надеюсь, какой-нибудь милосердный эксперт YACC наткнется на этот вопрос и предоставит немного информации; я не собираюсь задерживать дыхание на этом.

на линии 383 и 384 грамма.г, это выглядит как еще одна логика разбора, связанная с вышеупомянутым LEFT_ASSIGN и RIGHT_ASSIGN символы:

|   expr LEFT_ASSIGN expr       { $$ = xxbinary(,,);  setId( $$, @$); }
|   expr RIGHT_ASSIGN expr      { $$ = xxbinary(,,);  setId( $$, @$); }

хотя я не могу действительно сделать головы или хвосты этого сумасшедшего синтаксиса, я заметил, что второй и третий аргументы xxbinary заменяются на WRT LEFT_ASSIGN (xxbinary(,,)) и RIGHT_ASSIGN (xxbinary(,,)).

вот что я представляю в голове:

LEFT_ASSIGN сценарий: y <- x

  • является вторым "аргументом" для парсера в приведенном выше выражении, т. е. <-
  • это первое; а именно y
  • - третий; x

таким образом, в результате (C?) вызов будет xxbinary(<-, y, x).

применяя эту логику к RIGHT_ASSIGN, т. е. x -> y в сочетании с моей предыдущей гипотезы о <- и -> начало поменялись,

  • переводится с -> до <-
  • is x
  • и y

но так как результат xxbinary(,,) вместо xxbinary(,,), результат ещеxxbinary(<-, y, x).


отталкиваясь от этого немного дальше, у нас есть определение xxbinary on линия 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, который ведет себя так?? (У меня есть я вспомнил цитату Джона Чамберса через Хэдли книга: "все то, что существует-это объект. Все, что происходит-это вызов функции." Это явно лежит за пределами этой области - есть ли что-нибудь еще, как это?

во-первых, я согласен, что это лежит за пределами этого домена. Я считаю, что цитата Чемберса касается среды 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 -> г) имеет я уже перевернул выражение; я не мог понять из источника, какой заменитель делает это, чтобы пинговать YACC...

конечно, я все еще размышляю здесь, Но да, я думаю, что мы можем смело предположить, что когда вы звоните substitute(3 -> y) с точки зрения замена функция, выражение всегдаy <- 3; например, функция полностью не знает, что вы набрали 3 -> y. do_substitute, как и 99% функций C используется R, только ручки SEXP аргументы - это EXPRSXP в случае 3 -> y ( = = y <- 3), я верю. Это то, что я имел в виду выше, когда я сделал различие между средой R и процессом разбора. Я не думаю, что есть что - то, что специально запускает парсер, чтобы перейти в действие-но скорее все вы вводите в переводчик разбирается. Я сделал мало подробнее о парсере YACC / Bison генератор вчера вечером, и как я понимаю (ака не ставьте ферму на это), Зубр использует грамматику, которую вы определяете (в .y file (s)) to создать парсер в 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, либо с какого-либо базового кода. Я предлагаю, что на этом этапе процесса (от ввода набора символов в интерпретатор и нажатия "Enter" до фактической оценки допустимого выражения R) парсер уже сработал свою магию, поэтому вы не можете получить определение функции для -> или ** окружая их с помощью задних палочек, как вы обычно можете.