注釈付きでマークされた文字列の解析 #8

承前。というわけで、構文解析系と字句解析系の両方でアクセス可能な変数を一個定義し、
この変数で文字列にコロンが入ることを禁じるか否かのモードを表すものとして、
このモード変更の仕組みを構文解析の生成規則の途中にアクションとして記述する方法を採った。
字句解析器では現在のモードを参照して、どちらの終端記号を返すべきかを知る。
字句解析はyylex手書きではなくflexを使って自動生成する。

parser.y
%{
#define YYSTYPE char*

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

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

int colon_forbidden;
%}

%initial-action
{
    colon_forbidden = 0;
};

%token STRING_EXCEPT_PARENTHESIS STRING_EXCEPT_SPECIAL

%%

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 :
      '(' { colon_forbidden = 1; } marked_string_body_with_annotation ')'
    ;

marked_string_body_with_annotation :
      STRING_EXCEPT_SPECIAL
        {
            printf("marked-string [%s]\n", $1); free($1);
            colon_forbidden = 0;
        }
    | STRING_EXCEPT_SPECIAL ':' { colon_forbidden = 0; } STRING_EXCEPT_PARENTHESIS
        {
            printf("marked-string [%s]: annotation[%s]\n", $1, $4);
            free($1); free($4);
        }
    ;

%%

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

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

colon_forbiddenがモードを表す変数である。
零ならコロンを含んで構わないSTRING_EXCEPT_PARENTHESISを、
非零ならコロンも禁じたSTRING_EXCEPT_SPECIALを字句解析器yylexに要求する。
この二つの終端記号に対してyylexが返す意味値は、
yylexの方で新たにメモリを割り当ててコピーした文字列なので使用後にfreeで開放している。

lexer.l
%{
#define YYSTYPE char*

#include <stdlib.h>
#include <string.h>
#include "y.tab.h"

#ifdef __STRICT_ANSI__
int fileno(FILE *);
#endif

static char *estrdup(const char *s);

extern int colon_forbidden;
%}

%option noyywrap nounput noinput

%%

[^()]+  { if (colon_forbidden) REJECT; yylval = estrdup(yytext); return STRING_EXCEPT_PARENTHESIS; }
[^():]+ { yylval = estrdup(yytext); return STRING_EXCEPT_SPECIAL; }
[():]   { return yytext[0]; }

%%

static char *estrdup(const char *s)
{
    char *p = malloc(strlen(s) + 1);
    if (p == NULL) {
        fputs("error: estrdup cannot allocate any memories.\n", stderr);
        exit(1);
    }
    return strcpy(p, s);
}

要は、とりあえずコロンも含まれて構わないパターン[^()]+でマッチングを試みて、
colon_forbiddenが非零の時は、そのマッチングの結果をREJECTして他にマッチしていたものを探す。
その結果、コロンが含まれてはいけないパターン[^():]+にマッチすれば、これを返すことになる。

$ bison -dy parser.y
$ flex lexer.l
$ gcc -std=c89 -pedantic -Wall -Wextra -o parser y.tab.c lex.yy.c
$ echo -n "The (quick: swift) brown (fox) jumps over the (lazy: one of three virtues of a programmer) (dog)." | ./parser
string [The ]
marked-string [quick]: annotation[ swift]
string [ brown ]
marked-string [fox]
string [ jumps over the ]
marked-string [lazy]: annotation[ one of three virtues of a programmer]
string [ ]
marked-string [dog]
string [.]
$ echo -n "(x::):" | ./parser
marked-string [x]: annotation[:]
string [:]

のように、一応上手くいっているようだ。

ただ、せっかく#1に比べて#6の構文規則がコンパクトになっているのに、
parser.yでは生成規則中のアクションが加わって少しだけ見づらくなっているかも。