Lassen Sie mich vorab sagen, dass ich absolut nichts darüber weiß, wie Parser funktionieren. Abgesehen davon, Linie 296 von gram.y folgenden Token definiert Zuordnung in den (YACC?) Parser R Verwendungen zu repräsentieren:
%token LEFT_ASSIGN EQ_ASSIGN RIGHT_ASSIGN LBB
In den Zeilen 5140 bis 5150 von gram.c sieht dies dann wie der entsprechende C-Code aus:
case '-':
if (nextchar('>')) {
if (nextchar('>')) {
yylval = install_and_save2("<<-", "->>");
return RIGHT_ASSIGN;
}
else {
yylval = install_and_save2("<-", "->");
return RIGHT_ASSIGN;
}
}
Ab Zeile 5044 von gram.c wird schließlich die Definition von install_and_save2
:
static SEXP install_and_save2(char * text, char * savetext)
{
strcpy(yytext, savetext);
return install(text);
}
Also noch einmal, mit null Erfahrung im Umgang mit Parsern, scheint es , dass ->
und ->>
übersetzt werden direkt in <-
und <<-
jeweils bei einem sehr niedrigen Niveau im Interpretationsprozess.
Sie haben einen sehr guten Punkt angesprochen, als Sie gefragt haben, wie der Parser "weiß", wie er die Argumente umkehren soll ->
- wenn man bedenkt, dass ->
dies in der R-Symboltabelle als installiert zu sein scheint <-
- und somit in der Lage ist, korrekt x -> y
als y <- x
und nicht zu interpretieren x <- y
. Das Beste, was ich tun kann, ist, weitere Spekulationen anzustellen, da ich weiterhin auf "Beweise" stoße, um meine Behauptungen zu stützen. Hoffentlich stolpert ein barmherziger YACC-Experte über diese Frage und gibt einen kleinen Einblick. Ich werde jedoch nicht den Atem anhalten.
Zurück zu den Zeilen 383 und 384 von gram.y sieht dies nach einer weiteren Analyselogik aus , die sich auf die oben genannten LEFT_ASSIGN
und RIGHT_ASSIGN
Symbole bezieht :
| expr LEFT_ASSIGN expr { $$ = xxbinary($2,$1,$3); setId( $$, @$); }
| expr RIGHT_ASSIGN expr { $$ = xxbinary($2,$3,$1); setId( $$, @$); }
Obwohl ich mit dieser verrückten Syntax nicht wirklich Kopf oder Zahl machen kann, habe ich bemerkt, dass das zweite und dritte Argument xxbinary
gegen WRT LEFT_ASSIGN
( xxbinary($2,$1,$3)
) und RIGHT_ASSIGN
( xxbinary($2,$3,$1)
) ausgetauscht werden .
Folgendes stelle ich mir in meinem Kopf vor:
LEFT_ASSIGN
Szenario: y <- x
$2
ist das zweite "Argument" für den Parser im obigen Ausdruck, dh <-
$1
ist die erste; nämlichy
$3
ist der dritte; x
Daher wäre der resultierende (C?) Aufruf xxbinary(<-, y, x)
.
Anwenden dieser Logik auf RIGHT_ASSIGN
, dh x -> y
kombiniert mit meiner früheren Vermutung über <-
und ->
das Tauschen,
$2
wird von ->
nach übersetzt<-
$1
ist x
$3
ist y
Aber da das Ergebnis xxbinary($2,$3,$1)
statt ist xxbinary($2,$1,$3)
, ist das Ergebnis immer noch xxbinary(<-, y, x)
.
Darauf aufbauend haben wir die Definition von xxbinary
in Zeile 3310 von 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;
}
Leider konnte ich keine richtige Definition finden lang3
(oder seine Varianten lang1
, lang2
etc ...) in der R - Quellcode, aber ich gehe davon aus, dass es für die Auswertung spezieller Funktionen verwendet wird (dh Symbole) in einer Weise , die mit synchronisiert der Dolmetscher.
Updates
Ich werde versuchen, einige Ihrer zusätzlichen Fragen in den Kommentaren zu beantworten, so gut ich kann, wenn ich meine (sehr) begrenzten Kenntnisse über den Parsing-Prozess habe.
1) Ist dies wirklich das einzige Objekt in R, das sich so verhält? (Ich habe an das Zitat von John Chambers aus Hadleys Buch gedacht: "Alles, was existiert, ist ein Objekt. Alles, was passiert, ist ein Funktionsaufruf." Dies liegt eindeutig außerhalb dieser Domäne - gibt es noch etwas Ähnliches?
Erstens stimme ich zu, dass dies außerhalb dieser Domäne liegt. Ich glaube, Chambers 'Zitat betrifft die R-Umgebung, dh Prozesse, die alle nach dieser Analysephase auf niedriger Ebene stattfinden. Ich werde dies jedoch weiter unten etwas näher erläutern. Das einzige andere Beispiel für diese Art von Verhalten, das ich finden konnte, ist der **
Operator, ein Synonym für den häufigeren Exponentiationsoperator ^
. Wie bei der richtigen Zuweisung, **
scheint vom Interpreter nicht als Funktionsaufruf usw. "erkannt" zu werden:
R> `->`
R> `**`
Ich habe dies gefunden, weil es der einzige andere Fall install_and_save2
ist, in dem der C-Parser verwendet wird :
case '*':
if (nextchar('*')) {
yylval = install_and_save2("^", "**");
return '^';
} else
yylval = install_and_save("*");
return c;
2) Wann genau passiert das? Ich habe daran gedacht, dass der Ersatz (3 -> y) den Ausdruck bereits umgedreht hat. Ich konnte aus der Quelle nicht herausfinden, welcher Ersatz das YACC gepingt hätte ...
Natürlich spekuliere ich hier immer noch, aber ja, ich denke, wir können davon ausgehen, dass der Ausdruck , wenn Sie ihn substitute(3 -> y)
aus der Perspektive der Ersatzfunktion aufrufen , immer war y <- 3
; zB ist der Funktion völlig unbekannt, dass Sie eingegeben haben 3 -> y
. do_substitute
Wie 99% der von R verwendeten C-Funktionen werden nur SEXP
Argumente verarbeitet - und EXPRSXP
im Fall von 3 -> y
(== y <- 3
) glaube ich. Darauf habe ich oben angespielt, als ich zwischen der R-Umgebung und dem Parsing-Prozess unterschieden habe. Ich glaube nicht, dass irgendetwas den Parser speziell dazu veranlasst, aktiv zu werden - sondern alles , was Sie in den Interpreter eingeben, wird analysiert. Ich habe ein wenig getanLesen Sie mehr über den YACC / Bison-Parser- Generator letzte Nacht, und so wie ich es verstehe (auch bekannt als Wette die Farm nicht darauf), verwendet Bison die von Ihnen definierte Grammatik (in den .y
Dateien), um einen Parser in C - zu generieren. dh eine C-Funktion, die das eigentliche Parsen der Eingabe durchführt. Im Gegenzug wird alles, was Sie in eine R-Sitzung eingeben, zuerst von dieser C-Analysefunktion verarbeitet, die dann die entsprechenden Aktionen delegiert, die in der R-Umgebung ausgeführt werden sollen (ich verwende diesen Begriff übrigens sehr locker). In dieser Phase lhs -> rhs
wird nach rhs <- lhs
, **
nach ^
usw. übersetzt. Dies ist beispielsweise ein Auszug aus einer der Tabellen mit primitiven Funktionen in 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}},
Sie werden feststellen , dass ->
, ->>
und **
sind hier nicht definiert. Soweit ich weiß, sind primitive R-Ausdrücke wie <-
und [
usw. die engste Interaktion, die die R-Umgebung jemals mit einem zugrunde liegenden C-Code hat. Ich schlage vor, dass der Parser zu diesem Zeitpunkt (von der Eingabe eines festgelegten Zeichens in den Interpreter bis zur Eingabe eines gültigen R-Ausdrucks durch Drücken der Eingabetaste) bereits seine Magie entfaltet hat, weshalb Sie können keine Funktionsdefinition für ->
oder **
durch Umgeben mit Backticks erhalten, wie Sie es normalerweise können.