Грамматика Yacc не уменьшает последний литерал ввода правильно


Это код для генерации кода адреса 3 для арифметического выражения.

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

Пример: x = 1 + 2 * 3 - 4
1,2,3 читаются правильно. Даже с минусом. Когда он читает 4, он может сократить грамматику на 2 шага. Затем выдает ошибку.

Я поставил печать заявления о помощи. Это весь код. Проблема при чтении последнего литерала:

x -> IDEN | NUM | ....  
f -> x  
t -> f (This doesn't happen)  

Icg.y

%{
    #include<stdio.h>
    #include<stdlib.h>
    int temp_reg_num = 0;
    void print_three_address_code(char* text1,char oper,char* text2)
    {
        printf("t%d = %s %c %sn",temp_reg_num,text1,oper,text2);
    }   
%}

%token IDEN, NUM, FLOAT 
%union {
    char* text;
}

%left '+' '-'
%left '*' '/'

%%
s : IDEN '=' e 'n' {
    printf("startn");
    if($3.text == NULL)
        printf("%s = t%dn",$1.text,temp_reg_num-1);
    else
        printf("%s = %sn",$1.text,$3.text);
    return 0;
}
;
e : e '+' t {
    printf("e -> e + tn");
    char temp[5];
    print_three_address_code($1.text,'+',$3.text);
    sprintf(temp,"t%d",temp_reg_num++);
    $$.text = copy_string(temp);
}
  | e '-' t {
    printf("e -> e - tn");
    char temp[5];
    print_three_address_code($1.text,'-',$3.text);
    sprintf(temp,"t%d",temp_reg_num++);
    $$.text = copy_string(temp);
}
  | t {
    printf("e -> tn");
    $$.text = copy_string($1.text);
}
;
t : t '*' f {
    printf("t -> t * fn");
    char temp[5];
    print_three_address_code($1.text,'*',$3.text);
    sprintf(temp,"t%d",temp_reg_num++);
    $$.text = copy_string(temp);
}
  | t '/' f {
    printf("t -> t / fn");
    char temp[5];
    print_three_address_code($1.text,'/',$3.text);
    sprintf(temp,"t%d",temp_reg_num++);
    $$.text = copy_string(temp);
}
  | f {
    printf("t -> fn");
    $$.text = copy_string($1.text);
}
;
f : f '^' x {
    printf("f -> f ^ xn");
    char temp[5];
    print_three_address_code($1.text,'^',$3.text);
    sprintf(temp,"t%d",temp_reg_num++);
    $$.text = copy_string(temp);
}
  | x {
    printf("f -> xn");
    $$.text = copy_string($1.text);
    printf("Why syntax error??");
}
;
x : '(' s ')' {}
      | NUM {
    printf("x -> NUMn");
    $$.text = copy_string($1.text);
}
      | IDEN {
    printf("x -> IDENn");
    $$.text = copy_string($$.text,$1.text);
}
      | FLOAT {
    printf("x -> FLOATn");
    $$.text = copy_string($1.text);
}
      | '-' x {
    printf("x -> - xn");
    $$.text = (char*)malloc(sizeof(char)*(strlen($2.text)+2));
    $$.text[0] = '-';
    strcat($$.text,$2.text);
}
;
%%

main()
{
    printf("Enter your expression : ");
    yyparse();
}
yyerror()
{
    printf("Syntax Errorn");
}
yywrap()
{
    return 1;
}

Icg.l

%{
    #include<stdio.h>
    #include<stdlib.h>
    #include"y.tab.h"
    char* copy_string(char* fromstring)  
    {
        char* tostring = (char*)malloc(sizeof(char)*(strlen(fromstring)+1));  
        strcpy(tostring,fromstring);  
        return tostring;  
    }
%}

iden [a-zA-Z_][a-zA-Z_]*  
%%  
[0-9]+ {  
    yylval.text = copy_string(yytext);  
    return NUM;  
}
[0-9]+[.][0-9]+ {  
    yylval.text = copy_string(yytext);  
    return FLOAT;  
}
{iden} {  
    yylval.text = copy_string(yytext);  
    return IDEN;  
}  
[+] {  
    return '+';  
}  
[-] {  
    return '-';  
}  
[*] {  
    return '*';  
}  
[/] {  
    return '/';  
}  
['^'] {  
    return '^';  
}  
[=] {  
    return '=';  
}  
'n' {  
    return 'n';  
}  
. {}  
%%
1 2

1 ответ:

Вы получаете синтаксическую ошибку из-за этого правила:

'\n' { return '\n'; }

Flex не считает ' каким-либо особенным, так что это правило будет (только) соответствовать последовательности из трех символов'войдите'. Это не отображается во входных данных, поэтому правило, которое требует \n, не будет выполнено, и когда вы предоставляете конец файла (или конец файла, если Вы читаете из файла), то вы получите синтаксическую ошибку.

Итак, что вы должны написать is:

\n { return '\n'; }

Также,

['^'] 

Не только соответствует ^. Он также соответствует ', потому что, как указано выше, ' никоим образом не является специальным и поэтому класс символов состоит из трех символов,один из которых повторяется. Чтобы точно соответствовать символу ^, используйте двойные кавычки (которые являются специальными):

"^"

(однако, "\n" не будет работать, если только то, что вы хотите сопоставить, не является точно \, за которым следует n. Просто \n это то, что вы хотите.)

Ты на самом деле можно упростить все эти правила одного символа в одно правило:

[-+*/^()=\n]   { return yytext[0]; }

Я включил ( и ), хотя они не были в вашей icg.L файл, потому что вы используете их в своей грамматике.

Однако правило, в котором вы их используете, неверно:

x : '(' s ')' {}

Поскольку s - это назначение. Это позволило бы

x = 1 + (y = 2 * 3) - 4

Но не допускайте более нормального

x = 1 + (2 * 3) - 4

Чего вы хотите, так это:

x : '(' e ')' { $$ = $2; }

В вашей грамматике есть и другие ошибки., в том числе:

  • отсутствует заголовок #include <string.h> (для strlen)
  • пропущенные объявления для yylex, yyerror и copy_string
  • неправильные (или, точнее, устаревшие) прототипы для main и yyerror. "Современный C" - как и в последние 30 лет или около того-предпочитает явные типы возврата, а начиная с C99 (1999) требует их.
  • экземпляр copy_string с двумя аргументами вместо одного, что является неопределенным поведением. (Это было бы помечено как ошибка, если бы вы предоставили декларация для copy_string.)

Ваш icg.файл y был обработан без жалоб Berkeley yacc (byacc), но bison не будет обрабатывать его даже в режиме совместимости с yacc. Проблема заключается в том, что вы используете $1.text и не можете объявить теги ваших терминалов и нетерминалов. Если вы объявляете тип union, вы должны предоставить объявления тегов как для ваших терминалов:

%token <text> NUM IDEN FLOAT

И ваши не-терминалы (или, по крайней мере, те, которые имеют семантические значения):

%type <text> s e t f x
Тогда вы можете удалить все селекторы .text из ваших действий, так как bison (и даже byacc) будет знать, какой селектор использовать. Объявление тегов маркеров делает ваш синтаксический анализатор типобезопасным или, по крайней мере, менее типобезопасным, а также обычно более читаемым. Наконец, вам нужно поработать над управлением памятью. Вы никогда не free() ни одной из строк, которые вы выделили в string_copy, поэтому вы постепенно создаете довольно много утечек памяти. Кроме того, вы копируете во многих случаи, когда это не требуется; например, в единичном правиле:
x : NUM { $$.text = copy_string($1.text); }

Копия совершенно не нужна, так как $1 вот-вот выскочит из стека синтаксического анализатора, и строка, на которую он ссылается, больше никогда не будет использоваться. Так что вы просто напрасно сливаете копию. Гораздо чище было бы

x : NUM { $$ = $1; }
Но это на самом деле не нужно, потому что это действие по умолчанию.

Изменение единичных производств не остановит другие утечки памяти; в производствах, которые на самом деле сделайте что-нибудь с семантическими значениями, кроме их передачи, вам все равно придется вручную free копировать строки, если они вам больше не нужны (что, похоже, имеет место во всех ваших семантических действиях). Или вам нужно будет выяснить, как освободить их позже, если они вам понадобятся позже.

Вы можете использовать встроенную функцию трассировки yacc или bison, а не набивать свою программу printf s, которые вам позже придется удалить. Просто предоставьте опцию -t, Когда вы выполнить yacc или добавить

#define YYDEBUG 1

К' %{...% } 'блок в вашей icg.y файл. Затем измените свою основную функцию:

int main() {
  yydebug = 1;
  return yyparse();
}
Это позволит вам точно знать, что происходит во время анализа входных данных, включая информацию о том, какие терминалы принимаются от сканера.
Вот полный пример, показывающий один из подходов к управлению памятью. Он основан на вашем коде, но с определенным количеством рефакторинга. Обратите внимание, что синтаксический анализатор никогда не копирует строку, но emit_tac выделяет новую строку, а также освобождает строки, которые передаются в качестве аргументов. (Некоторые наблюдатели могут счесть это грубостью.) Вызов yylex_destroy() в main требуется для современных flex-генерируемых сканеров, чтобы освободить ресурсы, выделенные лексером. Bison-specific %destructor предотвращает утечку памяти в случае синтаксической ошибки: если вы не используете bison, вам придется найти другое решение этой проблемы.

Icg.y

%{
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    int yylex();
    int yylex_destroy();
    void yyerror(const char* msg);

    int temp_reg_num = 0;
    char* emit_tac(char* text1, char oper, char* text2) {
        char* rv = malloc(8);
        snprintf(rv, 7, "t%d", temp_reg_num++);
        printf("%s = %s %c %s\n", rv, text1 ? text1 : "", oper, text2);
        free(text1); free(text2);
        return rv;
    }
%}

%union {
    char* text;
}
%token <text> IDEN NUMBER
%type <text> e t f x
%destructor { free($$); } <text>

%%
s : IDEN '=' e '\n' { printf("%s = %s\n", $1, $3);
                      free($1); free($3);
                    }
e : e '+' t         { $$ = emit_tac($1,'+',$3); }
  | e '-' t         { $$ = emit_tac($1,'-',$3); }
  | t
t : t '*' f         { $$ = emit_tac($1,'*',$3); }
  | t '/' f         { $$ = emit_tac($1,'/',$3); }
  | f
f : f '^' x         { $$ = emit_tac($1,'^',$3); }
  | x 
x : IDEN 
  | NUMBER
  | '-' x           { $$ = emit_tac(NULL, '-', $2); }
  | '(' e ')'       { $$ = $2; }
%%

int main() {
    int rc = yyparse();
    yylex_destroy();
    return rc;
}

void yyerror(const char* msg) {
    printf("%s\n", msg);
}

Icg.l (требуется flex)

%{
    /* Change to y.tab.h if you use yacc */
    #include "icg.tab.h"
    char* copy_yytext() {
        char* tostring = malloc(yyleng + 1);
        memcpy(tostring, yytext, yyleng);
        tostring[yyleng] = 0;
        return tostring;  
    }
%}
%option noinput nounput noyywrap nodefault

%%

\n                           { return '\n'; }
[[:space:]]                  /* Ignore */
[[:digit:]]+(\.[[:digit]]*)? { yylval.text = copy_yytext(); return NUMBER;  }
[[:alpha:]_][[:alnum:]_]*    { yylval.text = copy_yytext(); return IDEN; } 
.                            { return yytext[0]; }  

Компиляция

$ bison -d icg.y
$ flex icg.l
$ gcc -Wall -o icg lex.yy.c icg.tab.c

Тест с valgrind для демонстрации отсутствия утечек памяти

$ valgrind --leak-check=full ./icg <<< ' x = 1 + 2 - 3 / 4 ^ 5 * ( 6 - 9 )'
==26225== Memcheck, a memory error detector
==26225== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==26225== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info
==26225== Command: ./icg
==26225== 
t0 = 1 + 2
t1 = 4 ^ 5
t2 = 3 / t1
t3 = 6 - 9
t4 = t2 * t3
t5 = t0 - t4
x = t5
==26225== 
==26225== HEAP SUMMARY:
==26225==     in use at exit: 0 bytes in 0 blocks
==26225==   total heap usage: 17 allocs, 17 frees, 16,530 bytes allocated
==26225== 
==26225== All heap blocks were freed -- no leaks are possible
==26225== 
==26225== For counts of detected and suppressed errors, rerun with: -v
==26225== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)