マーク付き文字列解析器の整理 #1

無計画に弄っている付けが回って来た。
コードが酷くごちゃごちゃしてきて読みにくいし、似たようなコードがあちこちに散らばっている。
メタ文字変更シーケンスを加えたのが変わり目か(その前から混沌とはしていたが)。
いろいろ整理が必要だ。
まず、関数yylexからこのシーケンスに関する字句解析を分離してしまおう。

mclex.h
#ifndef MCLEX_H_INCLUDED_
#define MCLEX_H_INCLUDED_

extern int mclval;

int mclex(void);
void mcunput(int type);
int mcinit(int sm, int em, int sa);

#endif /* MCLEX_H_INCLUDED_ */
mclex.c
#include <stdio.h>
#include <stdlib.h>
#include "mclex.h"
#include "y.tab.h"

#define ACT_UNEXPECTED_EOF \
    { \
        fputs("error: yylex detected unexpected EOF\n", stderr); \
        exit(1); \
    } \
\

#define ACT_NOT_DIFFER_MC \
    { \
        fputs("error: meta characters must differ from each other anytime\n", stderr); \
        exit(1); \
    } \
\

static int start_mark = '(';
static int end_mark = ')';
static int start_annotation = ':';

/* semantic value */
int mclval;

/* lexer for processing only meta-character change */
int mclex(void)
{
    for (;;) {
        int c = getchar(), c2;
        if (c == end_mark) return END_MARK;
        if (c == start_annotation) {
            mclval = start_annotation;
            return START_ANNOTATION;
        }
        if (c != start_mark) return c;
        c2 = getchar();
        if (c2 == start_mark) {
            int c3 = getchar();
            if (c3 == EOF) ACT_UNEXPECTED_EOF
            if (c3 == end_mark || c3 == start_annotation) ACT_NOT_DIFFER_MC
            start_mark = c3;
        } else if (c2 == end_mark) {
            int c3 = getchar();
            if (c3 == EOF) ACT_UNEXPECTED_EOF
            if (c3 == start_annotation || c3 == start_mark) ACT_NOT_DIFFER_MC
            end_mark = c3;
        } else if (c2 == start_annotation) {
            int c3 = getchar();
            if (c3 == EOF) ACT_UNEXPECTED_EOF
            if (c3 == start_mark || c3 == end_mark) ACT_NOT_DIFFER_MC
            start_annotation = c3;
        } else {
            ungetc(c2, stdin);
            return START_MARK;
        }
    }
}

/* push a token back to this lexer */
void mcunput(int type)
{
    switch (type) {
    case EOF:
        ungetc(type, stdin);
        break;
    case START_MARK:
        ungetc(start_mark, stdin);
        break;
    case END_MARK:
        ungetc(end_mark, stdin);
        break;
    case START_ANNOTATION:
        ungetc(start_annotation, stdin);
        break;
    default:
        fputs("error: push invalid-typed token back\n", stderr);
        exit(1);
    }
}

/* init lexer with meta-characters */
int mcinit(int sm, int em, int sa)
{
    if (sm == em || em == sa || sa == sm) return 1;
    start_mark = sm;
    end_mark = em;
    start_annotation = sa;
    return 0;
}

関数mclexはメタ文字変更シーケンスを内々で処理し、
メタ文字自身が現れたときには、yylexが構文解析器へ返していたトークンの種類として返す関数である。
メタ文字以外が現れた場合は、文字コードそのものを返す。
トークンの種類は構文解析器側で定義しているので、
bisonに-ydオプションを付けて得られるヘッダファイルy.tab.hをインクルードしてある。

現在のメタ文字の管理はmclexで行うようにしている。
また、メタ文字が具体的に何であるかはmclex内で閉じるようにしている。
唯一の例外は注釈の開始記号start_annotationで、
これは構文解析器で文字列を作り出す時に必要になるため、
mclex外へは意味値mclvalとして渡すようにしている。
関数mclexはメタ文字変更シーケンスの処理込みのgetchar関数のように働く。

関数mcunputはungetcのように働くが書き戻せるものに制限がある。
mclexで得られたどんなトークンでも書き戻せるのではなく、メタ文字を表すトークン専用である。
今までのyylexがメタ文字変更シーケンスの解析以外で書き戻しを行っているのは、
メタ文字を含まない文字列の末尾でメタ文字が出現した時だけである。
メタ文字変更シーケンスの解析自体はmclex側で行っているので、
この処理を除いたyylexで必要な書き戻し処理ではメタ文字の種類を戻すことができれば用は足りる、はず……