首先,让我说我对解析器的工作原理一无所知。话虽如此,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
:
static SEXP install_and_save2(char * text, char * savetext)
{
strcpy(yytext, savetext);
return install(text);
}
如此反复,具有解析器零经验的工作,似乎->
和->>
直接翻译成<-
和<<-
分别,在一个非常低的水平在解释过程。
你在问解析器如何“知道”的论点相反带来了一个非常好的问题->
考虑-->
似乎是安装转换为R符号表中<-
-从而能够正确地解释x -> y
为y <- x
并没有 x <- y
。我能做的最好的就是进一步猜测,因为我继续遇到“证据”来支持我的主张。希望一些仁慈的YACC专家会偶然发现这个问题并提供一些见识。不过,我不会屏住呼吸。
回到gram.y的第383和384行,这看起来像是一些与上述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)
,结果仍然是 xxbinary(<-, y, x)
。
这个建筑离远一点,我们的定义xxbinary
上gram.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
(或它的变体lang1
,lang2
等...),在R源代码,但是我假设它是用于在与同步的方式评估特殊功能(即符号)口译员。
更新
我将尽我所能(非常)有限的解析过程知识,尝试在注释中解决您的一些其他问题。
1)这真的是R中唯一的行为是这样的对象吗?(我想起了约翰·钱伯斯(John Chambers)在哈德利(Hadley)的书中的话:“存在的一切都是对象。发生的一切都是函数调用。”这显然位于该域之外-还有其他类似的东西吗?
首先,我同意这不属于该领域。我相信钱伯斯的名言涉及R环境,即所有在此低层解析阶段之后进行的过程。不过,我将在下面进一步介绍这一点。无论如何,我可以找到的这种行为的唯一另一个例子是**
运算符,它是更常见的幂运算符的同义词^
。与正确的赋值一样,解释**
器似乎没有将其“识别”为函数调用等:
R> `->`
R> `**`
我发现这是因为这install_and_save2
是C解析器使用的唯一其他情况:
case '*':
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 -> y
。do_substitute
就像R使用的99%的C函数一样,只能处理SEXP
参数-我相信EXPRSXP
在3 -> y
(== y <- 3
)的情况下。这是我在R环境和解析过程之间进行区分时所提到的内容。我不认为这有什么特异触发解析器春天付诸行动-而是一切您输入到解释被解析。我做了一点昨晚有更多关于YACC / Bison解析器生成器的信息,据我了解(也不要打赌农场),Bison使用您定义的语法(在.y
文件中)在C中生成解析器-即执行输入的实际解析的C函数。反过来,您在R会话中输入的所有内容都首先由C解析函数处理,然后委派要在R环境中执行的适当操作(顺便说一句,我非常宽松地使用了该术语)。在这个阶段,lhs -> rhs
将得到翻译为rhs <- lhs
,**
以^
等..例如,这是从一个摘录的names.c基本功能表:
{"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表达式进行实际评估为止),解析器已经发挥了魔力,这就是为什么通常情况下,您将无法获得函数定义->
或将**
其用反引号引起来。