R如何正确解析右赋值运算符`->`?


76

因此,这是一个微不足道的问题,但令我感到困惑的是我无法回答,也许答案会教给我更多有关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

?"->",,?assignOpsR语言定义都简单地以正确的赋值运算符的形式提及了它。

但是,关于如何->使用,显然存在一些独特之处。它不是一个函数/运算符(就像对调用的getAnywhere直接`->`显示一样),那是什么呢?是否完全属于自己的一类?

除了“ ->R语言在解释和处理方面是完全独特的;记住并继续前进”之外,还有什么可学的吗?


2
实际上,这个与链接相关的问题更加相关:stackoverflow.com/questions/23309687/…–
MichaelChirico

1
您只需为标签设置一个值。这并不意味着它们是相同的
Ole Petersen

Answers:


71

首先,让我说我对解析器的工作原理一无所知。话虽如此,gram.y的第296行定义了以下标记来表示(YACC?)解析器R使用的赋值:

%token      LEFT_ASSIGN EQ_ASSIGN RIGHT_ASSIGN LBB

然后,在gram.c的5140至5150行上,这看起来像相应的C代码:

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

最后,从gram.c的5044行开始,定义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 -> yy <- x没有 x <- y。我能做的最好的就是进一步猜测,因为我继续遇到“证据”来支持我的主张。希望一些仁慈的YACC专家会偶然发现这个问题并提供一些见识。不过,我不会屏住呼吸。

回到gram.y的第383和384行,这看起来像是一些与上述LEFT_ASSIGNRIGHT_ASSIGN符号有关的解析逻辑:

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

尽管我无法真正理解这种疯狂的语法,但我确实注意到,将第二个和第三个参数xxbinary交换为WRT LEFT_ASSIGNxxbinary($2,$1,$3))和RIGHT_ASSIGNxxbinary($2,$3,$1))。

这是我脑海中所描绘的:

LEFT_ASSIGN 场景: y <- x

  • $2 是上述表达式中解析器的第二个“参数”,即 <-
  • $1是第一个;即y
  • $3 是第三; x

因此,生成的(C?)调用将为xxbinary(<-, y, x)

将这种逻辑应用于RIGHT_ASSIGN,即x -> y结合我之前关于<-并被->交换的猜想,

  • $2从转换-><-
  • $1x
  • $3y

但是由于结果xxbinary($2,$3,$1)不是xxbinary($2,$1,$3),结果仍然是 xxbinary(<-, y, x)


这个建筑离远一点,我们的定义xxbinarygram.c 3310线

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(或它的变体lang1lang2等...),在R源代码,但是我假设它是用于在与同步的方式评估特殊功能(即符号)口译员。


更新 我将尽我所能(非常)有限的解析过程知识,尝试在注释中解决您的一些其他问题。

1)这真的是R中唯一的行为是这样的对象吗?(我想起了约翰·钱伯斯(John Chambers)在哈德利(Hadley)的书中的话:“存在的一切都是对象。发生的一切都是函数调用。”这显然位于该域之外-还有其他类似的东西吗?

首先,我同意这不属于该领域。我相信钱伯斯的名言涉及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)这到底是什么时候发生?我想到了replace(3-> y)已经翻转了表达式;我无法从源头上找出可以替代YACC的替代品...

当然,我仍然在这里推测,但是,是的,我认为我们可以放心地假设,当您调用时substitute(3 -> y),从替代函数的角度来看,表达式始终为 y <- 3; 例如,该函数完全不知道您键入了什么3 -> ydo_substitute就像R使用的99%的C函数一样,只能处理SEXP参数-我相信EXPRSXP3 -> y(== y <- 3)的情况下。这是我在R环境和解析过程之间进行区分时所提到的内容。我不认为这有什么特异触发解析器春天付诸行动-而是一切您输入到解释被解析。我做了一点昨晚有更多关于YACC / Bison解析器生成器的信息,据我了解(也不要打赌农场),Bison使用您定义的语法(在.y文件中)在C中生成解析器-即执行输入的实际解析的C函数。反过来,您在R会话中输入的所有内容都首先由C解析函数处理,然后委派要在R环境中执行的适当操作(顺便说一句,我非常宽松地使用了该术语)。在这个阶段,lhs -> rhs将得到翻译为rhs <- lhs**^等..例如,这是从一个摘录的names.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表达式进行实际评估为止),解析器已经发挥了魔力,这就是为什么通常情况下,您将无法获得函数定义->或将**其用反引号引起来。


17
同时我敢说这个答案值得gram.y吗?好吧,我应该认真地重新开始工作...
MichaelChirico

2
出于记录目的(以及作为一个完整的解析器新手),我将注意到,似乎令牌的类型(在此)和其值(在此,由by分配)之间是有区别的。在我看来,该类型用于引导表达式的解析(将我们发送到读取的分支下),而其是通过第一个参数(即)传递的。RIGHT_ASSIGN<-yylvalinstall_and_save2{ $$ = xxbinary($2,$3,$1); setId( $$, @$); }xxbinary$2
乔什·奥布莱恩

@Josh O'Brien感谢您的输入(以及编辑内容);对我来说听起来很合理。如果您愿意的话,请随时在我的回答中添加该信息或其他任何相关信息(如果我试图用自己的话说,恐怕我会掩盖解释)。
nrussell

3
@nrussell不客气。lang3等。是内联函数,可在此处$RHOME/src/include/Rinlinedfuns.h找到。在我看来,它们的作用是将各个标记和解析的表达式组合在一起,成为类似列表的语言对象,从而为输入表达式的完全解析版本奠定基础。
乔什·奥布莱恩

1
感谢更新!至于**,我确实记得至少在某个时候读过那个操作员有点过时,所以至少我之前已经看过它被认为是一种被抛弃的事情。无论如何,我构建的实用程序现在充满了可疑的有用知识……正是我所喜欢的!
MichaelChirico
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.