hakeの日記

Windows環境でプログラミングの勉強をしています。

re2cメモ その2

字句解析対象の文字列をファイルから読み取る様にしてみました。
といってもre2cのソースフファイル中のサンプルに既にYYFILL(n)で使用する為の関数fill()が用意されていたので基本的にそれを使用しました。一点open()とread()をそれぞれfopen(),fread()に変更しました。

YYFILL(n)の動作

その1のre2cが作成したcソースをみると、バッファ上の未解析文字数(YYLIMIT - YYCURSOR)が解析に必要な文字数nに足りない場合にYYFILL(n)が呼ばれる様です。ですからYYFILL(n)の動作は、基本的にはファイルからn文字以上を読み出してバッファの後にくっつければ良いことになります。
単純にくっつけていくとバッファが大変なことになるので、 関数fill()では以下の動作をさせているようです。

  1. 現バッファの最後に解析対象となった文字列の先頭以降をバッファ先頭に寄せる。
  2. 現在のバッファの未解析部分+ファイルから読み取る文字数(BSIZE)分の新バッファ領域を確保
  3. 現バッファの未解析部分を新バッファのコピー(正確には最後に解析対象となった文字列の先頭以降をコピー)
  4. ファイルからBSIZE文字を読み取り新バッファに追加(BSIZE > nでなければならない)
  5. YYCURSOR他ポインタを新バッファの所定の位置を指すように変更
  6. 現バッファの領域開放

構造体Scannerの意味

各ポインタを保持する為に構造体Scannerを作成、各メンバ変数の初期値は0(memsetで初期化)、各メンバ変数の意味は以下の通り

  • FILE *fd :読み取るファイル
  • uchar *bot:バッファ先頭アドレス
  • uchar *tok:最後に処理したトークンに対応する文字列の先頭アドレス
  • uchar *ptr:YYMARKER(re2cが使用)
  • uchar *cur:fill関数を抜けた際のYYCURSOR位置保持用
  • uchar *pos:現在処理しているソース行の先頭アドレス(に該当するアドレス、バッファ領域外を示す場合有り)
  • uchar *lim:バッファ上の解析対象最終文字アドレス + 1(YYLIMIT)
  • uchar *top:バッファ終端アドレス + 1
  • uchar *eof:通常 0, 最終行端数処理時のみデータ終端アドレス + 2(+ 1には"\n"が書かれる)
  • uint line:現在処理しているソース行番号

サンプルソース

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


typedef unsigned int uint;
typedef unsigned char uchar;

#define	BSIZE	4	/* fileから1度に読み込む文字数 */

#define	YYCTYPE		uchar
#define	YYCURSOR	cursor
#define	YYLIMIT		s->lim
#define	YYMARKER	s->ptr
#define	YYFILL(n)	{cursor = fill(s, cursor);}

#define	RET(i)	{s->cur = cursor; return i;}

enum {NUMBER = 1, PLUS, MINUS, EOI};




typedef struct Scanner {
	FILE		*fd;
	uchar		*bot; /* buf先頭                                 */
	uchar		*tok; /* 処理トークンの先頭                      */
	uchar		*ptr; /* YYMARKER                                */
	uchar		*cur; /* カーソル位置 (= cursor)                 */
	uchar		*pos; /* 処理行先頭位置                          */
	uchar		*lim; /* 解析対象文字 + 1(YYLIMIT)               */
	uchar		*top; /* buf終端 + 1                             */
	uchar		*eof; /* 0 / 最終行端数処理時のみ データ終端 + 2 */
    uint		line; /* 処理中行番号                            */
} Scanner;

uchar *fill(Scanner *s, uchar *cursor){

	printf("# exec fill()\n");

	if(!s->eof) {     /*  最終行端数処理時以外  */
		uchar *tmp;
		uint cnt = s->tok - s->bot;
		int i;

		if(cnt){
/*****	処理中トークン以降を前に詰める

			各ポインタ位置
			memcpy前
			      -------cnt-------
			                       pos
			      bot              tok            cursor             lim        top
			buf : 0  1  2  3    \n t  o  k  e  n                  end

			memcpy後
			      bot
			      pos
			      tok            cursor               lim                       top
			buf : t  o  k  e  n                  end
*****/
			memcpy(s->bot, s->tok, s->lim - s->tok);
			s->tok = s->bot;
			s->ptr -= cnt;
			cursor -= cnt;
			s->pos -= cnt;
			s->lim -= cnt;
		}
		if((s->top - s->lim) < BSIZE){
/*****	buf後部空きがBSIZE未満の場合、BSIZE分だけ追加する(新規に領域を取得して、旧bufをコピーする)

			各ポインタ位置
			      bot                                 --------------領域追加分-------------
			      pos
			      tok            cursor               lim                                  top
			buf : t  o  k  e  n                  end  0  1  2                       BSIZE-1
*****/

			uchar *buf = (uchar*) malloc(((s->lim - s->bot) + BSIZE)*sizeof(uchar));
			memcpy(buf, s->tok, s->lim - s->tok);
			s->tok = buf;                      /* 現行トークンの開始位置  */
			s->ptr = &buf[s->ptr - s->bot];    /* YYMARKER                */
			cursor = &buf[cursor - s->bot];    /* 現在のカーソル位置      */
			s->pos = &buf[s->pos - s->bot];    /* 現在行の先頭位置        */
			s->lim = &buf[s->lim - s->bot];    /* YYLIMIT                 */
			s->top = &s->lim[BSIZE];           /* 今回取得したbuf最後 + 1 */
			free(s->bot);                      /* 元のbufを開放           */
			s->bot = buf;                      /* 今回取得したbuf先頭     */
		}

/*****	追加した領域にファイルから読んだデータ(cntバイト)をコピー

			各ポインタ位置
			      bot                                 --------------領域追加分-------------
			      pos                                 --------cnt-------
			      tok            cursor              (lim)              lim eof            top
			buf : t  o  k  e  n                  end  0  1  2        end \n         BSIZE-1
*****/

		tmp = (uchar*) calloc((BSIZE + 1), sizeof(uchar)); /* 読み込みバイト数確認の為 */
		fread(tmp, BSIZE, 1, s->fd);                       /*   一旦一時バッファに     */
		cnt = strlen(tmp);                                 /*   コピーする             */
		memcpy(s->lim, tmp, cnt);            /* 追加領域にfileから読んだデータをコピー */
		free(tmp);

		if(cnt != BSIZE){          /* ファイルからラストの端数ブロックを読み込んだ場合 */
			s->eof = &s->lim[cnt]; *(s->eof)++ = '\n';
		}
		s->lim += cnt;
		printf("#   read %d charactor from file\n", cnt);
	}
	return cursor;
}

int scan(Scanner *s){
	uchar *cursor = s->cur;
std:
	s->tok = cursor;

/*!re2c
	any	= [\000-\377];
*/

/*!re2c
	[0-9]+	{RET(NUMBER);}
	"+"		{RET(PLUS);  }
	"-"		{RET(MINUS); }

	[ \t\v\f]+	{goto std;}

	"\n"
		{
		if(cursor == s->eof) RET(EOI);
		s->line++;
		s->pos = cursor;
		goto std;
		}

	any
		{
		printf("unexpected character: '%c' in line:%d  pos:%d\n", *s->tok, s->line, s->tok - s->pos);
		goto std;
		}

*/
}

main(){
	Scanner in;
	int t;
	memset((char*) &in, 0, sizeof(in));

	if((in.fd = fopen("sample.txt", "r")) == NULL){
		fprintf(stdout, "Error : file not open \n");
		return 1;
	}
	in.line = 1;

	while((t = scan(&in)) != EOI){
		printf("line : %d\ttokenID : %d\tstr : '%.*s'\t\n", in.line, t, in.cur - in.tok, in.tok);
	}
	fclose(in.fd);
}

sample.txt

1+2
345*45

実行結果

~/re2c$ ./a.exe
# exec fill()
#   read 4 charactor from file
line : 1        tokenID : 1     str : '1'
line : 1        tokenID : 2     str : '+'
line : 1        tokenID : 1     str : '2'
# exec fill()
#   read 4 charactor from file
line : 2        tokenID : 1     str : '345'
# exec fill()
#   read 3 charactor from file
unexpected character: '*' in line:2  pos:3
line : 2        tokenID : 1     str : '45'
# exec fill()
# exec fill()