マーク付けに使う文字の変更 #6

変な策とはいえflexに字句解析器の生成を任せられるようになった。
つまり記述量や簡潔さ等については置いておくとしても、
このことはflexだけで解析器全体を書ける可能性があるということになる。

注釈入りマーク付け文字列の解析の#13で作成した字句解析器のみによる解析器のコードを元にしてみる。

lexer.l
%{
#include <stdio.h>
#include <string.h>

static int current_start_mark_char = '(';
static int current_end_mark_char = ')';
static int current_start_annotation_char = ':';

#define YY_INPUT(buf,result,max_size) \
    { \
        int i; \
        errno = 0; \
        while ((result = fread(buf, 1, max_size, yyin)) == 0 && ferror(yyin)) { \
            if (errno != EINTR) yy_fatal_error("input failed"); \
            errno = 0; \
            clearerr(yyin); \
        } \
        for (i = 0; i < result; i++) { \
            switch (buf[i]) { \
            case '(': buf[i] = current_start_mark_char; break; \
            case ')': buf[i] = current_end_mark_char; break; \
            case ':': buf[i] = current_start_annotation_char; break; \
            default: \
                if (buf[i] == current_start_mark_char) buf[i] = '('; \
                else if (buf[i] == current_end_mark_char) buf[i] = ')'; \
                else if (buf[i] == current_start_annotation_char) buf[i] = ':'; \
                break; \
            } \
        } \
    } \
\

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

void yyerror(const char *msg);

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

static void restore(void);

static void restore(void)
{
    int i;
    for (i = 0; i < yyleng; i++) {
        if (yytext[i] == current_start_mark_char) yytext[i] = '(';
        else if (yytext[i] == current_end_mark_char) yytext[i] = ')';
        else if (yytext[i] == current_start_annotation_char) yytext[i] = ':';
        else if (yytext[i] == ':') yytext[i] = current_start_annotation_char;
    }
}
%}

%option noyywrap nounput noinput

%%

    int is_first = 1;

[^()]+ {
        restore();
        printf("string [%s]\n", yytext);
        if (is_first) is_first = 0;
    }

\([^():]+:[^()]+\) {
        ptrdiff_t c = strchr(yytext, ':') - yytext;
        restore();
        printf(
            "marked-string [%.*s]: annotation[%.*s]\n",
            c - 1, yytext + 1,
            yyleng - c - 2, yytext + c + 1
        );
        if (is_first) is_first = 0;
    }

\([^():]+\) {
        restore();
        printf("marked-string [%.*s]\n", yyleng - 2, yytext + 1);
        if (is_first) is_first = 0;
    }

<<EOF>> {
        return is_first ? yyerror("unexpected EOF"), 1 : 0;
    }

\(\)            { yyerror("null string between parentheses"); return 1; }
\(:             { yyerror("null marked string"); return 1; }
\([^):]*\(      { yyerror("illegal left parenthesis"); return 1; }
\([^):]*        { yyerror("unexpected EOF"); return 1; }
\([^):]+:\)     { yyerror("null annotation"); return 1; }
\([^):]+:\(     { yyerror("illegal left parenthesis"); return 1; }
\([^):]+:[^)]*  { yyerror("unexpected EOF"); return 1; }
\)              { yyerror("illegal right parenthesis"); return 1; }

%%

int main(int argc, char *argv[])
{
    int opt;
    while ((opt = getopt(argc, argv, "s:e:a:")) != -1) {
        switch (opt) {
        case 's':
            if (strlen(optarg) > 1) fprintf(stderr, "warning: start mark requires one character: %s\n", optarg);
            current_start_mark_char = optarg[0];
            break;
        case 'e':
            if (strlen(optarg) > 1) fprintf(stderr, "warning: end mark requires one character: %s\n", optarg);
            current_end_mark_char = optarg[0];
            break;
        case 'a':
            if (strlen(optarg) > 1) fprintf(stderr, "warning: start annotation requires one character: %s\n", optarg);
            current_start_annotation_char = optarg[0];
            break;
        default:
            fprintf(stderr, "usage: %s [-s start_mark] [-e end_mark] [-a start_annotation]\n", argv[0]);
            exit(1);
        }
    }
    if (
        current_start_mark_char == current_end_mark_char
        || current_end_mark_char == current_start_annotation_char
        || current_start_annotation_char == current_start_mark_char
    ) {
        fprintf(stderr, "error: meta characters must differ from each other\n");
        exit(1);
    }
    return yylex();
}

変更点は、指定したメタ文字を保持する三つの変数、YY_INPUTマクロの再定義、
出力用に新旧のメタ文字を再交換する関数restore、メタ文字関連のオプションを受け付ける機能付きのmain関数を追加し、
三つの文字列パターンに対するアクション内で結果を出力する前にrestoreを呼び出すようにしたことである。
全体的な骨格は元のコードから変えていない。
また、YY_INPUTマクロも前回示したもののままであり、
main関数も前回のparser.yでyyparseを呼び出す代わりにyylexを呼び出す点が異なるだけである。

$ flex lexer.l
$ gcc -std=c89 -pedantic -Wall -Wextra -Werror -o parser lex.yy.c
$ echo -n "Now, [(=left parenthesis] and [)=right parenthesis] and [:=colon] are not meta characters." | ./parser -s"[" -e"]" -a"="
string [Now, ]
marked-string [(]: annotation[left parenthesis]
string [ and ]
marked-string [)]: annotation[right parenthesis]
string [ and ]
marked-string [:]: annotation[colon]
string [ are not meta characters.]

ということでうまく動いているようだ。
メタ文字の交換部分は相変わらず煩雑だが、
字句解析器のソースのみにまとめることができたので、
構文解析器と字句解析器とに分かれていたものよりはコンパクトになったと思う。