让我先说一句,我对解析器的工作原理一无所知.话虽如此,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);
}
同样地,由于没有使用解析器的经验,在解释过程中,->
和->>
似乎分别在very low level处被直接翻译成<-
和<<-
.
您提出了一个非常好的问题,即解析器如何"知道"将参数反转为->
(考虑到->
似乎作为<-
安装到R符号表中),从而能够正确地将x -> y
解释为y <- x
和not 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
,再加上我之前关于<-
和->
被交换的猜想,
但由于结果是xxbinary($2,$3,$1)
而不是xxbinary($2,$1,$3)
,因此结果是still 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
(或其变体lang1
、lang2
等)的正确定义在R源代码中,但我假设它用于以与解释器同步的方式计算特殊函数(即符号).
Updates
1) 这真的是R中唯一一个这样的对象吗??(我已经
首先,我同意这不属于该领域.我相信钱伯斯的话与R环境有关,也就是说,所有的过程都发生在这个低级解析阶段之后.不过,我将在下面进一步讨论这个问题.无论如何,我能找到的这种行为的另一个例子是**
运算符,它是更常见的求幂运算符^
的同义词.与right赋值一样,**
似乎不被"识别"为函数调用,等等...翻译:
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的Angular 来看,表达always was y <- 3
;e、 g.函数完全不知道您键入了3 -> y
.do_substitute
,就像R使用的99%的C函数一样,只处理SEXP
个参数——我相信在3 -> y
(=y <- 3
)的情况下是EXPRSXP
个参数.这就是我在上面对R环境和解析过程进行区分时提到的.我不认为有任何东西会特别触发解析器启动,而是让您输入到解释器中的内容得到解析.昨晚我又读了little篇关于YACC/Bison解析器generator的文章,据我所知(也就是说,不要把赌注押在这个问题上),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代码之间最密切的交互.我想说的是,在这个过程中(从你在解释器中输入一组字符,然后点击"回车",直到对一个有效的R表达式进行实际计算),解析器已经发挥了它的魔力,这就是为什么你不能通过在->
或**
中加上反勾来获得函数定义,就像你通常能做的那样.