Search code examples
ccompiler-constructionbisonflex-lexer

Calc with bison and flex doesn't work operation negative


I have a problem with this Bison program. I don't know why Reuse does not work. For the operation with the negative numbers, I just use the same line for the get the first number and to operate with more operations. I just changed the first number to negative.

calc.y

%{

#include <stdio.h>
#include <stdlib.h>

extern int yylex();
extern int yyparse();
extern FILE* yyin;

void yyerror(const char* s);
%}

%union {
    int ival;
    float fval;
}

%token<ival> T_INT
%token<fval> T_FLOAT
%token T_PLUS T_MINUS T_MULTIPLY T_DIVIDE T_LEFT T_RIGHT
%token T_NEWLINE T_QUIT
%left T_PLUS T_MINUS
%left T_MULTIPLY T_DIVIDE

%type<ival> expression
%type<fval> mixed_expression

%start calculation

%%

calculation: 
       | calculation line
;

line: T_NEWLINE
    | mixed_expression T_NEWLINE { printf("\tResult: %f\n", $1);}
    | expression T_NEWLINE { printf("\tResult: %i\n", $1); } 
    | T_QUIT T_NEWLINE { printf("bye!\n"); exit(0); }
;

mixed_expression: T_FLOAT                        { $$ = $1; }
      | T_MINUS T_FLOAT  { $$ = -$2; }    
      | mixed_expression T_PLUS mixed_expression     { $$ = $1 + $3; }
      | mixed_expression T_MINUS mixed_expression    { $$ = $1 - $3; }
      | mixed_expression T_MULTIPLY mixed_expression { $$ = $1 * $3; }
      | mixed_expression T_DIVIDE mixed_expression   { $$ = $1 / $3; }
      | T_LEFT mixed_expression T_RIGHT      { $$ = $2; }
      | T_MINUS mixed_expression T_RIGHT         { $$ = -$2; }
      | expression T_PLUS mixed_expression       { $$ = $1 + $3; }
      | expression T_MINUS mixed_expression      { $$ = $1 - $3; }
      | expression T_MULTIPLY mixed_expression   { $$ = $1 * $3; }
      | expression T_DIVIDE mixed_expression     { $$ = $1 / $3; }
      | mixed_expression T_PLUS expression       { $$ = $1 + $3; }
      | mixed_expression T_MINUS expression      { $$ = $1 - $3; }
      | mixed_expression T_MULTIPLY expression   { $$ = $1 * $3; }
      | mixed_expression T_DIVIDE expression     { $$ = $1 / $3; }
      | expression T_DIVIDE expression       { $$ = $1 / (float)$3; }
;

expression: T_INT               { $$ = $1; }

      | expression T_PLUS expression    { $$ = $1 + $3; }
      | expression T_MINUS expression   { $$ = $1 - $3; }
      | expression T_MULTIPLY expression    { $$ = $1 * $3; }
      | T_LEFT expression T_RIGHT       { $$ = $2; }
      | T_MINUS T_INT   { $$ = -$2; }
;

%%

int main() {
    yyin = stdin;

    do { 
        yyparse();
    } while(!feof(yyin));

    return 0;
}

void yyerror(const char* s) {
    fprintf(stderr, "Parse error: %s\n", s);
    exit(1);
}

calclex.l

%option noyywrap

%{
#include <stdio.h>

#define YY_DECL int yylex()

#include "calc.tab.h"

%}

%%

[ \t]   ; // ignore all whitespace
[0-9]+\.[0-9]+  {yylval.fval = atof(yytext); return T_FLOAT;}
[0-9]+      {yylval.ival = atoi(yytext); return T_INT;}
\n      {return T_NEWLINE;}
"+"     {return T_PLUS;}
"-"     {return T_MINUS;}
"*"     {return T_MULTIPLY;}
"/"     {return T_DIVIDE;}
"("     {return T_LEFT;}
")"     {return T_RIGHT;}
"exit"      {return T_QUIT;}
"quit"      {return T_QUIT;}

%%

Solution

  • As said in my comment, the problem is called "unary minus" and it describes the trouble of distinguishing the normal subtraction operation a - b from the negation operation 0 - a if it is abbreviated to -a. The common solution is to add a bit of code to make the minus-symbol act as two different operators depending on position. It is done in Bison by implementing a precedence for a non-existing symbol (here NEG) and bind that precedence to the case -a.

    You need to do it twice in your code, once for T_FLOAT and the second time for T_INT. I also deleted one line that made no sense, at least for me.

    calc.y:

    %{
    #include <stdio.h>
    #include <stdlib.h>
    
    extern int yylex();
    extern int yyparse();
    extern FILE* yyin;
    
    void yyerror(const char* s);
    %}
    
    %union {
        int ival;
        float fval;
    }
    
    %token<ival> T_INT
    %token<fval> T_FLOAT
    %token T_PLUS T_MINUS T_MULTIPLY T_DIVIDE T_LEFT T_RIGHT
    %token T_NEWLINE T_QUIT
    %left T_PLUS T_MINUS
    %left T_MULTIPLY T_DIVIDE
    
    %precedence NEG   /* unary minus */
    
    %type<ival> expression
    %type<fval> mixed_expression
    
    %start calculation
    
    %%
    
    calculation: 
           | calculation line
    ;
    
    line: T_NEWLINE
        | mixed_expression T_NEWLINE { printf("\tResult: %f\n", $1);}
        | expression T_NEWLINE       { printf("\tResult: %i\n", $1); } 
        | T_QUIT T_NEWLINE           { printf("bye!\n"); exit(0); }
    ;
    
    mixed_expression: T_FLOAT                            { $$ = $1; }
          | T_MINUS mixed_expression %prec NEG           { $$ = -$2; }    
          | mixed_expression T_PLUS mixed_expression     { $$ = $1 + $3; }
          | mixed_expression T_MINUS mixed_expression    { $$ = $1 - $3; }
          | mixed_expression T_MULTIPLY mixed_expression { $$ = $1 * $3; }
          | mixed_expression T_DIVIDE mixed_expression   { $$ = $1 / $3; }
          | T_LEFT mixed_expression T_RIGHT              { $$ = $2; }
          /* | T_MINUS mixed_expression T_RIGHT             { $$ = -$2; } */
          | expression T_PLUS mixed_expression           { $$ = $1 + $3; }
          | expression T_MINUS mixed_expression          { $$ = $1 - $3; }
          | expression T_MULTIPLY mixed_expression       { $$ = $1 * $3; }
          | expression T_DIVIDE mixed_expression         { $$ = $1 / $3; }
          | mixed_expression T_PLUS expression           { $$ = $1 + $3; }
          | mixed_expression T_MINUS expression          { $$ = $1 - $3; }
          | mixed_expression T_MULTIPLY expression       { $$ = $1 * $3; }
          | mixed_expression T_DIVIDE expression         { $$ = $1 / $3; }
          | expression T_DIVIDE expression               { $$ = $1 / (float)$3; }
    ;
    
    expression: T_INT                                    { $$ = $1; }
          | expression T_PLUS expression                 { $$ = $1 + $3; }
          | expression T_MINUS expression                { $$ = $1 - $3; }
          | expression T_MULTIPLY expression             { $$ = $1 * $3; }
          | T_LEFT expression T_RIGHT                    { $$ = $2; }
          | T_MINUS expression  %prec NEG                { $$ = -$2; }
    ;
    
    %%
    
    int main() {
        yyin = stdin;
        do { 
            yyparse();
        } while(!feof(yyin));
        return 0;
    }
    
    void yyerror(const char* s) {
        fprintf(stderr, "Parse error: %s\n", s);
        exit(1);
    }
    

    The file calclex.l can be left unchanged (although a floating point number is a bit more complicated).