让我先说我对解析器的工作原理一无所知。话虽如此,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 -> 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 (@987654352 @)。
这是我脑海中的想象:
LEFT_ASSIGN 场景:y <- x
-
$2 是上述表达式中解析器的第二个“参数”,即<-
-
$1是第一个;即y
-
$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)。
在此基础上更进一步,我们在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(或其变体 lang1、lang2 等)的正确定义,但我假设它用于评估特殊以与解释器同步的方式执行函数(即符号)。
更新
鉴于我对解析过程的(非常)有限的知识,我将尽我所能解决您在 cmets 中的一些其他问题。
1) 这真的是 R 中唯一表现出这种行为的对象吗? (我有
想起了约翰·钱伯斯在哈德利的书中引用的一句话:“一切
存在的是一个对象。发生的一切都是函数调用。”
这显然位于该域之外 - 还有其他类似的东西吗
这个?
首先,我同意这在该域之外。我相信钱伯斯的引用涉及 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) 这究竟是什么时候发生的?我已经记住了那个替代品(3
-> y) 已经翻转了表达式;我无法从源头上弄清楚什么替代品会 ping YACC...
当然我仍然在这里推测,但是是的,我认为我们可以有把握地假设,当您调用substitute(3 -> y) 时,从the substitute function 的角度来看,表达式总是 @987654388 @;例如该函数完全不知道您输入了3 -> y。 do_substitute,就像 R 使用的 99% 的 C 函数一样,只处理 SEXP 参数 - 我相信在 3 -> y 的情况下是 EXPRSXP(== y <- 3)。这就是我在上面区分 R 环境和解析过程时所暗示的内容。我不认为有任何东西可以特别触发解析器启动 - 而是您输入到解释器中的 所有内容 都会被解析。昨晚我又一点阅读了有关 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 代码最接近的交互。我的建议是,在这个阶段(从你在解释器中输入一组字符并点击“Enter”,一直到对有效 R 表达式的实际评估),解析器已经发挥了它的魔力,这就是为什么您无法像通常那样通过用反引号括起来来获得 -> 或 ** 的函数定义。