LINUX.ORG.RU

LR(1) с помощью GNU Bison

 , ,


0

1

Есть вот такая КС-грамматика:

S::=E
E::=E+T
E::=T
T::=T*F
T::=F
F::=c|d|e|(E)
Вот такой файл с bison-кодом:
%{
        #define YYSTYPE char
        #include <stdio.h>
        int yylex (void);
        int yyerror (const char *s);
%}

%token 'c' 'e' 'd' '(' ')'

%%
S: E'\n' { printf("Правильная строка\n"); }
;
E: E'+'T
   |T
;
T: T'*'F
   |F
;
F: 'c'
   |'e'
   |'d'
   |'('E')'
;
%%
Вот такой файл с C-кодом:
#include <stdio.h>
#include <ctype.h>
#include "bison.tab.hh"
int yyparse(void); 

int yyerror (const char *s)  /* Called by yyparse on error */
{
    printf ("%s\n", s);
}

int yylex(void) {
        int c;
        while ( (c = getchar()) == ' ' || c == '\t' );
        if (c == EOF)
                return 0;
        return c;
}

int main (void)
{
        return yyparse();
}

Всё это компилируется, и запускается, но во второй раз почему-то работает совсем не так, как во второй:

$ ./a.out 
e+c
Правильная строка
e+c
syntax error
Что я не так сделал-то?


Я yacc (форк чего является bison) уже подзабыл - давно в студенческие годы только сталкивался, но думаю у тебя проблема в этом:

S: E'\n' { printf("Правильная строка\n"); }
;
а точнее в символе '\n' в грамматике. Попробуй без него

comp00 ★★★★
()
Ответ на: комментарий от kosc

Судя по докам токены должны быть без кавычек. Плюс, у тебя не объявлен приоритет операций. Добавь

%left '+'             
%left '*'  
Сразу после токена.

И скобки из токенов убери.

Возможно придется изменить лексический анализатор (попробуй что нибудь поделать). Домой приду - посмотрю сои старые работы, может помогу чем.

comp00 ★★★★
()
Последнее исправление: comp00 (всего исправлений: 2)
Ответ на: комментарий от comp00

Спасибо, с приоритетом действительно надо было подправить, но вот с повторным вводом пока всё то же самое. И скобки убрал из токенов, да.

kosc
() автор топика

А у вас тут нет второго раза. Работает всё ещё первый раз, т.к. выхода из yyparse() ведь не произошло.

Чтобы работало так, как вам надо (я включил режим телепата), уберите '\n' в .y-файле, чтобы было так:

S: E { printf("Правильная строка\n"); }

А в функции yylex() при c == '\n' возвращайте 0, как при EOF. Затем в main() вызывайте yyparse() в бесконечном цикле, с проверкой возвращаемого значения на ошибку.

Sorcerer ★★★★★
()
Ответ на: комментарий от Sorcerer

И тогда printf() лучше тоже вызывать снаружи парсера, из main(). А если вы хотите одним парсером парсить сразу несколько строк, тогда придётся это явно задать в нём:

MS: S
   |S MS
;
S: E'\n' { printf("Правильная строка\n"); }
;
Sorcerer ★★★★★
()
Ответ на: комментарий от Sorcerer

Спасибо большое, теперь всё работает как надо. Честно говоря, понимал, что проблема скорее всего в main, но не понимал, как с ним надо работать, потому что оно возвращает yyparse() (из загугленого кода, само собой), и не понимал, как его «возвращать» в бесконечном цикле.

kosc
() автор топика
Ответ на: комментарий от Sorcerer

В общем, решил уж дотюнить это задание до конца, осталась одна проблема:

$ ./a.out 
e+cd
Ошибка: неправильная строка
Ошибка: неправильная строка
Несмотря на:
#include <stdio.h>
#include <ctype.h>
#include «bison.tab.hh»
int yyparse(void); 

int yyerror (const char *s)  // Called by yyparse on error 
{
    // printf («%s\n», s);
}

int yylex(void) {
	int c;
	while ( (c = getchar()) == ' ' || c == '\t' );
	if (c == EOF || c == '\n')
		return 0;
	return c;
}

int main (void)
{
	while (1) {
		if (!yyparse())
			printf(«Правильная строка\n»);
		else {
			printf(«Ошибка: неправильная строка\n»);
			continue;
		}
	}
}

kosc
() автор топика
Ответ на: комментарий от kosc

Это проблема уже в yylex(): он у вас сейчас не знает, правильная строка или неправильная, а просто выдаёт байты. yyparse() первый раз вывалился после получения 'd', а второй раз - после получения '\n'. Чтобы этого избежать, нужно «переинициализировать» лексер, т.е. в вашем случае после неудачного yyparse() дочитывать оставшиеся до '\n' или до EOF байты, если '\n' или EOF не были считаны в последний раз.

Sorcerer ★★★★★
()
Ответ на: комментарий от Sorcerer

Но если я читаю байты в yylex, который про неправильность строки не знает, а проверяю строку в main, то как мне в main дочитать строку, которая уже читается в yylex?

kosc
() автор топика
Ответ на: комментарий от Sorcerer

И я всё же не понял эту концепцию. Сколько раз вызывается yyparse() за одну итерацию цикла? Логика подсказывает, что один. Практика подсказывает, что по разу на каждый байт. В общем, я запутался окончательно :(

kosc
() автор топика
Ответ на: комментарий от kosc

Проще всего ввести глобальную переменную - последнее возвращенное yylex()'ом значение. И сделать функцию yylex_reinitialize(), которая будет вызывать getchar() до тех пор, пока не встретит '\n' или EOF, но только в том случае, если глобальная переменная не равна 0. Эту новую функцию нужно вызывать после неуспешного yyparse(). Так вы вычитаете весь ненужный остаток строки, тем самым подготовив yylex() к обработке новой строки.

Sorcerer ★★★★★
()
Последнее исправление: Sorcerer (всего исправлений: 1)
Ответ на: комментарий от kosc

Вставьте отладочный printf в yylex(), чтобы всё стало понятно. Просто печатайте каждый прочитанный байт.

Sorcerer ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.