単純なマーク付けがなされた文字列の解析 #4

yylexを手書きするのが面倒だと感じたら字句解析器生成系lex/flexの出番となる。
これにはyytextという文字列バッファが最初から付いてくる。
このバッファはflexのデフォルト動作では解析中の字句の長さに合わせて動的に長くなるので便利である。
固定長配列の場合でもその長さは十分長いはずだが、
必要ならYYLMAXというマクロで定義されているのでこれを変更すればよい。
構文解析器の生成は以下のコードを使う。

parser.y
%{
#define YYSTYPE char*

#include <stdio.h>

int yylex(void);
void yyerror(char const *);
%}

%token STRING_EXCEPT_PARENTHESIS

%%

text : string_headed
     | marked_string_headed
     ;

string_headed : STRING_EXCEPT_PARENTHESIS                   { printf("string [%s]\n", $1); free($1); }
              | STRING_EXCEPT_PARENTHESIS { printf("string [%s]\n", $1); free($1); } marked_string_headed
              ;

marked_string_headed : marked_string
                     | marked_string marked_string_headed
                     | marked_string string_headed
                     ;

marked_string : '(' STRING_EXCEPT_PARENTHESIS ')'           { printf("marked-string [%s]\n", $2); free($2); }
              ;

%%

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

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

構文規則自体は全く変えていない。
アクションにおいて意味値スタックに置かれたSTRING_EXCEPT_PARENTHESISの値をfreeで開放する処理を加えているのみである。
これは手書きの字句解析用関数yylexの代わりとした、次に述べる自動生成した字句解析器の動作に関係する。
このソースからyacc/bisonでCのコードを生成する際には、
-dオプションを付けて終端記号のマクロ定義の入ったヘッダファイルも生成する。
bisonの場合は-yオプションも一緒に付けることでlex互換モードになりヘッダをy.tab.hの名前で作ってくれる。

字句解析器は次のソースから作る。flex用。

lexer.l
%{
#define YYSTYPE char*

#include "y.tab.h"
#include <string.h>
%}

%option noyywrap

%%

[^()]+  { yylval = strdup(yytext); return STRING_EXCEPT_PARENTHESIS; }
[()]    { return yytext[0]; }

%%

<string-except-parenthesis>を返す際にyytextを複製してyylvalに設定している。
この複製された文字列を構文解析器のアクションで使用した後に開放しているのである。
yylex関数を手書きするよりも全体として少ない記述量で済んでいる。