R如何正确赋值运算符来parsing` – >`?
所以这是一个微不足道的问题,但是我不能回答这个问题,也许答案会教给我一些关于R如何工作的细节。
标题说明了这一切:R如何parsing->
,不明确的右侧分配函数?
我常用的技巧来探究这个失败:
`->`
错误:对象
->
未find
getAnywhere("->")
没有find名为
->
对象
我们不能直接调用它:
`->`(3,x)
错误:找不到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语言定义都只是简单地提到它作为正确的赋值运算符。
但是显然有一些关于如何使用的东西是独一无二的。 这不是一个函数/运算符(因为调用getAnywhere
并直接指向`->`
似乎performance出来),那么它是什么呢? 它完全在一个自己的阶级?
除了“ ->
在R语言中完全是独一无二的,它是如何解释和处理的,还有什么可以从中学习呢?
让我先说一句,我对parsing器的工作一无所知。 话虽如此, gram.y的第296行定义了下列标记来表示在(YACC?)parsing器中的赋值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); }
再一次,在parsing器的工作中没有任何经验,在解释过程中,似乎“ ->
和“ ->>
分别被直接翻译成<-
和<<-
。
你提出了一个非常好的问题,就是如何parsing器“知道”将参数反转为->
– 考虑到->
似乎被安装到R符号表中作为<-
– 并且因此能够正确地解释x -> y
如y <- x
而不是 x <- y
。 我可以做的最好的是提供进一步的猜测,因为我继续遇到“证据”来支持我的主张。 希望有一些慈悲的YACC专家会在这个问题上磕磕碰碰,提供一点见解, 不过,我不会屏住呼吸。
回到gram.y的第383和384行 ,这看起来像是一些与上述LEFT_ASSIGN
和RIGHT_ASSIGN
符号相关的parsing逻辑:
| expr LEFT_ASSIGN expr { $$ = xxbinary($2,$1,$3); setId( $$, @$); } | expr RIGHT_ASSIGN expr { $$ = xxbinary($2,$3,$1); setId( $$, @$); }
虽然我不能真正地使这个疯狂的语法xxbinary
,我注意到xxbinary
的第二个和第三个参数交换到WRT LEFT_ASSIGN
( xxbinary($2,$1,$3)
)和RIGHT_ASSIGN
( xxbinary($2,$3,$1)
)。
这就是我在脑海中描绘的东西:
LEFT_ASSIGN
scheme: y <- x
-
$2
是上述expression式中parsing器的第二个“参数”,即<-
- 第一个是
$1
; 即y
- 第三名是3
$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)
。
进一步build立起来,我们在xxbinary
的第3310行定义了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源代码中findlang3
(或其变体lang1
, lang2
等)的正确定义,但我假设它用于评估特殊函数(即符号)与解释器同步。
更新我会尝试解决您在评论中的一些额外问题,因为我可以给予(非常)有限的parsing过程知识。
1)这真的是R中的唯一对象,就像这样? (我记得约翰·钱伯斯(John Chambers)通过哈德利(Hadley)的着作引用:“所有存在的东西都是一个对象,所发生的一切都是一个函数调用。”这显然不在这个领域之外 – 还有其他的东西吗?
首先,我同意这不在这个领域。 我相信钱伯斯的引用关系到R环境,即在这个低级parsing阶段之后都发生的进程。 不过,我会在下面详细介绍一下。 无论如何,我能find的这种行为的另一个例子是**
运算符,它是更常见的指数运算符^
的同义词。 就像正确的分配一样, **
似乎不被“识别”为函数调用,等等。
R> `->` #Error: object '->' not found R> `**` #Error: object '**' not found
我发现这是因为这是C语法分析器使用 install_and_save2
的唯一情况:
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)已经翻转了这个expression; 我无法从源头上找出什么替代品可以打击YACC
当然,我还在猜测,但是,我认为我们可以放心地假设,当你从替代函数的angular度来看substitute(3 -> y)
,expression式总是 y <- 3
。 例如,函数完全不知道你input了3 -> y
。 do_substitute
,就像R所使用的C函数的99%,只处理SEXP
参数 – 在3 -> y
(== y <- 3
)的情况下是EXPRSXP
,我相信。 当我在R环境和parsing过程之间进行区分时,这就是我所指的。 我不认为有任何具体触发parsing器进入行动的东西 – 而是你input到解释器的所有东西都被parsing。 我昨天晚上对YACC / Bisonparsing器生成器进行了一些了解,据我了解(也就是说不要在农场上下注),Bison使用你定义的语法(在.y
文件中)来用C语言生成一个parsing器 – 也就是一个C语言函数,它实际上parsing了input。 反过来,您在R会话中input的所有内容都将首先由此Cparsing函数处理,然后委托在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原语expression式(如<-
和[
等等)是R Environment与任何底层C代码的最接近的交互。 我所build议的是,在这个阶段(从input一组字符到解释器并敲入“Enter”,通过对一个有效的Rexpression式的实际评估),parsing器已经发挥了它的魔力,这就是为什么你可以通过用反引号把它们包围起来,来获得->
或者**
的函数定义,就像你通常所做的那样。