tomdawncats
@tomdawncats

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

Cで書かれたflex, bisonプログラムをC++のflex, bisonに変換したい(長文注意)

解決したいこと

flex、bisonのプログラムを、C++用のものに変換したいです。

ソースコード

calc.l
%option c++

%%

"+"   { return ADD; }
"-"   { return SUB; }
"*"   { return MUL; }
"/"   { return DIV; }
"("   { return '('; }
")"   { return ')'; }
"\n"  { return NL; }

([1-9][0-9]*)|0|([0-9]+\.[0-9]*) {
    double temp;
    sscanf(yytext, "%lf", &temp);
    yylval.double_value = temp;
    return NUM;
}

"-"([1-9][0-9]*)|0|([0-9]+\.[0-9]*) {
    double temp;
    sscanf(yytext, "%lf", &temp);
    yylval.double_value = temp;
    return NUM;
}

[ \t] ;

. {
    fprintf(stderr, "lexical error.\n");
    exit(1);
}

%%
calc.y
%{
    #include <stdio.h>
    #include <stdlib.h>

    static void yyerror(const char* s)
    {
        fputs(s, stderr);
        fputs("\n", stderr);
    }

    static int yywrap(void)
    {
        return 1;
    }
%}

%union {
    double double_value;
}

%type <double_value> expr
%token <double_value> NUM
%token ADD SUB MUL DIV
%token NL
%left '+' '-'
%left '*' '/'
%expect 16

%%

program     : statement
            | program statement
            ;

statement   : expr NL
                {
                    printf("%g\n", $1);
                }
            ;

expr        : NUM
            | '(' expr ')' 
                {
                    $$ = $2;
                }
            | expr ADD expr
                {
                    $$ = $1 + $3;
                }
            | expr SUB expr
                {
                    $$ = $1 - $3;
                }
            | expr MUL expr
                {
                    $$ = $1 * $3;
                }
            | expr DIV expr
                {
                    if ($3 != 0) {
                        $$ = $1 / $3;
                    } else {
                        fprintf(stderr, "division by zero\n");
                        exit(1);
                    }
                }
            ;

%%

#include "lex.yy.c"

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

現在わかっていること

色々と調べた結果、flexの方は最初に%option c++をつけるだけで良いと判明しました。

自分で試したこと

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

の部分を

#include <cstdio>
#include <cstdlib>

に変えてみたりしたのですが、

/usr/bin/ld: /tmp/ccQaJpf8.o: in function `yyparse()':
calc.tab.cc:(.text+0x361): undefined reference to `yylex()'
/usr/bin/ld: /tmp/ccQaJpf8.o:(.data.rel.ro._ZTV11yyFlexLexer[_ZTV11yyFlexLexer]+0x68): undefined reference to `yyFlexLexer::yywrap()'
collect2: error: ld returned 1 exit status

このようなエラーが出たりしてうまく行きませんでした。

動作環境

OS : ZorinOS
bison : 3.8.2
flex : 2.6.4

コンパイル

今の所

gcc calc.tab.c -o calc
0

1Answer

flex, bisonでC++のコードを生成するのはお勧めしません。

ネット上にほとんど情報がありませんし、あっても旧バージョンの記法が混在していますし、flexに至っては「experimental」であると書かれています。実際、メンバ関数のシグネチャを直接変更する手段が提供されていないようです。

単にC++で動作するコードにしたいだけなら、CとC++は互換性があるので、C++に変換する必要はありません。calc.l先頭の%option c++を削除して、calc.yに以下のように手を加えて、g++でビルドするだけでもC++で動作するようになります。

%{
    #include <iostream>

    static void yyerror(const char* s)
    {
        std::cerr << s << std::endl;
    }

    extern "C" int yywrap(void)
    {
        return 1;
    }

    int yylex(void);
%}

%union {
    double double_value;
}

%type <double_value> expr
%token <double_value> NUM
%token ADD SUB MUL DIV
%token NL
%left '+' '-'
%left '*' '/'
%expect 16

%%

program     : statement
            | program statement
            ;

statement   : expr NL
                {
                    std::cout << $1 << std::endl;
                }
            ;

expr        : NUM
            | '(' expr ')' 
                {
                    $$ = $2;
                }
            | expr ADD expr
                {
                    $$ = $1 + $3;
                }
            | expr SUB expr
                {
                    $$ = $1 - $3;
                }
            | expr MUL expr
                {
                    $$ = $1 * $3;
                }
            | expr DIV expr
                {
                    if ($3 != 0) {
                        $$ = $1 / $3;
                    } else {
                        std::cerr << "division by zero\n";
                        exit(1);
                    }
                }
            ;

%%

#include "lex.yy.c"

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

どうしてもC++のflex, bisonに変換したい(クラスベースのパーサを使いたい)場合、一行二行書き換えるだけでは無理で、大幅に書き換えることになります。

試しにC++のスキャナ・パーサを生成するコードを作成してみましたが、両者をそのまま組み合わせることができず、かなり苦労しました。一応ソースは載せておきますが、こちらは使わないことをお勧めします。

%option c++ noyywrap yyclass="yyLex"

%{
using token = yy::parser::token;
struct yyLex : yyFlexLexer
{
    yy::parser::symbol_type operator()();
};
#undef YY_DECL
#define YY_DECL yy::parser::symbol_type yyLex::operator()()
%}

%%

"+"   { return token::ADD; }
"-"   { return token::SUB; }
"*"   { return token::MUL; }
"/"   { return token::DIV; }
"("   { return '('; }
")"   { return ')'; }
"\n"  { return token::NL; }

([1-9][0-9]*)|0|([0-9]+\.[0-9]*) {
    double temp;
    sscanf(yytext, "%lf", &temp);
    return yy::parser::make_NUM(temp);
}

"-"([1-9][0-9]*)|0|([0-9]+\.[0-9]*) {
    double temp;
    sscanf(yytext, "%lf", &temp);
    return yy::parser::make_NUM(temp);
}

[ \t] ;

. {
    fprintf(stderr, "lexical error.\n");
    exit(1);
}

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

    class yyLex;
%}

%require "3.2"
%language "c++"
%define api.token.constructor
%define api.value.type variant
%parse-param { yyLex& yylex }
%type <double> expr
%token <double> NUM
%token ADD SUB MUL DIV
%token NL
%left '+' '-'
%left '*' '/'
%expect 16

%code{
#include "lex.yy.cc"
}

%%

program     : statement
            | program statement
            ;

statement   : expr NL
                {
                    printf("%g\n", $1);
                }
            ;

expr        : NUM
            | '(' expr ')' 
                {
                    $$ = $2;
                }
            | expr ADD expr
                {
                    $$ = $1 + $3;
                }
            | expr SUB expr
                {
                    $$ = $1 - $3;
                }
            | expr MUL expr
                {
                    $$ = $1 * $3;
                }
            | expr DIV expr
                {
                    if ($3 != 0) {
                        $$ = $1 / $3;
                    } else {
                        fprintf(stderr, "division by zero\n");
                        exit(1);
                    }
                }
            ;

%%

void yy::parser::error(const std::string& msg)
{
    fputs(msg.c_str(), stderr);
    fputs("\n", stderr);
}

int main()
{
    yyLex yylex;
    yy::parser{yylex}();
    return 0;
}

コンパイル手順

flex calc.l
bison calc.y
g++ calc.tab.cc -o calc

あと、あえて修正していませんが、元のコードの時点で演算子優先順位の指定に誤りがあります。例えば、3*2+1と入力した場合、正しい計算結果は7ですが、このコードだと9になります。正しく指定すれば、%expect 16で無理やりコンフリクトを抑え込まなくてもよくなるので、修正してみてください。

0Like

Comments

  1. @tomdawncats

    Questioner

    返信が遅れて本当に申し訳ありません。
    非常に参考になりました。ありがとうございます

Your answer might help someone💌