FLEX日本語活用編

(ubuntu 6.10版)

 

Windows版のFLEX日本語活用編に引き続き、Debian Linux系OSであるubuntu( ぅぉー ぶーん つぅーと発音するようです。ubuntuの意味は、南アフリカの倫理的な概念で、「他者への思いやり」だそうです)上で、Windowsと同じようにFLEXを用いて日本語文書処理をやってみたいと思います。(FLEXについての概説はWinddows版の方に記載しております)FLEXはもともとUNIXというOS上で作られたLEXというツールの後継にあたりますので、UNIXの流れをくむUbuntuの方がより洗練されたFLEXの使い方ができるのではないでしょうか。また、ubuntuは優秀なOSであるだけではなく、誰でもフリーで入手することがてきます。

ここではかなり古めのバージョンですが、ubuntu6.10というOS上で、FLEX(version 2.5)をインストールしました。それをGNOME端末というビジュアルなCUIを利用して動かしてみます。FLEXを動かすにはC言語の処理系が必要となりますが、ubuntu_6.10ではOSに標準で装備されているようです。また、ubuntuでは古いバージョンのFLEXをインターネットから入手する必要があります。 FLEX(version 2.5.4a)の場合には、「flex-old_2.5.4a-7_i386.deb」というパッケージをネット上のリポジトリ(ファイル保管倉庫)から探してインストールしました。

FLEXのプログラムは一度C言語に変換されます。それをC言語処理系によって翻訳することにより、プログラムを実行することができます。従ってFLEXのプログラムを動かすにはC言語の処理系が必要となりますので、これもubuntuの場合には「sudo apt-get install」などのコマンドにより、gcc(version 4.1.1-13)というフリーの処理系をインストールしておくことが必要となります。

FLEXの起動確認
例題FU─1 入力ファイルをそのまま出力する
例題FU─2 文字列を変換する(その一)
例題FU─3 文字列を変換する(その二)
例題FU─4 簡単なC言語風日本語プログラムの実行
例題FU─5 FLEX規則を利用してC言語を日本語で記述する
例題FU─6 文字クラスを使った日本語文字処理の基本練習
例題FU─7 FLEXを利用した送り仮名のチェック
例題FU─8 FLEXを利用した簡単な同音異義語のチェック
例題FU─9 FLEXでスタート状態を使った練習
例題FU─10 FLEXを利用した簡単な表記チェッカー
例題FU-11 FLEXでCGIを動かす(その一)
例題FU-12 FLEXでCGIを動かす(その二)
例題FU-13 自動番号の付いた規則をつくる
例題FU-14 FLEX規則を利用してC言語を日本語で記述する(続編)
例題FU-15 FLEXを利用して自動販売機のシミュレーションを行なう

FLEXの起動確認

まずubuntuでGNOME端末を立ち上げ、FLEXが利用できる作業用フォルダ/ディレクトリに移動します。ここではカレントディレクトリ~/flexという場所を使います。本来はユーザー名やホスト名の後にディレクトリ名が表示されるのですが、ここではわざとユーザー名やホスト名は非表示にしてあります。(GNOME端末はテーマ色の変更が可能です)

FLEXのインストールを行った後、次のようなコマンド入力をします。(--version または -V)

$flex --version

うまくインストールできている場合には、次のようにバージョン番号が出力されます。

Windows版とのバージョンを合わせるためにも、上記のようなFLEXのバージョンを選びましたが、リポジトリから入手できるバージョンとは異なるかもしれません。もし、うまくFLEXがインストールできない場合には、リポジトリのアドレスが現在サポートされているアドレスかどうかを一度確かめてください。正式なサポートが終了していても、別の場所で管理されていることがありますので、ネットで根気よく調べてみてください。

例題FU─1 入力ファイルをそのまま出力する

ここで最初に簡単なFLEXのプログラムを作ってみたいと思います。

GNOMEテキストエディタなどで、次のようにプログラムを入力します。

/* FLEX テストプログラム1 */
%{

#define YY_SKIP_YYWRAP
int yywrap( void ) { return(1) ; }

%}

%%
%%

int main()
{
    yylex();
    return(0);
}

このファイルをtest1.lexという名前で作業用フォルダ/ディレクトリ内に保存します。Windows版のときにはmainの関数をvoid型で定義しておりますが、ubuntuの場合にはmainの関数をint型で定義して、戻り値をreturn(0)で返すようにしてください。

コマンドプロンプトから次のように入力するか、簡単なシェルスクリプト(バッチファイル)によって自動的にtest1を生成できるようにします。

コマンドプロンプトから入力する場合

$flex test1.lex
$gcc lex.yy.c
$cp lex.yy.exe test1

 

簡単なシェルスクリプトlccの内容

#!/bin/bash
flex $1.lex
gcc lex.yy.c
cp a.out $1
exit 0

シェルスクリプトから実行する場合

$./lcc test1

ここではシェルスクリプト(バッチファイル)で実行させてみました。シェルスクリプトは、必ずプログラムとして実行できるアクセス権を与えておかないといけません。このやり方は、ファイル・ブラウザでlcc.shを右クリックしてプロパティを選択します。プロパティ画面の「アクセス権」タブを選択し、「実行権」の項目にある「プログラムとして実行することを許す」にチェックをするだけです。

次にGNOMEテキストエディタなどで、次のようなテキストを入力してex1.txtという名前で保存します。

あいうえお
かきくけこ
さしすせそ
たちつてと
なにぬねの
はひふへほ
まみむめも
やゆよ
らりるれろ
わをん

コマンドプロンプトから次のように入力します。

$./test1 < ex1.txt

ex1.txtの内容がそのまま次のように表示されることを確認します。

Windows版の日本語文書処理編をまだご覧になっていない方にとっては、これが一体何をするプログラムかということは、まだよく分からないかもしれませんが、これで一応FLEXプログラム環境で、うまく動作するかどうかの確認ができたと思います。

FLEXプログラムでは、大まかに次のような構造になっているようです。

定義や初期Cコード
%%
規則部分
%%
その他Cコード

プログラム中に直接C言語のコードを含める場合には、そのコードを%{と%}の間に含めます。

%{

#define YY_SKIP_YYWRAP
int yywrap( void ) { return(1) ; }

%}

上記%{と%}の間に含まれる二行については、ラッパー関数といわれる定義の部分ですが、ここでは深入りせずに、このように書くということで進めたいと思います。 (詳しくは、yywrapの説明を参照

また、さまざまな規則は、%%と%%に挟まれた行間に記述し、その他のCコードは二番目の%%の次の行以後に書きます。

int main()
{
    yylex();
    return(0);
}

上記のCコードについては、その他のCコードにあたる部分ですが、yylex()が実際に字句解析を行うための関数呼び出しです。これはint main( ){ }という関数内に記述しますが、これについてもあまり深入りせずに、ubuntu版ではこのように書くということで先へ進めたいと思います。

例題FU─2 文字列を変換する(その一)

次のプログラムはtest1.lexをベースに規則部分の記述の方法について練習します。test1.lexの規則部分に次のような一行を加え、test2.lexという名前で保存します。

/* FLEX テストプログラム2 */
%{

    #define YY_SKIP_YYWRAP
    int yywrap( void ) { return(1) ; }

%}

%%

"あいうえお"     { printf("かきくけこ"); }

%%

int main()
{
    yylex();
    return(0);
}

このtest2.lexを先ほどのtest1.lexと同様に、バッチファイルを利用してコンパイルし、次のようにex1.txtを読み込むように実行します。

この結果から、規則部分に"あいうえお"     {printf("かきくけこ");}と記述すると、ex1.txtファイル中の「あいうえお」という文字列がそっくり「かきくけこ」という文字列に置き換えられて表示されました。つまり、"あいうえお"の後に区切り文字(空白、タブ)をはさんで、{printf("かきくけこ");}と書くと、"あいうえお"という文字列を見つけるたびに、"かきくけこ"と出力せよという規則になります。{ }内にはC言語のプログラムをそのまま書くことができます。printf("かきくけこ");とは文字列「かきくけこ」を出力させるC言語のコードです。

ここで規則部分は必ず行の先頭から記述してください。行頭に空白やタブが入っているとFLEXからエラーメッセージが出ます。

表示された内容をそのまま別のファイルとして保存する場合には、次のようにコマンドプロンプトのリダイレクション機能を利用します。ここでは保存するファイル名をexout.txtとしました。


exout.txtの内容をメモ帳で表示させてみます。

また、このFLEXによる規則は入力ファイルの1箇所だけではなく、すべての箇所について適用されます。

例題FU─3 文字列を変換する(その二)

次に複数の規則を記述してみます。次のようなFLEXプログラムを書いて、これをtest3.lexとして保存します。

/* FLEX テストプログラム3 */
%{

#define YY_SKIP_YYWRAP
int yywrap( void ) { return(1) ; }

%}

%%

"らりるれろ"    { printf("あいうえお"); }
"やゆよ"        { printf("わをん"); }
"わをん"        { printf("やゆよ"); }

%%

int main()
{
    yylex();
    return(0);
}

次にGNOMEテキストエディタなどで、次のようなテキストを入力してex3.txtという名前で保存します。

らりるれろ
わをん
なにぬねの
らりるれろ
やゆよ
がぎぐげご
たちつてと
ぱぴぷぺぽ
やゆよ

このtest3.lexをコンパイルし、次のようにex3.txtを読み込むように実行します。

規則部分で指定したように、入力ファイルの内容が変換されたことが分かります。このように複数の規則でも、%%と%%に挟まれた行間に並べて記述することができます。ここではFLEX文法の詳細については立ち入りませんが、より知識を深めたい方は、ダウンロードしたFLEXプログラムに付随するマニュアル/ドキュメント等を参照してください。

例題FU─4 簡単なC言語風日本語プログラムの実行

次に簡単なC言語風の日本語プログラムをC言語に変換するためのFLEXプログラムを作ってみます。ex4.txtファイルを作成して、以下のように入力、保存しておきます。

ここで、前回までのFLEXプログラムでは、初期Cコードとしてラッパー関数の定義をそのまま記述しておりましたが、これを別のインクルードファイルflex_test.incに記述します。

このようにするとFLEXプログラムは、よりすっきり見えるのではないかと思います。これに変換規則を適用させたものをtest4.lexとします。

/* FLEX テストプログラム4 */
%{

#include "flex_test.inc"

%}


%%

"型なし"     { printf("void"); }
"整数型"     { printf("int"); }
"はじまり"   { printf("main"); }
"印字"       { printf("printf"); }

%%

int main()
{
    yylex();
    return(0);
}

このtest4.lexをコンパイルし、次のようにex4.txtを読み込むように実行します。

また、この結果をexout.cというファイルにして、C言語の処理系に通せば、立派なプログラムとして動作することも分かります。(C言語処理系のgccでは、オプションを何も指定しなければ、デフォルトでa.outという実行可能ファイルを出力します)

例題FU─5 FLEX規則を利用してC言語を日本語で記述する

flexやgccにはいくつかのコマンドラインオプション機能があります。-oオプションを指定することにより、出力ファイル名の指定もできますので、今までのシェルスクリプトを以下のように変更します。これでよりすっきりとコンパイル、実行ができるようになると思います。

シェルスクリプトの内容(lcc.sh)
#!/bin/bash
flex -o$1 $1.lex
gcc -o$1 $1.c
exit 0

次に日本語カスタマイザーC言語編からの例題ですが、構造体を使ったC言語風の日本語プログラムをC言語に変換するFLEXプログラムを作ってみます。ex5.txtファイルを作成して、以下のように入力、保存しておきます。

/* 個人情報を印字する */
#include <stdio.h>

構造体 個人情報型 {
        文字型 名前[30];
        整数型 年齢;
        文字型 性別[4];
        整数型 生まれた年;
        整数型 生まれた月;
        整数型 生まれた日;
};

整数型 はじまり(型なし)
{

        構造体 個人情報型 鈴木さんの記録 = {"鈴木太郎",46,"男",1963,2,14};
        構造体 個人情報型 山田さんの記録 = {"山田花子",35,"男",1974,8,23};

        印字("名前=%s\n",鈴木さんの記録.名前);
        印字("年齢=%d歳\n",鈴木さんの記録.年齢);
        印字("性別=%s\n",鈴木さんの記録.性別);
        印字("生年月日=%d年",鈴木さんの記録.生まれた年);
        印字("%d月",鈴木さんの記録.生まれた月);
        印字("%d日生まれ\n\n",鈴木さんの記録.生まれた日);

        印字("名前=%s\n",山田さんの記録.名前);
        印字("年齢=%d歳\n",山田さんの記録.年齢);
        印字("性別=%s\n",山田さんの記録.性別);
        印字("生年月日=%d年",山田さんの記録.生まれた年);
        印字("%d月",山田さんの記録.生まれた月);
        印字("%d日生まれ\n",山田さんの記録.生まれた日);

        通常終了;

}

Windows版のFLEXでは、シフトJISコードの2バイト目が16進数で「5C」のコード(文字ではバックスラッシュ、¥マーク)は、文字としてうまく認識させるときには注意が必要でした。しかし、ubuntu版では文字コード体系がUTF-8というUNICODEの文字体系が採用されていますので、特に気にしなくても規則のパターンマッチ部分は、日本語で書くことができるようです。

/* FLEX テストプログラム5 */
%{

#include "flex_test.inc"

%}

%%

"型なし"         { printf("void"); }
"整数型"         { printf("int"); }
"文字型"         { printf("char"); }
"はじまり"       { printf("main"); }
"通常終了"       { printf("return(0)"); }
"印字"           { printf("printf"); }
"構造体"         { printf("struct"); }
"個人情報型"     { printf("person"); }

"名前"           { printf("jp_str_1"); }
"年齢"           { printf("jp_str_2"); }
"性別"           { printf("jp_str_3"); }
"生まれた年"     { printf("jp_str_4"); }
"生まれた月"     { printf("jp_str_5"); }
"生まれた日"     { printf("jp_str_6"); }
"鈴木さんの記録" { printf("jp_str_7"); }
"山田さんの記録" { printf("jp_str_8"); }

%%

int main()
{
    yylex();
    return(0);
}

このtest5.lexをコンパイルし、ex5.txtを読み込むと次のような結果が表示されます。

/* 個人情報をprintfする */
#include <stdio.h>

struct person {
        char jp_str_1[30];
        int jp_str_2;
        char jp_str_3[4];
        int jp_str_4;
        int jp_str_5;
        int jp_str_6;
};

int main(void)
{

        struct person jp_str_7 = {"鈴木太郎",46,"男",1963,2,14};
        struct person jp_str_8 = {"山田花子",35,"男",1974,8,23};

        printf("jp_str_1=%s\n",jp_str_7.jp_str_1);
        printf("jp_str_2=%d歳\n",jp_str_7.jp_str_2);
        printf("jp_str_3=%s\n",jp_str_7.jp_str_3);
        printf("生年月日=%d年",jp_str_7.jp_str_4);
        printf("%d月",jp_str_7.jp_str_5);
        printf("%d日生まれ\n\n",jp_str_7.jp_str_6);

        printf("jp_str_1=%s\n",jp_str_8.jp_str_1);
        printf("jp_str_2=%d歳\n",jp_str_8.jp_str_2);
        printf("jp_str_3=%s\n",jp_str_8.jp_str_3);
        printf("生年月日=%d年",jp_str_8.jp_str_4);
        printf("%d月",jp_str_8.jp_str_5);
        printf("%d日生まれ\n",jp_str_8.jp_str_6);

        return(0);

}

この結果をexout.cというファイルに保存し、コンパイルして実行結果を確認します。

実行結果からも分かるように、実際の(C言語用)日本語カスタマイザーでは、二重引用符(ダブルクォーテーション)で囲まれた文字列は変換しないようにしておりますが、今回のテストプログラムでは、すべての文字列について変換規則が適用されています。

例題FU─6 文字クラスを使った日本語文字処理の基本練習

ここで、少し文字コードについて触れておきたいと思います。ubuntuのコマンドプロンプトで、GNOMEテキストエディタを使用してファイルを保存する場合をここでは想定します。このとき文字コードは通常UTF-8として保存されます。これはUNICODEと呼ばれるコード体系の中でも、日本語文字を3~4バイトで表現することができます。`UTF-8は多国語をエンコードする方法の一つで、UNIXのシステムプログラマーのKen Thompson氏が策定に貢献したことでも有名です。

また、英語圏で通常使用されるアスキー(ascii)コードは、1バイトコードであり、16進数で00~7fですが、UTF-8でもアスキーコードと同じ半角文字は1バイト目に同じコードが割り当てられております。

さらに、日本独自の規格である1バイトカタカナ(半角カタカナ)も、UTF-8ではコードが割り当てられています。

このあたりの様子を理解するためにも、次のようなtest6.lexというFLEXプログラムを書いてみます。ただし、文字クラスと呼ばれる定義部については、一行があまりに長くなり過ぎたため少し見づらいとは思いますが、GNOMEテキストエディタの右端折り返しで表示しております。

/* FLEX テストプログラム6 */
%{

#include "flex_test.inc"

#define      pr_text     printf("%s",yytext)

%}


%%

{katakana_1}     { }

[[:alpha:]]      { }

[[:digit:]]      { }

{katakana_2}     { }

{hiragana}       { }

{utf8_code}      { }

.                { }

\n               { pr_text; }

%%

void main()
{
        yylex();
}

文字クラスの詳細な文法については、ここでは触れませんが、文字クラスのラベルとそれに対する文字集合を定義できます。test6.lexにおいて、それぞれの意味は次のようなものです。

katakana_1 半角カタカナ文字の定義
katakana_2 全角カタカナ文字の定義
hiragana ひらがな文字の定義
utf8_code 日本語文字を含む文字コードを数バイト単位で定義したコード
[[:alpha:]] V2.5で使用できる1バイトのアルファベット(大文字と小文字)の文字クラス式
[[:digit:]] V2.5で使用できる1バイトの数字の文字クラス式
. 文字クラスではないが、上記と改行以外の1バイト文字
\n 文字クラスではないが、改行文字

UTF-8コードについては、漢字や記号コードのすべてを列挙すると、それだけで膨大な量になりますので、簡易的な定義をしております。また、このようにすることでよく利用する漢字や記号のすべてについてスキャンすることができます。

Windowsなどから、UTF-8コード以外で作られたテキストファイルをGNOMEテキストエディタで読み込む場合には、注意が必要です。表面には現れませんが、FLEXで重要な働きをするバックスラッシュ(\マーク)コードが内部で化けるような現象も確認しております。 また、改行コードもWindowsからのファイルでは、GNOMEテキストエディタで読み込んだときに「\r\n」となりますので、これを「\n」で置き換えることも必要のようです。簡単な例題などは、UTF-8に設定しておいて、文字はなるべくタイピングで入力することをお勧めします。

ここで、以下のようなテキストを作り、ex6.txtというファイルに保存します。

最初に定義したtest6.lexをコンパイル、実行してex6.txtを読み込ませます。そうすると改行だけが表示されます。

2番目にFLEXプログラムtest6.lexの規則部分を以下のように変更します。

%%

{katakana_1}     { pr_text; }

[[:alpha:]]      { }

[[:digit:]]      { }

{katakana_2}     { }

{hiragana}       { }

{utf8_code}      { }

.                { }

\n               { pr_text; }

%%

これをコンパイル、実行して、ex6.txtを読み込ませます。すると1バイトカナカナ文字だけが表示されます。ここでpr_textというのは、パターンマッチした文字を表示させるためのマクロ名です。マクロの定義は#defineで定義してあります。

3番目にFLEXプログラムtest6.lexの規則部分を以下のように変更します。

%%

{katakana_1}     { }

[[:alpha:]]      { pr_text; }

[[:digit:]]      { }

{katakana_2}     { }

{hiragana}       { }

{utf8_code}      { }

.                { }

\n               { pr_text; }

%%

これをコンパイル、実行してex6.txtを読み込ませます。すると1バイトのアルファベットだけが表示されます。

4番目にFLEXプログラムtest6.lexの規則部分を以下のように変更します。

%%

{katakana_1}

[[:alpha:]]

[[:digit:]]       { pr_text; }

{katakana_2}

{hiragana}

{utf8_code}

.

\n               { pr_text; }

%%

これをコンパイル、実行してex6.txtを読み込ませます。すると1バイトの数字だけが表示されます。このとき規則部のパターンマッチ条件を記述して、それに対するアクション部分に{ }という空文を定義していたところが、今回はアクション部には何も記述していません。しかし、アクション部に何も書かないことと、{ }という空文を書くこととは結果的に同じであるといえます。

5番目にFLEXプログラムtest6.lexの規則部分を以下のように変更します。

%%

{katakana_1}

[[:alpha:]]

[[:digit:]]

{katakana_2}    { pr_text; }

{hiragana}

{utf8_code}

.

\n              { pr_text; }

%%

これをコンパイル、実行してex6.txtを読み込ませます。する全角のカタカナ文字だけが表示されます。

6番目にFLEXプログラムtest6.lexの規則部分を以下のように変更します。

%%

{katakana_1}

[[:alpha:]]

[[:digit:]]

{katakana_2}

{hiragana}     { pr_text; }

{utf8_code}

.

\n             { pr_text; }

%%

これをコンパイル、実行してex6.txtを読み込ませます。するとひらがな文字だけが表示されます。

7番目にFLEXプログラムtest6.lexの規則部分を以下のように変更します。

%%

{katakana_1}

[[:alpha:]]

[[:digit:]]

{katakana_2}

{hiragana}

{utf8_code}    { pr_text; }

.

\n             { pr_text; }

%%

これをコンパイル、実行してex6.txtを読み込ませます。すると漢字や句読点だけが表示されます。

8番目にFLEXプログラムtest6.lexの規則部分を以下のように変更します。

%%

{katakana_1}

[[:alpha:]]

[[:digit:]]

{katakana_2}

{hiragana}

{utf8_code}

.                 { pr_text; }

\n                { pr_text; }

%%

これをコンパイル、実行してex6.txtを読み込ませます。するとこれまでに表示されていない1バイトの文字が表示されます。

最後にFLEXプログラムtest6.lexの規則部分を以下のように変更します。

%%

{katakana_1}      { pr_text; }

[[:alpha:]]       { pr_text; }

[[:digit:]]       { pr_text; }

{katakana_2}      { pr_text; }

{hiragana}        { pr_text; }

{utf8_code}       { pr_text; }

.                 { pr_text; }

\n                { pr_text; }

%%

これをコンパイル、実行してex6.txtを読み込ませます。するとすべての文字が表示されます。

例題FU─7 FLEXを利用した送り仮名のチェック

近年、日本語文書処理の中心はなんといっても日本語ワードプロセッサー(略してワープロ)ではないでしょうか。最近のワープロは専用の処理言語を搭載したものが珍しくないという状況になっているようです。

ワープロに標準で装備されていない機能でも、付属の処理言語を使えば機能を増やすことができるようになっているようです。しかし、それは一般の人々には非常に難解な部分でもあります。このときFLEXで日本語処理を行えば、かなり楽にできることもたくさんあるのではないでしょうか。

その一例として、送り仮名のチェックがあります。最近はパソコンに付属の標準日本語入力システムのみならず、さまざまな日本語入力システムが使用されています。そのため、かな漢字変換機能を利用すれば、送り仮名についてあまり意識せずに使うことができるようになっております。しかし、中には日本語入力システムでも、あいまいさを残した語句もあります。次のような文章を入力して、ex7.txtとします。

この文章では「押さえる」を「押える」という送り仮名を使っています。また、「捕らえる」を「捕える」という送り仮名を使っていますが、送り仮名の付け方として、誤読、難読がないようにという通則に従ってチェックをするプログラムをtest7.lexとします。

文字クラス定義部分については、test6.lexと同じ内容です。また、規則部分についても一行が長くなり過ぎたため、少し見づらいとは思いますが、メモ帳の表示画面を右端で折り返すモードで表示しております。さらにpr_cmntは文字列を表示させるマクロ定義です。

/* FLEX テストプログラム7 */
%{

#include    "flex_test.inc"

#define     pr_cmnt(str)     printf("%s\n",str)

%}

この文字クラス定義部はtest6.lexと同じ内容です。





int main()
{
        yylex();
        return(0);
}

これをコンパイル、実行してex7.txtを読み込ませます。すると「押える」、「捕われる」についての注意事項が表示されます。このとき「捕われる」「捕える」を同じメッセージとして検出するために、パターンマッチ部の「捕わ」と「捕え」を「|」(バーティカル・バー)で結び、「または」という意味を持たせています。

例題FU─8 FLEXを利用した簡単な同音異義語のチェック

次もFLEXプログラムで、文書チェッカーの機能を練習してみます。

日本語は同音異義語が大変多く存在する言語ではないでしょうか。この同音異義語は、かな漢字変換時には変換候補として表示されることもありますが、最終的には人間が選択し、決定をしなければなりません。うっかりすると用法をよく確かめずに、同音異義語の使い方を誤って使用してしまう場合もあるかもしれません。このようなことを防ぐためにも、文書を作成した後でチェッカーを利用して全体を校正することが考えられます。

いま、次のような同音異義語の使い方が適切でない文書を作成し、これをex8.txtとして保存します。

この文章では、「遍在」(どこにでも広くあるという意)を「偏在」(かたよっているという意)という言葉を使っています。また、「機運」(機会、時機がめぐるという意)を「気運」(歴史的背景の中での時流の意)という言葉を使っています。さらに「不断」(たゆまないという意)を「普段」(日常、平生の意)という言葉を使っています。これらをチェックをするFLEXプログラムをtest8.lexとします。

/* FLEX テストプログラム8 */
%{

#include    "flex_test.inc"

#define     pr_cmnt(str)     printf("%s\n",str)

%}


この文字クラス定義部はtest6.lexと同じ内容です。


int main()
{
        yylex();
        return(0);
}

これをコンパイル、実行してex8.txtを読み込ませます。すると同音異義語についての注意事項が表示されますので、文書作成者がこれに気付いて、あらためて注意箇所を校正することができます。

例えば、日本語ワープロで1ページ(40文字、36行)あたり1,440文字とすると、10ページで軽く1万文字を超えてしまいます。つまり一つの文書で数万文字を扱うというようなことは、日常的によくあることだと考えられます。この数万文字について、送り仮名や同音異義語をはじめ、表記についてもさまざまな規則や要求があると考えれらます。このような日本語文書を取り巻く状況で、大量の文書を素早く必要に応じてチェックをするために、FLEXで日本語文書処理を行うことは大変便利であると感じております。

例題FU─9 FLEXでスタート状態を使った練習

パターンマッチ部でこれらの文字を使用する場合には、文字ではなく文字コードで定義しないといけません。ここで、次のような2バイト目が「5C」の文字である「暴」を含む文書を作成し、ex9.txtとして保存します。

この文書において、「無暴」を「無謀」、「無謀」を「無暴」と自動的に変換するFLEXプログラムをtest9.lexとします。 ただし、二重引用符(ダブルクォーテーション)で囲まれた「無謀」という文字列については、変換しないというテクニックも使ってみます。

/* FLEX テストプログラム9 */
%{

#include    "flex_test.inc"

#define     pr_str printf("%s",yytext)

%}

%x string

%%

\"                    { pr_str; BEGIN(string);}
<string>\"            { pr_str; BEGIN(INITIAL);}
<string>.             { pr_str; }

"無暴"                { printf("無謀"); }
"無謀"                { printf("無暴"); }

%%

int main()
{
        yylex();
        return(0);
}

これをコンパイル、実行してex9.txtを読み込ませます。 すると「無暴」を「無謀」と変換してくれますが、二重引用符(ダブルクォーテーション)で囲まれた「無謀」という文字列はそのまま残っていることが分かります。

ここで「%x string」という記述部分は、スタート状態というFLEXの機能を使っています。FLEXの文法について詳しいことは触れませんが、スタート状態によってある規則が活性化する条件をFLEXに通知するための論理値のようなものとされています。これによって二重引用符が認識されると<string>で始まる規則部分が適用されます。また、二度目の二重引用符が認識されると通常の規則処理に戻ります。

二重引用符の処理テクニックは日本語カスタマイザーでは、コメントの処理にも用いられています。ただし、C言語などは二重引用符の中でもさらに「\"」などが使われたりしますので、実際の日本語カスタマイザーではこれらのことにも対処できるようにしております。

例題FU─10 FLEXを利用した簡単な表記チェッカー

文章を書くとき注意する事柄の一つに、表記の問題があると思います。日本語の中では、ある漢字複合語について別の読み方と区別するために、表記を分けて記述することがあります。 例えば名詞、形容動詞の「じょうず」を漢字で「上手」と書く場合がありますが、形容詞として「うまい」を「上手い」と同じ漢字を用いて表記する場合などです。これに対して、この二つの読み方を明確に分けるために、「上手い」という読み方に対しては、すべて「うまい」という平仮名で表記するという規則が求められる場合もあるかもしれません。 また、自分流の文書作成スタイルでは、特にこの二つを分けて書く習慣がない方もいらっしゃると思います。このようなときに、文書をすべて作成した後、一括して表記をチェックするという例についてFLEXプログラムで書いてみます。 次のような「上手」(じょうず)と「上手い」(うまい)を含む文章を作成し、ex10.txtとして保存します。

この文書において、「上手い」を「うまい」、「上手く」を「うまく」という表記上の注意を文章内に埋め込むFLEXプログラムをtest10.lexとします。ただし、「上手」(じょうず)という文字列については、何もしないというようにします。

/* FLEX テストプログラム10 */
%{

#include    "flex_test.inc"

#define     pr_str printf("%s",yytext)

%}

KEIYOSHI_1         ("い"|"か"|"く"|"け")

%%

"上手"/{KEIYOSHI_1}   { printf("[注意]上手→うま"); }

%%

int main()
{
        yylex();
        return(0);
}

「上手い」は「い」だけでなく、「上手かろう」「上手く」「上手ければ」などの活用が考えられますので、「上手」の後に続く数種類の平仮名を認識できることが必要となります。ここでは、文字クラスの中で数種類の平仮名を一括して定義しています。パターンマッチ部で用いる"/"(スラッシュ記号)は、先読み演算子として機能します。従って、「上手」に続く「い」「か」「く」「け」などがある限りパターンマッチしますが、そのときの「い」「か」「く」「け」は、まだマッチの対象とはなりません。 これをコンパイル、実行してex10.txtを読み込ませます。すると「上手い」や「上手く」について表記の注意はそこに記入されますが、「上手」(じょうず)の場合は何もしないことが分かります。

例題FU-11 FLEXでCGIを動かす(その一)

近年インターネット利用者数は、国内だけでも9,000万人を超えたといわれておりますが、ネットサーフィンをするときには、気が付かないうちにCGI(コモン・ゲート・インターフェース)と呼ばれるプログラムが動作していることがあります。これは、ホームページなどを閲覧するブラウザーと呼ばれるソフトウェアが、インターネットサーバー上の情報を閲覧するだけではなく、ユーザーの方からサーバー側に情報を発信するときなどに、サーバー側の処理機能としてよく利用されています。

ubuntuというLinux/Unix系OS上では、apache2という定番のウェブサーバーソフトが用意されています。これも システム管理者であれば、インターネットから簡単にダウンロードすることができます。CGIはこのサーバーソフトと連動させるものです。最近は、Perlというスクリプト言語がCGIとして有名ですが、今回はこのCGIにFLEXを利用してみます。もともとC言語はCGIとして利用されることがありましたので、FLEXでもCGIは記述できるはずです。

それではまず、テスト用のホームページtest11.htmをHTML言語で以下のように記述します。また、test11.htmは/var/www/ディレクトリ下に システム管理者権限でコピーしておきます。ここでは、formとinput要素を使って、氏名、郵便番号、住所が入力できるようにしました。送信ボタンをクリックすると、test11.cgiというプログラムが動作するようにします。

<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<title>CGIのテスト</title>
</head>
<body>
<form METHOD="POST" ACTION="http://localhost/cgi-bin/test11.cgi">
名 前<input TYPE="TEXT" NAME="NAME" size="18" ><br><br>
郵便番号<input TYPE="TEXT" NAME="ZIP" size="10"><br><br>
住 所<input TYPE="TEXT" NAME="ADDRESS" size="40"><br><br>
<input TYPE="submit" VALUE="送信">
</body>
</html>

test11.cgiを作るために、FLEXでtest11.lexというプログラムを以下のように記述しておきます。ここでは、inputで指定したNAMEに対応して、文字列を表示するという簡単なものです。また、「&」文字を受け取ると、ブラウザー上で改行するようにしています。

/* FLEX テストプログラム11 */

%{

    #include "flex_test1.inc"

%}

%%

NAME=    { pr_text; }
ZIP=     { pr_text; }
ADDRESS= { pr_text; }
&        { printf("<br>"); }

%%

int main()
{

        pr_web_out1;

        yylex();

        pr_web_out2;

        return(0);
}

また、flex_test1.incの内容は以下のようになります。POSTメソッドの場合には、最初に"CONTENT_LENGTH"という環境変数によって、文字列全体の長さをチェックすることが義務付けられておりますので、その部分の最大長さを10,000バイトとしております。

#define YY_SKIP_YYWRAP
int yywrap( void ) { return(1); }

#define pr_text printf("%s",yytext)

#define MAX_DATA_SIZE    10000

#define check_size       if((int)getenv("CONTENT_LENGTH") > MAX_SIZE ) exit(1)

#define pr_web_out1 check_size;\
                    printf("content-type: text/html\n");\
                    printf("\n");\
                    printf("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n");\
                    printf("<html lang=\"ja\">\n");\
                    printf("<head>\n");\
                    printf("<meta http-equiv=\"Content-Language\" content=\"ja\">\n");\
                    printf("<meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">\n");\
                    printf("<body>\n")

#define pr_web_out2 printf("</body></html>\n")

test11.lexをflexとgccでコンパイルした後、実行可能ファイル名をtest11.cgiに変更し、ubuntuの場合には、/usr/lib/cgi-bin/というディレクトリの下にシステム管理者権限でコピーしておきます。

ここで、apacheサーバーが動いているときには、http://localhost/test11.htmをブラウザーで表示させると以下のようになりますので、実際に名前、郵便番号、住所の項目に入力して、送信ボタンをクリックします。

すると、以下のようにNAME(名前)、ZIP(郵便番号)、ADRESS(住所)に対応した文字列を受け取って表示します。また、HTML言語のform要素において、POSTメソッドを指定した場合には、標準入力で文字列が受け渡されます。ただし、日本語の文字列は%に続く数字によって表されています。(実際には、ADRESSに対応する文字列が表示幅を超えていますので、すべての文字列が表示されているわけではありません)

このようにapacheサーバー上では、FLEXプログラムがCGIとして動作することが分かりました。

例題FU-12 FLEXでCGIを動かす(その二)

次に、apacheサーバーが動作しているubuntuマシンに、ローカルエリアで接続されているWindowsパソコンからアクセスして、CGI動作を確認してみます。また、今回はCGIに渡された文字列を正しい日本語に変換するということもやってみたいと思います。

まず、テスト用のホームページtest12.htmをHTML言語で以下のように記述し、test12.htmとして システム管理者権限で/var/www/ディレクトリ下にコピーしておきます。送信ボタンをクリックすると、test12.cgiというプログラムが動作するようにします。

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<title>CGIのテスト</title>
</head>
<body>

<form METHOD="POST" ACTION="/cgi-bin/test12.cgi">
名 前<input TYPE="TEXT" NAME="NAME" size="18" ><br><br>
郵便番号<input TYPE="TEXT" NAME="ZIP" size="10"><br><br>
住 所<input TYPE="TEXT" NAME="ADDRESS" size="40"><br><br>
<input TYPE="submit" VALUE="送信">

</body>
</html>

test12.cgiは、FLEXでtest12.lexというプログラムにより記述します。ここでは、前回のtest11.lexに加えて文字列を変換する機能を付けています。一応UTF-8で 連続した3バイト分のコードパターンだけを文字に変換することはやっていますが、あくまでも簡単なテスト的なものです。また、郵便番号は赤文字で表示させるようにしてみます。(flex_test1.incは前回と同じものです)

/* FLEX テストプログラム12 */

%{

#include <string.h>

#include "flex_test1.inc"

#define ch_code(ch1,ch2)    ( ( ch1 & 0x40 ? ch1 - 0x37 : ch1 - 0x30 ) << 4 ) +\
                              ( ch2 & 0x40 ? ch2 - 0x37 : ch2 - 0x30 )

%}

zip_type [0-9]+-[0-9]+
other_char         .

%%

NAME=       { pr_text; }
ZIP=        { pr_text; }
ADDRESS=    { pr_text; }
&           { printf("<br>"); }

{zip_type} { printf("<font color=\"red\">%s</font>",yytext); }

(%..)(%..)(%..)     { printf("%c%c%c",
                        ch_code(yytext[1],yytext[2]),
                        ch_code(yytext[4],yytext[5]),
                        ch_code(yytext[7],yytext[8])); }
{other_char}

%%


int main()
{

        pr_web_out1;

        yylex();

        pr_web_out2;

        return(0);
}

test12.lexをflexとgccでコンパイルした後、実行可能ファイル名をtest12.cgiに変更しシステム管理者権限で/usr/lib/cgi-bin/ディレクトリの下にコピーしておきます。

ここで、ローカルエリアに接続されているWindowsパソコン上からは、ubuntuのローカルアドレス(プライベートIPアドレス)が192.168.3.Xとして見えることを確認して、http://192.168.3.X/test12.htmをブラウザーで表示させます。(Xは接続しているルーターによって決められている範囲内の数字です)

実際に名前、郵便番号、住所の項目に入力して、送信ボタンをクリックします。

前回と違い、今度は日本語に変換されて表示されました。

これまではHTML言語のform要素で、POSTメソッドを使用して文字列のやり取りを行いましたが、form要素にはGETメソッドという方法により、文字列のやり取りもできるようになっています。POSTメソッドの場合には、標準入力が文字列のやり取りに使われているため、FLEX側では特に何も気にせずに字句解析できましたが、GETメソッドを使用する場合にはFLEXプログラム側もひと工夫する必要があるようです。GETメソッドでは環境変数の「QUERY_STRING」というものが使用されます。

GETメソッドテスト用のホームページtest12-G.htmをHTML言語で以下のように記述し、test12-G.htmとして/var/www/ディレクトリ下にコピーしておきます。送信ボタンをクリックすると、test12-G.cgiというプログラムが動作するようにします。

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<title>CGIのテスト</title>
</head>
<body>

<form METHOD="GET" ACTION="/cgi-bin/test12-G.cgi">
名 前<input TYPE="TEXT" NAME="NAME" size="18" ><br><br>
郵便番号<input TYPE="TEXT" NAME="ZIP" size="10"><br><br>
住 所<input TYPE="TEXT" NAME="ADDRESS" size="40"><br><br>
<input TYPE="submit" VALUE="送信">

</body>
</html>

test12-G.cgiは、test12-G.lexというプログラムにより記述します。ここでは、環境変数の「QUERY_STRING」をシェルコマンドの「printenv」で表示させる機能がありますが、これをC言語のpopenというパイプ機能を使ってFLEXのyyinで読めるようにしています。また、「YY_NEW_FILE」により、yyinが新しいファイルを指すようにしています。

/* FLEX テストプログラム12-G */

%{

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

#include "flex_test2.inc"

#define ch_code(ch1,ch2)    ( ( ch1 & 0x40 ? ch1 - 0x37 : ch1 - 0x30 ) << 4 ) +\
                              ( ch2 & 0x40 ? ch2 - 0x37 : ch2 - 0x30 )

%}

zip_type [0-9]+-[0-9]+
other_char         .

%%

NAME=    { pr_text; }
ZIP=     { pr_text; }
ADDRESS= { pr_text; }

& { printf("<br>"); }
{zip_type} { printf("<font color=\"red\">%s</font>",yytext); }

(%..)(%..)(%..)     { printf("%c%c%c",
                        ch_code(yytext[1],yytext[2]),
                        ch_code(yytext[4],yytext[5]),
                        ch_code(yytext[7],yytext[8])); }
{other_char}

%%


int main()
{

        pr_web_out1;

        yyin = popen( "printenv QUERY_STRING","r" );
        YY_NEW_FILE;

        yylex();

        pclose(yyin);

        pr_web_out2;

        return(0);
}

また、flex_test2.incの内容は以下のようになります。

#define YY_SKIP_YYWRAP
int yywrap( void ) { return(1); }

#define pr_text printf("%s",yytext)

#define pr_web_out1 printf("content-type: text/html\n");\
                    printf("\n");\
                    printf("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n");\
                    printf("<html lang=\"ja\">\n");\
                    printf("<head>\n");\
                    printf("<meta http-equiv=\"Content-Language\" content=\"ja\">\n");\
                    printf("<meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">\n");\
                    printf("<body>\n")

#define pr_web_out2 printf("</body></html>\n")

ここで、http://192.168.3.X/test12-G.htmをブラウザーで表示させます。(Xは接続しているルーターによって決められている範囲内の数字です)

実際に名前、郵便番号、住所の項目に入力して、送信ボタンをクリックします。

このようにGETメソッドでも、FLEXプログラムのCGIは、うまく動作することを確認しました。今回は簡単のためにローカルアドレスをそのまま使用しましたが、ubuntuでは/etc/hostsをはじめ、他の設定ファイルを編集することにより、ドメインネームが使用できます。また、Windwosでも同様にhostsファイルを編集することで、ローカルアドレスをドメインネームとして使うことができます。(説明が長くなりますので、ここでは割愛させていただきます)

さらに、apacheサーバーを外部に公開し、グローバルIPアドレス(WAN側)から参照するときには、ルーターのDHCP設定や静的IPマスカレード設定などを適切に行えば、グローバルIPアドレスでホームページ参照や、CGIの実行ができるようになります。

例題FU-13 自動番号の付いた規則をつくる

これまで種々のFLEXを利用した文字処理の練習問題を提示してきましたが、これらの基本的なテクニックを使用して、日本語カスタマイザーのヒントともいうべき練習をやってみます。

次のようなテキストファイルex13.txtを準備します。このテキストには、各行の先頭に「#日本語定義」という文字列があります。その後に区切り文字(空白、タブ)をはさんで日本語の文字列が入力されています。

このテキストファイルをFLEXプログラムから読み込むと、FLEXの規則部分を出力するようにします。

/* FLEX テストプログラム13 */
%{

#include "flex_test.inc"

int auto_num = 0;
#define pr_text         printf("%s",yytext)
#define pr_dq_text      printf("\"%s\"",yytext)

#define pr_autonum      printf(" { printf(\"jp_str_%-d\"); }\n",auto_num);++auto_num
#define pr_rule         printf("%%%%\n")


%}

この文字クラス定義部はtest6.lexと同じ内容です。

dlmt [ \t]

%x JP_DEF
%x JP_STRING

%%

^"#日本語定義"         { BEGIN(JP_DEF); }

<JP_DEF>{dlmt}+        { BEGIN(JP_STRING); }

<JP_STRING>{utf8_code} { pr_dq_text; }

<JP_STRING>\n        { pr_autonum; BEGIN(INITIAL); }

.
\n

%%

int main()
{
    pr_rule;
    yylex();
    pr_rule;

    return(0);

}

このFLEXプログラムでは、あらかじめ「JP_DEF」状態と「JP_STRING」状態の二つの状態を持っています。まず、テキスト各行の先頭で「#日本語定義」という文字列を見つけると、「JP_DEF状態」に遷移します。

そして、区切り文字(空白、タブ)が一つ以上あれば、さらに「JP_STRING状態」に遷移します。そして、日本語文字列を認識するたびにダブルクォートで囲んで出力するという処理を行っています。さらに、改行を認識すると初期状態に戻ります。ここで、区切り文字は文字クラスで、dlmtという名前で定義しています。

このプログラムを実行した結果を以下に示します。

このプログラムは、規則の部分を非常にシンプルに書いていますが、実際の日本語カスタマイザーは、もう少し複雑な処理を行っていて、日本語文字列の間に半角英数記号が入っていたときや、コメント処理などのさまざまな文字列の場合に、柔軟な対応ができるようにしてあります。

この例題プログラムでは、日本語文字一文字をそれぞれダブルクォートで囲んでいますが、このような書き方をしても、FLEX処理系は問題なく処理してくれるようです。また、jp_strという文字列に続く自動番号は、マクロ定義によってシンプルに実現しています。さらに、FLEXの規則部分に使用される「%%」記号は、main()において、yylex()による字句解析の前と後でそれぞれ出力しています。

また、この例題だけを考えると、何も状態を二つも持つ必要はありませんが、あとで機能を拡張することを考えると、二つある方が良いのではないでしょうか。簡単な例題ではありますが、日本語カスタマイザー作成の十分なヒントになると考えています。

例題FU-14 FLEX規則を利用してC言語を日本語で記述する(続編)

今回は、例題FU-5の続編として、もう少し日本語で書いたC言語風プログラムを処理するための練習をやってみたいと思います。このFLEXの記述は、日本語カスタマイザーの2パス目の処理を行うためのヒントになると考えています。

まず、次のようなテキストファイルex14.txtを準備します。ex14.txtファイルを作成して、以下のように入力、保存しておきます。このテキストは、例題FU-5で使用したテキストの先頭部分に、日本語定義の宣言が付加されたものです。

/* 個人情報を印字する */
#include <stdio.h>

#日本語定義 型なし         "void"
#日本語定義 整数型         "int"
#日本語定義 文字型         "char"
#日本語定義 はじまり       "main"
#日本語定義 通常終了       "return(0)"
#日本語定義 印字           "printf"
#日本語定義 構造体         "struct"
#日本語定義 個人情報型     "person"

#日本語定義 名前
#日本語定義 年齢
#日本語定義 性別
#日本語定義 生まれた年
#日本語定義 生まれた月
#日本語定義 生まれた日
#日本語定義 鈴木さんの記録
#日本語定義 山田さんの記録

構造体 個人情報型 {
        文字型 名前[30];
        整数型 年齢;
        文字型 性別[4];
        整数型 生まれた年;
        整数型 生まれた月;
        整数型 生まれた日;
};

整数型 はじまり(型なし)
{

        構造体 個人情報型 鈴木さんの記録 = {"鈴木太郎",46,"男",1963,2,14};
        構造体 個人情報型 山田さんの記録 = {"山田花子",35,"男",1974,8,23};

        印字("名前=%s\n",鈴木さんの記録.名前);
        印字("年齢=%d歳\n",鈴木さんの記録.年齢);
        印字("性別=%s\n",鈴木さんの記録.性別);
        印字("生年月日=%d年",鈴木さんの記録.生まれた年);
        印字("%d月",鈴木さんの記録.生まれた月);
        印字("%d日生まれ\n\n",鈴木さんの記録.生まれた日);

        印字("名前=%s\n",山田さんの記録.名前);
        印字("年齢=%d歳\n",山田さんの記録.年齢);
        印字("性別=%s\n",山田さんの記録.性別);
        印字("生年月日=%d年",山田さんの記録.生まれた年);
        印字("%d月",山田さんの記録.生まれた月);
        印字("%d日生まれ\n",山田さんの記録.生まれた日);

        通常終了;

}

例題FU-5と違うところは、「#日本語定義」という文字列から始まる一行文は、そのまま何も処理しないで、スキップさせています。これは状態遷移を利用しています。さらに、例題FU-5でも少し触れましたが、C言語の文字列中は、FLEXの置き替え規則を適用しないようにしています。これも状態遷移を利用しています。このFLEXプログラムを以下に示します。

/* FLEX テストプログラム14 */
%{

#include "flex_test.inc"

#define pr_text    printf("%s",yytext)

%}

%x SKIP
%x STRING

%%

^"#日本語定義"   { BEGIN(SKIP);}
<SKIP>\n         { BEGIN(INITIAL);}
<SKIP>.          { }

\"               { pr_text; BEGIN(STRING);}
<STRING>\"       { pr_text; BEGIN(INITIAL);}
<STRING>.        { pr_text; }

"型なし"         { printf("void"); }
"整数型"         { printf("int"); }
"文字型"         { printf("char"); }
"はじまり"       { printf("main"); }

"通常終了"       { printf("return(0)"); }

"印字"           { printf("printf"); }
"構造体"         { printf("struct"); }
"個人情報型"     { printf("person"); }

"名前"           { printf("jp_str_1"); }
"年齢"           { printf("jp_str_2"); }
"性別"           { printf("jp_str_3"); }
"生まれた年"     { printf("jp_str_4"); }
"生まれた月"     { printf("jp_str_5"); }
"生まれた日"     { printf("jp_str_6"); }
"鈴木さんの記録" { printf("jp_str_7"); }
"山田さんの記録" { printf("jp_str_8"); }

%%

int main()
{
      yylex();
      return(0);

}

このtest14.lexをコンパイルし、ex14.txtを読み込むと次のような結果が出力されます。

/* 個人情報をprintfする */
#include <stdio.h>

struct person {
    char jp_str_1[30];
    int jp_str_2;
    char jp_str_3[4];
    int jp_str_4;
    int jp_str_5;
    int jp_str_6;
};

int main(void)
{

    struct person jp_str_7 = {"鈴木太郎",46,"男",1963,2,14};
    struct person jp_str_8 = {"山田花子",35,"男",1974,8,23};

    printf("名前=%s\n",jp_str_7.jp_str_1);
    printf("年齢=%d歳\n",jp_str_7.jp_str_2);
    printf("性別=%s\n",jp_str_7.jp_str_3);
    printf("生年月日=%d年",jp_str_7.jp_str_4);
    printf("%d月",jp_str_7.jp_str_5);
    printf("%d日生まれ\n\n",jp_str_7.jp_str_6);

    printf("名前=%s\n",jp_str_8.jp_str_1);
    printf("年齢=%d歳\n",jp_str_8.jp_str_2);
    printf("性別=%s\n",jp_str_8.jp_str_3);
    printf("生年月日=%d年",jp_str_8.jp_str_4);
    printf("%d月",jp_str_8.jp_str_5);
    printf("%d日生まれ\n",jp_str_8.jp_str_6);

    return(0);

}

この結果をexout.cというファイルに保存し、コンパイルして実行結果を確認します。

例題FU-5の結果とは違い、二重引用符(ダブルクォーテーション)で囲まれた文字列は変換されないことが分かります。この例はシンプルな文字列に対応して処理していますが、実際のC言語では、文字列中の中で、さらに二重引用符が使われることもあります。一方、C言語用の日本語カスタマイザーでは、このことにも対応しています。今回は、「/*」「*/」のコメント処理について触れてはいませんが、大体この二重引用符と同じような規則の書き方になります。

また、「#日本語定義」という文字列から始まる一行文は、完全に読み飛ばし(無視)されていることも分かります。この点について、C言語用の日本語カスタマイザーでは、この行の最後にコメントが入っている場合であっても、コメント部分だけは出力するようにしてあります。以上のことから、日本語カスタマイザーの2パス目では、変換規則と共に、これらの処理ができるようにしておくことが必要となります。

例題FU-15 FLEXを利用して自動販売機のシミュレーションを行なう

今回は、日本語処理というよりも、FLEXを使って自動販売機のシミュレーションをやってみたいと思います。最近、自動販売機はおサイフケータイでも使えるようになっているみたいですが、全国的には、まだまだお札やコインで商品を購入することが一般的なようです。

普通、自動販売機の動作を考えるときには、まず状態遷移図で設計することが多いと思いますが、自分で書いてみると、かなり複雑な図になってしまいます。そこで、FLEXで直接その動作を記述できないかと考え、やってみたところ意外とすっきり書けることが分かりました。

今回の自動販売機としては、商品は「商品A」、「商品B」、「商品C」の3種類だけで、価格はそれぞれ100円、120円、150円とします。この自動販売内に投入できるお金は、1000円札、500円コイン、100円コイン、50円コイン、10円コインだけとします。1円コイン、5円コインなどは受け付けられずに、自動的にコイン返却口に戻るものとします。 また、2000円札、5000円札、10000円札も同様に紙幣判別機では受け付けられずに、入れても自動的に返却されるものとします。

さらに、1000円札、500円コイン、100円コイン、50円コイン、10円コインについては、それぞれ許容上限が決まっていて、1000円札は1枚、500円コインは3枚、100円コインは4枚、50円コインは18枚、10円コインは40枚までしか受け付けないこととし、上限を超えた紙幣やコインは自動的に返却されるものとします。この制限も今回のプログラムでチェックするようにしています。

こられのシミュレーションを行なうFLEXプログラムをtest15.lexとして、以下に示します。

/* FLEX テストプログラム15 */

%{

#include "flex_test.inc"

void ad_money( int ),out_shouhin( int );

int yen_10,yen_50,yen_100,yen_500,yen_1000,goukei = 0;

#define pr_money printf("%sが投入されました。",yytext)
#define pr_goukei printf(" 合計は%d円\n",goukei)
#define henkyaku printf("お金は返却されました\n")
#define reset yen_10, yen_50, yen_100, yen_500, yen_1000, goukei=0

#define LIMIT_10  40
#define LIMIT_50  18
#define LIMIT_100  4
#define LIMIT_500  3
#define LIMIT_1000 1

%}

%%

10円     { ad_money(10); }
50円     { ad_money(50); }
100円    { ad_money(100); }
500円    { ad_money(500); }
1000円   { ad_money(1000); }

商品A    { out_shouhin(100); pr_goukei; if( goukei==0 ) reset; }
商品B    { out_shouhin(120); pr_goukei; if( goukei==0 ) reset; }
商品C    { out_shouhin(150); pr_goukei; if( goukei==0 ) reset; }

返却      { printf("おつりを%d円返却しました。\n\n",goukei); reset; }

\n        {}
.         {}

%%

void ad_money( int yen) {

    switch (yen) {
        case 10: if (yen_10 < LIMIT_10)
                        { ++yen_10; goukei+=yen; pr_money; pr_goukei;}
                    else henkyaku; break;
        case 50: if (yen_50 < LIMIT_50)
                        { ++yen_50; goukei+=yen; pr_money; pr_goukei; }
                    else henkyaku; break;
        case 100: if (yen_100 < LIMIT_100)
                        { ++yen_100; goukei+=yen; pr_money; pr_goukei; }
                    else henkyaku; break;
        case 500: if (yen_500 < LIMIT_500)
                        { ++yen_500; goukei+=yen; pr_money; pr_goukei; }
                    else henkyaku; break;
        case 1000: if (yen_1000 < LIMIT_1000)
                        { ++yen_1000; goukei+=yen; pr_money; pr_goukei; }
    }
}

void out_shouhin( int yen) {

    if ( goukei - yen >=0 ) {
            goukei-=yen;
            printf("%d円の商品を出しました。",yen);
        }
        else
            printf("残高が足りません。");
}

int main()
{
    yylex();
    return(0);
}

次に、入力用のファイルex15.txtを用意します。このとき、入れた紙幣やコインは、「1000円」、「500円」、「100円」、「50円」、「10円」として入力します。また、商品選択ボタンが押された場合として、それぞれ「商品A」、「商品B」、「商品C」を入力します。最後に、返却レバーが回されることに対応して、「返却」を入力します。

FLEXプログラムtest15.lexをコンパイルし、このex15.txtを読み込むと次のような結果が出力されます。

いろいろと入力を変えてみて、動作を観察するのも面白いと思います。

yywrap関数について

yywrap関数の返す値は、構文解析器であるbisonと字句解析器であるflexとのインターフェースとして使われているようです。両者のインターフェースでは、トークンと呼ばれる数値により、やり取りが行なわれています。このサイトでは、主にflexを単体で使用することがほとんどですからyywrap関数の値を1(通常値)にしておけば、特にこの関数について気にする必要はないと考えております。

ここで、もし日本語カスタマイザーの前半処理部分をflexではなく、bisonで書き直せばどうなるのでしょうか。確かに「#日本語定義」部分だけはflexで書くよりもすっきりと記述できそうです。しかし、bisonで書こうとすると、「#日本語定義」部分以外をどうするかが問題となります。もし、各プログラム言語で規定されているすべての構文規則について記述するとなると、エラーの出ないように記述しないと、パーサーエラーメッセージが続出してしまいます。これではまず実現がとても難しいと思います。また、「#日本語定義」部以外はflexの方で処理しないようにするという方法も考えられますが、flexとbison間の文字列をやり取りするインターフェースを考える必要があります。やはり日本語カスタマイザーについては、flexの字句解析を2回行う方が、より簡単に実現できるのではないでしょうか。

 

参考図書
オートマトン・言語理論 本多波雄著 コロナ社 1972
UNIXプログラミング環境 Brian W.Kernighan Rob Pike 石田晴久監訳 アスキー出版局 1985
プログラミング言語C B.W. カーニハン D.M. リッチー 石田晴久訳 共立出版社 1981
UNIX 石田晴久著 共立出版社 1983
情報通信プロトコル 秋丸春夫 奥山徹共著 オーム社 2001
yacc/lex 五川女健治著 啓学出版社 1992
flex, version2.5 A Fast Scannar Generator ドキュメント University of California. Vern Paxsonほか多数のAuthor編著
Cプリプロセッサー・パワー 林晴比古著 日本ソフトバンク出版社 1988
HTMLハンドブック 渡辺竜生著 ソフトバンクパブリッシング 1999
ubuntu6.10 システム utf-8 manual page
RFC3875 The Common Gateway Interface CGI Version 1.1