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

标签 r yacc

所以这是一个微不足道的问题,但让我烦恼的是我无法回答它,也许答案会教会我更多关于 R 的工作原理的细节。

标题说明了一切:R 如何解析 -> ,晦涩的右侧赋值函数?

我通常的潜入技巧失败了:

`->`

Error: object -> not found


getAnywhere("->")

no object named -> was found



我们不能直接调用它:
`->`(3,x)

Error: could not find function "->"



但当然,它有效:
(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 语言中它的解释和处理方式是完全独一无二的;记住并继续前进”之外,还有什么可以学习的吗?

最佳答案

让我先说我对解析器的工作原理一无所知。话虽如此,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);
}

因此,在使用解析器的经验为零的情况下,似乎 ->->>直接翻译成<-<<- ,分别在解释过程中处于非常低的水平。

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

返回 lines 383 and 384 of 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翻译自 -><-
  • $1x
  • $3y

  • 但由于结果是 xxbinary($2,$3,$1)而不是 xxbinary($2,$1,$3) ,结果还是xxbinary(<-, y, x) .

    进一步构建这个,我们有 xxbinary 的定义在 line 3310 of 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 的正确定义。 (或其变体 lang1lang2 等...),但我假设它用于以与解释器同步的方式评估特殊函数(即符号)。

    更新
    鉴于我对解析过程的(非常)有限的了解,我将尽力在评论中解决您的一些其他问题。

    1) Is this really the only object in R that behaves like this?? (I've got in mind the John Chambers quote via Hadley's book: "Everything that exists is an object. Everything that happens is a function call." This clearly lies outside that domain -- is there anything else like this?



    首先,我同意这不属于该领域。我相信钱伯斯的引述与 R 环境有关,即在此低级解析阶段之后发生的所有过程。不过,我将在下面多谈一点。无论如何,我能找到的这种行为的唯一其他例子是 **运算符,它是更常见的求幂运算符 ^ 的同义词.与正确分配一样,**解释器似乎没有“识别”为函数调用等......

    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) When exactly does this happen? I've got in mind that substitute(3 -> y) has already flipped the expression; I couldn't figure out from the source what substitute does that would have pinged the YACC...



    当然,我仍然在这里进行推测,但是是的,我认为我们可以放心地假设当您拨打 substitute(3 -> y) 时,从the substitute function的角度来看, 表达式总是 y <- 3 ;例如该函数完全不知道您输入了 3 -> y . do_substitute ,就像 R 使用的 99% 的 C 函数一样,只处理 SEXP参数 - EXPRSXP3 -> y 的情况下(== y <- 3),我相信。这就是我在上面对 R 环境和解析过程进行区分时所暗示的。我认为没有什么可以特别触发解析器开始行动 - 而是 一切你输入到解释器被解析。昨晚我做了更多关于 YACC/Bison 解析器生成器的阅读,据我所知(也就是不要在这方面押注农场),Bison 使用您定义的语法(在 .y 文件中)在 C 中生成解析器 - 即执行输入实际解析的 C 函数。反过来,您在 R session 中输入的所有内容首先由这个 C 解析函数处理,然后委托(delegate)在 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 代码最接近的交互。我的建议是,在这个过程中(从你在解释器中输入一组字符并点击“Enter”,直到对有效 R 表达式的实际评估),解析器已经发挥了它的魔力,这就是为什么您无法获得 -> 的函数定义或 **像往常一样用反引号包围它们。

    关于r - R 究竟如何解析 `->` ,右赋值运算符?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34599027/

    相关文章:

    r - 如何根据每个ID汇总R中的表

    r - 将嵌套 for 循环转换为函数

    c - yacc:在 yyparse() 方法中使用文件指针

    parsing - 通过运算符优先级简化语法

    parsing - 在同一个应用程序中是否可以有两个或多个 Lex/Yacc 解析器

    r - 为什么 RMarkdown 无法正确转义第二个 LaTeX 表?

    r - as.tibble()、as_data_frame() 和 tbl_df() 之间有什么区别?

    r - 如何检查PDF是否是扫描图像或包含R中的文本

    parsing - GNU bison 和 yacc 有什么区别?

    javascript - JISON 解析命令时出现问题