C言語アルゴリズム-字句解析
前へ 目次へ 次へ 

2 字句解析

単語に分解して取り出す関数と取り出した単語を元に戻す関数を作成する。

関数の書式 char *get_token(void); void unget_token(char *word);
関数名 get_token unget_token
引数 なし char *word; 戻す単語を格納しておく
返却値 取り出した単語の先頭アドレス なし
その他 グローバル変数
char *gt_line; 1行分の文字列を格納しておく
(MAX=256文字)
char *token; 取り出した単語が格納される
なし

 例えば、次の1行を変数gt_lineに格納する。

2.1 + 3 + 4.33 + 5e2

 最初にget_token()を呼び出すと、変数tokenには、2.1が格納される。次に呼び出すと空白は無視されて+が格納される。次に3, +, 4.33, +, 5e2 と順に格納され、最後は\0(ナル)が格納される。
 この様子を図で示すと次のようになる。pは配列gt_lineを、ptkは配列tokenを操作するポインタ変数である。

@ ポインタ変数pの内容が数字であるので、数字以外の文字が現れるまで1文字ずつ変数tokenに格納しながら進める。

gettoken

A 数字以外の文字が現れた時点で変数tokenの最後に\0を付加し、変数gt_lineにはポインタ変数p以降の文字列を新たに変数gt_lineに格納する。

gettoken

B  配列gt_line, tokenはこのような状態で、関数の呼び出し側に戻る。

gettoken

 次に関数get_token()が呼ばれると、最初の空白は無視されポインタ変数pは記号の位置に進む。記号の場合は、その1文字だけを変数tokenに格納する。

gettoken

 これを繰り返して単語に分解している。このように、

 1文字ずつ変数tokenに格納し、 上記以外の文字(記号)の場合は、その1文字だけを変数tokenに格納する。

 「void get_token(void) /* トークン取りだし */」は、void, get, _, token, (, void, ), /, *, トークン取りだし, *, /, \n, \0 に分解されて順次格納される。
 数式を処理するだけのためであれば、数値と演算子に分解できるだけでよい。

[gettoken.c]

1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
/*
 * gettoken.c - 字句解析関数群 Ver 1.2
 *  1998.2 (C) Hiroshi Masuda
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#define STR_MAX 256             /* 1行の最大長 */
#define EUC         1
#define SJIS1       2
#define SJIS2       3
#ifdef UNIX
    #define KANJI1  EUC
    #define KANJI2  EUC
#else
    #define KANJI1  SJIS1
    #define KANJI2  SJIS2
#endif

/* グローバル変数 */
char    gt_line[STR_MAX];   /* get_token()用の1行データバッファ */
char    token[STR_MAX];     /* 取得トークン */
/* プロトタイプ宣言 */
char *get_token(void);              /* トークン取りだし */
void unget_token(char *);           /* トークン戻す */
int iskanji(int, unsigned char);    /* 漢字コードの判定 */

char *get_token(void)
{
    char    *p, *ptk, ch;

    p = gt_line;        /* ポインタの初期化 */
    ptk = token;        /* ポインタの初期化 */
    while(*p == ' ' || *p == '\t')  /* 空白を読み飛ばす */
        ++p;
    if(*p == '\0'){             /* バッファが空 */
        token[0] = '\0';
        return(token);
    }
    /* ----- 漢字 ----- */
    if(iskanji(KANJI1, *p)){
        do{
            *ptk++ = *p++;          /* 1バイト目 */
            if(!iskanji(KANJI2, *p)){   /* 2バイト目 */
                printf("漢字コードが不正である\n");
                exit(1);
            }
            *ptk++ = *p++;
        }while(iskanji(KANJI1, *p) && *p != '\0');
    /* ----- 英字 英数字----- */
    }else if(isalpha(*p)){
        do{
            *ptk++ = *p++;
        }while(isalnum(*p) && *p !='\0');   /* 2文字目以降は英数字 */
    /* ----- 数字 ----- */
    }else if(isdigit(*p)){
        do{
            *ptk++ = *p++;
        }while((isdigit(*p) || *p == '.' || toupper(*p) == 'E') && *p !='\0');
    /* ----- 文字・文字列定数 ----- */
    }else if(*p == '\'' || *p == '"'){
        ch = *p;
        *ptk++ = *p++;
        do{
            if(*p == '\\'){         /* エスケープ文字 */
                *ptk++ = *p++;
                *ptk++ = *p++;
            }else if(*p != '\n')    /* 改行コード無視 */
                *ptk++ = *p++;
        }while(*p != ch);
        ++p;
    /* ----- その他の文字 ----- */
    }else
        *ptk++ = *p++;
    *ptk = '\0';        /* 文字列終端 */
    strcpy(gt_line, p);     /* 残りの文字列をバッファにコピー */
    return(token);
}
void unget_token(char *t)
{
    char    work[STR_MAX];

    strcpy(work, t);    /* 戻すトークン文字列をworkにコピー */
    if(*t == '\'' || *t == '"'){
        t[1] = '\0';
        strcat(work, t);
    }
    strcat(work, gt_line);  /* workに1行のデータを連結 */
    strcpy(gt_line, work);  /* workを1行データにコピー */
}
int iskanji(int type, unsigned char code)
{
    int     ret = 0;
    switch(type){
        case EUC:
            if((code >= 0xa1 && code <= 0xfe))
                ret = code;
            break;
        case SJIS1:
            if((code >= 0x81 && code <= 0x9F) || (code >= 0xe0 && code <= 0xfc))
                ret = code;
            break;
        case SJIS2:
            if((code >= 0x40 && code <= 0x7e) || (code >= 0x80 && code <= 0xfc))
                ret = code;
            break;
        default:
            printf("iskanji:unknown Kanji code type.\n");
            exit(1);
    }
    return(ret);
}

#ifdef TEST
void main(void)
{
    FILE    *bf;
    char    file[80];       /* ファイル名 */

    printf("ファイル名 : ");  /* プロンプト表示 */
    gets(file);             /* ファイル名入力 */
    if((bf = fopen(file, "r")) == NULL){      /* ファイルオープン */
        printf("ファイルがオープンできない\n");
        exit(1);    /* 強制終了 */
    }
    while(fgets(gt_line, STR_MAX, bf) != NULL){     /* 1行読み込み */
        while(1){
            get_token();
            if(*token == '\0')      /* 1行分終了 */
                break;
            if(*token == '\n')
                printf("(\\n)");    /* 改行コード */
            else
                printf("(%s) ", token);     /* 表示 */
        }
        printf("\n");
    }
    fclose(bf);         /* ファイルクローズ */
}
#endif

 117行目から141行目は関数get_tokenをテストするためのメインプログラムである。
116行目の#ifdef TEST と142行目の #endif は、コンパイラに対する指示命令(プリプロセッサ命令)でTESTが#defineで定義されているときだけ 117〜141行が有効になる。
 コンパイル前に プログラムに #define TEST の1行を追加するか、コンパイルを次のようにする。

> cc -DTEST gettoken.c

次のヘッダファイルは、他のプログラムから字句解析の関数get_token等を使用するときにインクルードする。

[gettoken.h]

1:
2:
3:
4:
5:
6:
7:
8:
/* gettoken.h - head file for gettoken library */
#define STR_MAX 256                 /* 1行の最大長 */
extern char     gt_line[STR_MAX];   /* get_token()用の1行データバッファ */
extern char     token[STR_MAX];     /* 取得トークン */
/* プロトタイプ宣言 */
extern char *get_token(void);       /* トークン取りだし */
extern void unget_token(char *);    /* トークン戻す */
int iskanji(int, unsigned char);    /* 漢字コードの判定 */

 次の実行例は、ファイルgettoken.cをオプション -DTEST 付きでコンパイルしたものである。 取り出された単語が( )で囲まれて表示される。各行末に ( だけが表示されているがここで改行されているためである。 したがって、2行目以降の行頭には ) が表示されることになる。

実行結果
ファイル名 : pro1-3.c
(#) (include) (<) (stdio) (.) (h) (>) (\n)
(\n)
(void) (main) (() (void) ()) (\n)
({) (\n)
(char) (buff) ([) (256) (]) (;) (/) (*) (入力文字列) (*) (/) (\n)
(\n)
(printf) (() ("文字列 : ) ()) (;) (/) (*) (プロンプト表示) (*) (/) (\n)
(gets) (() (buff) ()) (;) (/) (*) (文字列入力) (*) (/) (\n)
(printf) (() ("文字列は%sです\n) (,) (buff) ()) (;) (/) (*) (入力文字列表示) (*)
 (/) (\n)
(}) (\n)



前へ 目次へ 次へ 
Copyright © 2001 Hiroshi Masuda 

 

 

inserted by FC2 system