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

时间:2016-01-04 20:11:11

标签: r yacc

所以这是一个微不足道的问题,但是我无法回答这个问题,也许答案会告诉我一些关于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 Language Definition都只是简单地提及它作为正确的赋值运算符。

但显然有一些关于如何使用->的独特之处。它不是函数/运算符(因为调用getAnywhere并且直接调用`->`似乎证明了),那么它是什么?它完全属于自己的一类吗?

除了“->在R语言中如何解释和处理它之外,还有什么值得学习的东西;记住并继续前进”?

1 个答案:

答案 0 :(得分:71)

让我先说一下,我完全不知道解析器是如何工作的。话虽如此,line 296 of gram.y定义了以下标记来表示(YACC?)解析器R使用的赋值:

%token      LEFT_ASSIGN EQ_ASSIGN RIGHT_ASSIGN LBB

然后,on lines 5140 through 5150 of gram.c,这看起来像对应的C代码:

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

最后,从line 5044 of gram.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);
}

再次,由于没有使用解析器的经验,似乎->->>分别直接转换为<-<<- 解释过程中的水平很低

你提出了一个非常好的观点,询问解析器&#34;如何知道&#34;将->的参数反转 - 考虑->似乎作为<-安装到R符号表中 - 从而能够正确地将x -> y解释为y <- x }和 x <- y。我能做的最好的事情是提供进一步的推测,因为我继续遇到&#34;证据&#34;支持我的主张。希望一些仁慈的YACC专家会偶然发现这个问题并提供一些见解;不过,我不会屏住呼吸。

返回lines 383 and 384 of gram.y,这看起来像是与上述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是第二个&#34;参数&#34;上述表达式中的解析器,即<-
  • $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)

进一步加强这一点,我们在line 3310 of gram.c上有xxbinary的定义:

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;
}

很遗憾,我在R源代码中找不到lang3(或其变体lang1lang2等等)的正确定义,但我还是假设它用于以与解释器同步的方式评估特殊函数(即符号)。

<强>更新 我将尝试在评论中解决您的一些其他问题,因为我可以在解析过程中获得(非常)有限的知识。

  

1)这真的是R中唯一表现得像这样的对象吗? (I&#39;已经   记住约翰钱伯斯引用哈德利的书:&#34;一切   存在是一个对象。发生的一切都是函数调用。&#34;   这明显位于该领域之外 - 还有其他类似的东西   此?

首先,我同意这不属于该领域。我相信钱伯斯&#39;引用涉及R环境,即在此低级解析阶段之后全部发生的过程。不过,我会在下面再说一点。无论如何,我能找到的这种行为的唯一另一个例子是**运算符,它是更常见的取幂运算符^的同义词。与正确的作业一样,**似乎并未得到认可&#34;作为函数调用等...由解释器:

R> `->`
#Error: object '->' not found
R> `**`
#Error: object '**' not found 

我发现这是因为它是install_and_save2 is used by the C parser唯一的另一种情况:

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    - &GT; y)已经翻了表达;我无法从消息来源中找出那会替代YACC的替代品......

我当然还在这里推测,但是,我认为我们可以安全地假设当你从the substitute function的角度来电话substitute(3 -> y)时,表达式始终是 y <- 3;例如该函数完全不知道您键入了3 -> y。与{R}使用的99%的C函数一样,do_substitute仅处理SEXP个参数 - EXPRSXP(== 3 -> y时的y <- 3 , 我相信。当我在R环境和解析过程之间做出区分时,这正是我在上面提到的。我不认为有什么能够特别触发解析器开始行动 - 而是输入解释器的一切得到解析。我做了一个关于YACC / Bison解析器发生器更多的阅读,据我所知(也就是说不要打赌农场),Bison使用您定义的语法(在.y文件中)来生成 C中的解析器 - 即一个C函数,它实际解析输入。反过来,你在R会话中输入的所有内容首先由这个C解析函数处理,然后委托在R环境中采取适当的操作(顺便说一句,我使用这个术语非常松散)。在此阶段,lhs -> rhs将转换为rhs <- lhs**转换为^等等...例如,这是tables of primitive functions in 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代码最接近的交互。我建议的是,通过这个阶段(从你输入一组字符到解释器并点击&#39; Enter&#39;,通过对有效R表达式的实际评估),解析器已经工作了魔术,这就是为什么你不能像往常一样用反引号包围它们来获得->**的函数定义的原因。