2014-12-15 3 views
0

Я пытаюсь воспроизвести, как cout работает с использованием flex и bison. Например:Bison печатает мой пользовательский cout назад

cout << "hello world"; 

напечатает:

hello world 

Это работает прекрасно, когда есть только один параметр, но у меня есть проблема, когда я ставлю несколько переменных.

cout << "I like " << "bananas" << endl; 

напечатает

\nbananasI like 

вместо

I like bananas\n 

Я думаю, что это потому, что он анализирует всю линию перед печатью, так что начинается с «End L,» затем «бананы», а затем «Мне нравится», но я не понимаю, как это сделать. Я попытался изменить свой токен в нескольких настройках, чтобы отменить приоритет, например, для умножения и добавления, но пока не добился успеха.

Эти полезные части myfile.y

%union { 
    char* string; 
} 

%token <string> STRING 
%token ENDL 
%token COUT 
%token INSERT 

%% 

displayBegin : 
    COUT displayContent 
; 

displayContent : 
    ';' 
| INSERT displayEnd 
| INSERT STRING displayContent { printf("%s",$2); } 
; 

displayEnd : 
    ENDL ';' { printf("\n"); } 
; 

%% 

Одинаковые для myfile.l

"cout"     { return COUT; } 
"endl"     { return ENDL; } 
"<<"     { return INSERT; } 
[\"][^\"]+[\"]   { char* tmp = strdup(yytext); tmp++; tmp[strlen(tmp)-1] = '\0'; yylval.string = tmp; return STRING; } 

Я отредактированные myfile.y, как следовать, и он работает, но, как это дважды рекурсивный это не так здорово (показывает предупреждение во время компиляции):

displayBegin : 
    COUT INSERT displayContent 
; 

displayContent : 
    ';' 
| displayEnd 
| STRING { printf("%s",$1); } 
| displayContent INSERT displayContent 
; 

ответ

1

В C++ оператор <<левый ассоциативный, что означает, что a << b << c - это то же самое, что и у ((a << b) << c). Если он был право-ассоциативным (например, оператор присваивания), группировка была бы вложенной в другом направлении: (a << (b << c)).

Хотя это выглядит чисто семантическим, это также отражает то, как строки анализируются. В частности, сначала необходимо уменьшить внутреннее выражение , потому что результат сокращения является одним из аргументов для следующих самых внутренних терминалов в следующем самом внутреннем выражении. И если оценка выполняется действиями по сокращению, порядок оценки будет определяться порядком сокращения.

Короче говоря, если вы хотите сделать немедленную оценку, и вы хотите, чтобы оценка << была слева направо, вам необходимо убедиться, что << разобран как левый ассоциативный.

В общем смысле левоассоциативные операторы связаны с леворекурсивными производными, а право-ассоциативные операторы связаны с праворекурсивными произведениями.Ваше первоначальное определение прав рекурсивного, так что производство

displayContent: INSERT STRING displayContent 

требует, чтобы displayContent на праве быть уменьшен (и, следовательно, в печатном виде) до STRING может быть включен в наружном displayContent. Другими словами, он оценивает право налево, что нежелательно. (Он также не может действительно представить семантику выражения, так как оператор не связан с его левым операндом.)

Ваше второе определение неоднозначно, хотя это можно было бы зафиксировать с объявлением о начале. Но полезно написать грамматику в недвусмысленной форме, хотя бы для того, чтобы показать, что делает объявление приоритета. Так вот левоассоциативной (и левая рекурсивный) грамматика:

displayStatement: display ';' ; 

display: COUT 
     | display "<<" STRING { printf("%s",$3); } 
     | display "<<" ENDL { putchar('\n'); } 
     ; 

В отличие от оригинальной грамматики, это одна позволяет несколько экземпляров endl и не требует, чтобы линия заканчиваться одним. Так что он намного ближе к C++. Но он все еще не совсем отражает семантику C++, поскольку он не показывает, как используется левый оператор <<. В частности, левым оператором и результатом являются потоки; в терминах C, это может быть FILE*. Так давайте делать это таким образом:

%union { 
    char* string; 
    FILE* file; 
} 

%token <string> STRING 
%token ENDL "endl" 
%token COUT "cout" 
%token INSERT "<<" 
%type <file> display 

%% 

displayStatement: display ';' ; 

display: "cout"    { $$ = stdin;      } 
     | display "<<" STRING { fprintf($1, "%s", $3); $$ = $1; } 
     | display "<<" "endl" { putc('\n', $1);  $$ = $1; } 
     ; 

Это будет сделать намного проще добавить возможность вывода на stderr; все, что необходимо, это добавить cerr в лексер и добавить произведение к display: «cerr» {$$ = stderr; } `.

В заключение вы правильно звоните strdup в свой сканер, чтобы вы могли передать копию yytext в парсер. Но вы никогда не повторяете дублируемую строку, чтобы вы потеряли память. В качестве первого шага, вы можете освободить строку после печати, что делает действие второго display производства по:

{ fprintf($1, "%s", $3); free($3); $$ = $1; } 

Также обрати внимание на %destructor декларации бизона, чтобы увидеть, как избежать утечек памяти в случае синтаксическая ошибка.

0

Вход обрабатывается рекурсивно. Правило будет проанализировано успешно после того, как все в нем будет проанализировано. Таким образом, в вашем случае, первое, что нужно быть проанализировано успешно будет displayEnd, печатью \n, то, что получает подкатил к немедленному верхнему уровню, который затем получает напечатанный и т.д.

+0

О, да, я вижу. Однако я не вижу, как разбирать его по-разному. Я сделал это «двойной рекурсивный» ('displayContent INSERT displayContent'), как я делаю свои операции, и он отображает правильно, но из-за этого он отображает предупреждение (сдвиг/уменьшение). Это лучше, на мой взгляд, так как это работает, но я хотел бы заставить его работать без предупреждения. – Ananas

+0

Нет необходимости разбирать его по-разному. Например, вы можете распечатать все это, как только вы закончите синтаксический анализ всего (в правиле 'displayBegin') – Eduardo

Смежные вопросы