FLEX日本語活用編

(Windows10版)

FLEX (first lexical analyzer generator)は、字句解析器と呼ばれるソフトウェアです。 このFLEXは高級言語といわれるコンピュータープログラムを開発するために作られたソフトウェアの一つとして知られておりますが、一言でこのFLEXの機能を説明するのは難しいと思います。(コンピューターのプログラム開発では構文解析器のBISONと組み合わせて使われることが多いようです)

このFLEXは別名(生成する)スキャナーと呼ばれていますが、ファイルを走査して細かく調べながら字句を解析するという働きがあるようです。コンピューターのプログラム言語だけではなく、FLEX単体でもさまざまな利用法があると思います。ここでは、日本語文書処理という観点からFLEXプログラムの活用法について述べてみます。

FLEXのプログラムは一度C言語に変換されます。それをC言語処理系によって翻訳することにより、プログラムを実行することができます。また、FLEXのブログラムはフリーソフトとして公開されております。 ここではFLEX (version 2.5.4)をインストールして、Windows 10上のコマンドプロンプトで動作することを前提にします。最初はWindows-PowerShellに移行しようかと思ったのですが、標準入力のリダイレクション「<」が使えないので、ここは以前と同じようにコマンドプロンプトを使うことにします。 さらに、Windwos10のバージョンによっては、システムロケールをUTF-8にするオプションが追加されているものもあるようです。このUnicodeUTF-8使用というチェックボックスをチェックして再起動すると、コマンドプロンプトのコードページが既定でUTF-8になったり、 メモ帳のテキストエンコーディングがUTF-8になる(ANSIを選んでもUTF-8で保存されるためシフトJISの保存ができない)という情報もありますので、このサイトでの例題を実行する場合には注意が必要です。 (注) 前述のように、FLEXを動かすにはC言語の処理系が必要となりますので、これにはBorland社 (2008年よりエンバカデロ・テクノロジーズ社に買収)のC/C++ Compiler 5.5が既にインストールされているものとします。また、テキストファイルの文字コードはANSI(SHIFT-JIS)で作成しています。UTF-8コードではFlex2.5.4はうまく動かないようです。

Windows XP版はこちら

Windows 7版はこちら

FLEXの起動確認
例題F─1 入力ファイルをそのまま出力する
例題F─2 文字列を変換する(その一)
例題F─3 文字列を変換する(その二)
例題F─4 簡単なC言語風日本語プログラムの実行
例題F─5 FLEX規則を利用してC言語を日本語で記述する
例題F─6 文字クラスを使った日本語文字処理の基本練習
例題F─7 FLEXを利用した送り仮名のチェック
例題F─8 FLEXを利用した簡単な同音異義語のチェック
例題F─9 FLEX(Windows版)の規則記述で注意するべき漢字
例題F─10 FLEXを利用した簡単な表記チェッカー
例題F─11 FLEXで特定の文字をコードに変換するプログラム
例題F─12 自動番号の付いた規則をつくる
例題F─13 FLEX規則を利用してC言語を日本語で記述する(続編)
例題F─14 シンタックスダイヤグラムと状態遷移図
捕足 パスの設定について

FLEXの起動確認

まずWindows 10のコマンドプロンプトを立ち上げ、FLEXが利用できる作業用フォルダ/ディレクトリに移動します。ここではC:\My tool\flexという場所を使います。(コマンドプロンプトの背景色は変更が可能です。ここでは青色系を使用しております)

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

>flex --version

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

もし、うまく動作しない場合には、パス(プログラムを参照するための場所の設定)が通っているかどうかを確かめてください。(パスの設定については補足 パスの設定を参照のこと)

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

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

Windowsのメモ帳などで、次のようにプログラムを入力します。

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

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

%}

%%
%%

void main()
{
    yylex();
}

このファイルをtest1.lexという名前で作業用フォルダ/ディレクトリ内に保存します。

コマンドプロンプトから次のように入力するか、バッチファイルによって自動的にtest1.exeを生成できるようにします。

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

>flex test1.lex
>bcc32 lex.yy.c
>copy lex.yy.exe test1.exe

バッチファイルの内容 (lcc.bat)

flex %1.lex
bcc32 lex.yy.c
copy lex.yy.exe %1.exe

バッチファイルから実行する場合

>lcc test1

ここではバッチファイルで実行させてみました。(もし、C言語の処理系(コンパイラ)が動作しない場合にも、やはりパスの設定をもう一度確認してみてください。パスの設定については補足 パスの設定についてを参照のこと)

次にWindowsのメモ帳などで、次のようなテキストを入力してex1.txtという名前で保存します。

あいうえお
かきくけこ
さしすせそ
たちつてと
なにぬねの
はひふへほ
まみむめも

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

>test1 < ex1.txt

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

これだけでは一体何をするプログラムかということは、まだよく分からないかもしれませんが、これで一応FLEXプログラム環境で、うまく動作するかどうかの確認ができたと思います。

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

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

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

%{

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

%}

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

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

void main()
{
    yylex();
}

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

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

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

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

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

%}

%%

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

%%

void main()
{
    yylex();
}

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

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

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

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


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

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

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

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

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

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

%}

%%

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

%%

void main()
{
    yylex();
}

次にWindowsのメモ帳などで、次のようなテキストを入力してex3.txtという名前で保存します。

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

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

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

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

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

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

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

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

#include "flex_test.inc"

%}


%%

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

%%

void main()
{
    yylex();
}

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

また、この結果をexout.cというファイルにして、C言語の処理系に通せば、立派なプログラムとして動作することも分かります。

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

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

バッチファイルの内容 (lcc.bat)

flex -o%1.c %1.lex
bcc32 %1.c

次に日本語カスタマイザー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",山田さんの記録.生まれた日);

}

これに対するFLEXプログラムですが、実はこのバージョンのFLEXは、日本語のような2バイトコード文字の中でも、2バイト目が16進数で「5C」のコード(文字ではバックスラッシュ、¥マーク)は 、文字としてうまく認識してくれないようです。FLEXの規則部分はパターンマッチ部と、それに対するアクション部を記述するようになっておりますが(アクション部は書かなくても可)、この パターンマッチ部で、前述したような16進数の「5C」というのは、持別な役割を持たせているためではないかと思われますが、「構造体」の「構」という文字もこの2バイト目が16進数の「5C」にあたります。

パターンマッチ部で2バイト目が16進数の「5C」の文字を適用させるためには、文字ではなくコードを直接記述しなければいけません。これを考慮したプログラムをtest5.lexとします。このとき、日本語の変数は日本語カスタマイザーの考え方と同じで、jp_str_に続く数字を順番に割り当てています。

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

#include "flex_test.inc"

%}

%%

"型なし"         { printf("void"); }
"整数型"         { printf("int"); }
"文字型"         { printf("char"); }
"はじまり"       { printf("main"); }
"印字"           { printf("printf"); }
(\x8d\x5c)"造体" { 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"); }

%%

void main()
{
    yylex();
}

この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;
};

void 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);

}

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

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

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

ここで、少し文字コードについて触れておきたいと思います。Windwos 7のコマンドプロンプトで、メモ帳を使用してファイルを保存する場合をここでは想定します。このとき文字コードは通常ANSIとして保存されます。これは日本語の2バイトコードは シフトJISと呼ばれるコード体系になります。シフトJISコードでは日本語は2バイトコードで表され、このうち第1バイトが16進数で81~9f、e0~efに割り当てられています。

一方、英語圏で通常使用されるアスキー(ascii)コードは、1バイトコードであり、16進数で00~7fまでとなっているので、シフトJISの1バイト目と アスキーコードを区別することができます。

また、日本独自の規格である1バイトカタカナ(半角カタカナ)も、16進数でa1~dfとなっいるので、これらもシフトJISコードの1バイト目と区別ができます。また シフトJISコードの第2バイトは、16進数で40~7eと80~fcです。つまり、7fを除く40~fcまでに割り当てられています。

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

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

#include "flex_test.inc"

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

%}


%%

{katakana_1}     { }

[[:alpha:]]      { }

[[:digit:]]      { }

{katakana_2}     { }

{hiragana}       { }

{sjis_code}      { }

.                { }

\n               { pr_text; }

%%

void main()
{
        yylex();
}

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

katakana_1 1バイトのカタカナ文字
katakana_2 2バイトのカタカナ文字
hiragana ひらがな文字(2バイトコード)
sjis_code シフトJISコード、上位バイトに続く任意の下位1バイト文字として簡易的に定義
[[:alpha:]] V2.5で使用できる1バイトのアルファベット(大文字と小文字)の文字クラス式
[[:digit:]] V2.5で使用できる1バイトの数字の文字クラス式
. 文字クラスではないが、上記と改行以外の1バイト文字
\n 文字クラスではないが、改行文字

2バイトのシフトJISコードについては、コードのすべてを列挙すると、それだけで膨大な量になりますので、簡易的な定義をしております。 また、このようにすることで2バイトコードのすべてについてスキャンすることができます。

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

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

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

%%

{katakana_1}     { pr_text; }

[[:alpha:]]      { }

[[:digit:]]      { }

{katakana_2}     { }

{hiragana}       { }

{sjis_code}      { }

.                { }

\n               { pr_text; }

%%

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

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

%%

{katakana_1}     { }

[[:alpha:]]      { pr_text; }

[[:digit:]]      { }

{katakana_2}     { }

{hiragana}       { }

{sjis_code}      { }

.                { }

\n               { pr_text; }

%%

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

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

%%

{katakana_1}

[[:alpha:]]

[[:digit:]]      { pr_text; }

{katakana_2}

{hiragana}

{sjis_code}

.

\n               { pr_text; }

%%

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

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

%%

{katakana_1}

[[:alpha:]]

[[:digit:]]

{katakana_2}    { pr_text; }

{hiragana}

{sjis_code}

.

\n               { pr_text; }

%%

これをコンパイル、実行してex6.txtを読み込ませます。すると2バイトのカタカナ文字だけが表示されます。 2バイトのカタカナを文字クラスで定義する場合には、「ソ」など2バイト目が16進数で5cの文字は、(\x83\x5c)というような文字コードで定義する必要があります。また 、「ゼ」、「ゾ」や「ボ」、「ポ」、「マ」なども文字コードで定義しないと受け付けてくれないようです。

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

%%

{katakana_1}

[[:alpha:]]

[[:digit:]]

{katakana_2}

{hiragana}     { pr_text; }

{sjis_code}

.

\n               { pr_text; }

%%

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

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

%%

{katakana_1}

[[:alpha:]]

[[:digit:]]

{katakana_2}

{hiragana}

{sjis_code}    { pr_text; }

.

\n             { pr_text; }

%%

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

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

%%

{katakana_1}

[[:alpha:]]

[[:digit:]]

{katakana_2}

{hiragana}

{sjis_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; }

{sjis_code}       { pr_text; }

.                 { pr_text; }

\n                { pr_text; }

%%

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

例題F─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と同じ内容です。

void main()
{
        yylex();
}

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

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

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

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

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

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

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

#include    "flex_test.inc"

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

%}

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




void main()
{
        yylex();
}

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

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

例題F─9 FLEX(Windows版)の規則記述で注意するべき漢字

文字クラスの定義のところでも述べましたが、FLEXでは2バイト文字のうち、2バイト目が(16進数で)「5C」の文字には注意をする必要があります。具体的には「ソ」、「構」、「十」、「申」、「能」、「表」、「予」、「噂」、「欺」、「圭」、「蚕」、「曾」、「貼」、「暴」、「禄」などです。

パターンマッチ部でこれらの文字を使用する場合には、文字ではなく文字コードで定義しないといけません。ここで、次のような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; }

"無"(\x96\x5c)        { printf("無謀"); }
"無謀"                { printf("無%c%c",0x96,0x5c); }

%%

void main()
{
        yylex();
}

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

また、アクション部では、2バイト目が「5C」の文字であっても、printfにおける二重引用符内では漢字として記述することができます。しかし、最後の二重引用符の直前に、2バイト目が「5C」である漢字の場合に限り、Windows版シフトJISのFLEXでは、受け付けてくれないようです。従って、ここでは2バイト文字コードで出力するようにしています。

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

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

例題F─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("[注意]上手→うま"); }

%%

void main()
{
        yylex();
}

「上手い」は「い」だけでなく、「上手かろう」「上手く」「上手ければ」などの活用が考えられますので、「上手」の後に続く数種類の平仮名を認識できることが必要となります。ここでは、文字クラスの中で数種類の平仮名を一括して定義しています。パターンマッチ部で用いる"/"(スラッシュ記号)は、先読み演算子として機能します。従って、「上手」に続く「い」「か」「く」「け」などがある限りパターンマッチしますが、そのときの「い」「か」「く」「け」は、まだマッチの対象とはなりません。

これをコンパイル、実行してex10.txtを読み込ませます。すると 「上手い」や「上手く」について表記の注意はそこに記入されますが、「上手」(じょうず)の場合は何もしないことが分かります。

例題F─11 FLEXで特定の文字をコードに変換するプログラム

これまでにwindows10環境で、FLEXの日本語処理をいくつか紹介してきましたが、プログラムを書く上で、少々わずらわしい点がありました。 第一に、FLEXの規則部分には、日本語文字の2バイト目に16進数で「5c」の文字をパターンマッチさせるときの問題があり、文字ではなくコードで定義しないとエラーとなります。 また、FLEXの文字クラスの定義でも、文字ではなく、文字コードで定義しないとエラーになる例外がありました。

これを何とかubuntuのutf-8ファイルでFLEXを扱うように、簡単にできないものかと、これまでにいろいろと試行錯誤をしてきました。 まず、現行バージョンのFLEXで、utf-8のファイルを処理できないかとやってみましたが、windows10上のFLEXバージョン2.5.4では駄目でした。 次に、比較的新しいwin_flexバージョン2.5.37というソフトを試してみましたが、これもutf-8ファイルをうまく処理することはできないようでした。 おまけに、この新しいFLEXのバージョンは、ANSI/Shift-JISファイルでも、C言語の処理系であるbcc32(バージョン5.5.1)との整合の面で、すっきり処理できないことが分かりました。(FLEX側のオプションをいろいろといじってはみましたが、警告が消えません) このため、FLEXの新しいバージョンを使うことはあきらめて、現状のバージョンを使って、何とか文字のコードで書かなくてもよい方法を探すことにしました。

いろいろと考えた末に、ひらめいたのは、FLEXプログラムで文字クラスや規則部分に、問題のある文字をスキャンすると、自動的にそのコードを生成する前処理フィルターをFLEXでつくるというものでした。 このフィルターをバッチファイルの中で、前処理(プリプロセッサー)として使うようにしておけば、ユーザーはあたかも今までと同じような使い勝手で、FLEXのプログラムを作成できるのではないかと考えました。 これを実現したのが、次の「test11.lex」というプログラムです。これは、文字クラスと規則部分で、特定の文字をコードに変換します。

/* 文字クラスと規則部分で特定の文字をコードに変換するプログラム */
%{

#include "flex_test.inc"

#define PMAT_NODQ 0
#define PMAT_INDQ 1
#define ACTION 2

#define pr_text printf("%s",yytext)
#define pr_cmnt( str, H, L) pr_dq;\
                            if( wt_dq_sts != ACTION ) printf("%s",str);\
                            else printf("%c%c", H, L);\
                            pr_dq

#define pr_dq if ( wt_dq_sts == PMAT_INDQ ) printf("\"",yytext)

#define chang_sts switch ( wt_dq_sts ) {\
                    case PMAT_NODQ :\
                        wt_dq_sts = PMAT_INDQ;\
                        break;\
                    case PMAT_INDQ :\
                        wt_dq_sts = PMAT_NODQ;\
                        break;\
                    case ACTION :\
                        break;\
                    }

int wt_dq_sts = PMAT_NODQ;

%}

dlmt [ \t]

%x IN_CH_CLS
%x IN_RULE

%%

"%}" { printf("%%}"); BEGIN(IN_CH_CLS);}

<IN_CH_CLS>(\x83\x5c) { printf("(\\x83\\x5c)"); }
<IN_CH_CLS>(\x83\x5b) { printf("(\\x83\\x5b)"); }
<IN_CH_CLS>(\x83\x5d) { printf("(\\x83\\x5d)"); }
<IN_CH_CLS>(\x83\x7b) { printf("(\\x83\\x7b)"); }
<IN_CH_CLS>(\x83\x7c) { printf("(\\x83\\x7c)"); }
<IN_CH_CLS>(\x83\x7d) { printf("(\\x83\\x7d)"); }
<IN_CH_CLS>(\x81\x5b) { printf("(\\x81\\x5b)"); }
<IN_CH_CLS>.          { pr_text; }
<IN_CH_CLS>"%%"       { printf("%%%%"); BEGIN(IN_RULE);}

<IN_RULE>\"           { pr_text; chang_sts; }
<IN_RULE>{dlmt}+      { pr_text; if (wt_dq_sts != ACTION ) wt_dq_sts = ACTION; }
<IN_RULE>\n           { pr_text; wt_dq_sts = PMAT_NODQ; }

<IN_RULE>(\x83\x5c) { pr_cmnt("(\\x83\\x5c)",0x83,0x5c); }
<IN_RULE>(\x87\x5c) { pr_cmnt("(\\x87\\x5c)",0x87,0x5c); }
<IN_RULE>(\x89\x5c) { pr_cmnt("(\\x89\\x5c)",0x89,0x5c); }
<IN_RULE>(\x8b\x5c) { pr_cmnt("(\\x8b\\x5c)",0x8b,0x5c); }
<IN_RULE>(\x8c\x5c) { pr_cmnt("(\\x8c\\x5c)",0x8c,0x5c); }
<IN_RULE>(\x8d\x5c) { pr_cmnt("(\\x8d\\x5c)",0x8d,0x5c); }
<IN_RULE>(\x8e\x5c) { pr_cmnt("(\\x8e\\x5c)",0x8e,0x5c); }
<IN_RULE>(\x8f\x5c) { pr_cmnt("(\\x8f\\x5c)",0x8f,0x5c); }
<IN_RULE>(\x90\x5c) { pr_cmnt("(\\x90\\x5c)",0x90,0x5c); }
<IN_RULE>(\x91\x5c) { pr_cmnt("(\\x91\\x5c)",0x91,0x5c); }
<IN_RULE>(\x92\x5c) { pr_cmnt("(\\x92\\x5c)",0x92,0x5c); }
<IN_RULE>(\x93\x5c) { pr_cmnt("(\\x93\\x5c)",0x93,0x5c); }
<IN_RULE>(\x94\x5c) { pr_cmnt("(\\x94\\x5c)",0x94,0x5c); }
<IN_RULE>(\x95\x5c) { pr_cmnt("(\\x95\\x5c)",0x95,0x5c); }
<IN_RULE>(\x96\x5c) { pr_cmnt("(\\x96\\x5c)",0x96,0x5c); }
<IN_RULE>(\x97\x5c) { pr_cmnt("(\\x97\\x5c)",0x97,0x5c); }
<IN_RULE>(\x98\x5c) { pr_cmnt("(\\x98\\x5c)",0x98,0x5c); }
<IN_RULE>(\x99\x5c) { pr_cmnt("(\\x99\\x5c)",0x99,0x5c); }
<IN_RULE>(\x9a\x5c) { pr_cmnt("(\\x9a\\x5c)",0x9a,0x5c); }
<IN_RULE>(\x9b\x5c) { pr_cmnt("(\\x9b\\x5c)",0x9b,0x5c); }
<IN_RULE>(\x9c\x5c) { pr_cmnt("(\\x9c\\x5c)",0x9c,0x5c); }
<IN_RULE>(\x9d\x5c) { pr_cmnt("(\\x9d\\x5c)",0x9d,0x5c); }
<IN_RULE>(\x9e\x5c) { pr_cmnt("(\\x9e\\x5c)",0x9e,0x5c); }
<IN_RULE>(\x9f\x5c) { pr_cmnt("(\\x9f\\x5c)",0x9f,0x5c); }
<IN_RULE>(\xe0\x5c) { pr_cmnt("(\\xe0\\x5c)",0xe0,0x5c); }
<IN_RULE>(\xe1\x5c) { pr_cmnt("(\\xe1\\x5c)",0xe1,0x5c); }
<IN_RULE>(\xe2\x5c) { pr_cmnt("(\\xe2\\x5c)",0xe2,0x5c); }
<IN_RULE>(\xe3\x5c) { pr_cmnt("(\\xe3\\x5c)",0xe3,0x5c); }
<IN_RULE>(\xe4\x5c) { pr_cmnt("(\\xe4\\x5c)",0xe4,0x5c); }
<IN_RULE>(\xe5\x5c) { pr_cmnt("(\\xe5\\x5c)",0xe5,0x5c); }
<IN_RULE>(\xe6\x5c) { pr_cmnt("(\\xe6\\x5c)",0xe6,0x5c); }
<IN_RULE>(\xe7\x5c) { pr_cmnt("(\\xe7\\x5c)",0xe7,0x5c); }
<IN_RULE>(\xe8\x5c) { pr_cmnt("(\\xe7\\x5c)",0xe8,0x5c); }
<IN_RULE>(\xe9\x5c) { pr_cmnt("(\\xe9\\x5c)",0xe9,0x5c); }
<IN_RULE>(\xea\x5c) { pr_cmnt("(\\xea\\x5c)",0xea,0x5c); }
<IN_RULE>(\xed\x5c) { pr_cmnt("(\\xed\\x5c)",0xed,0x5c); }
<IN_RULE>(\xee\x5c) { pr_cmnt("(\\xee\\x5c)",0xee,0x5c); }
<IN_RULE>.          { pr_text; }
<IN_RULE>"%%"       { printf("%%%%"); BEGIN(INITIAL);}

.                   { pr_text; }
\n                  { pr_text; }

%%

void main()
{
    yylex();
}

このプログラムは、少々複雑で分かりにくいとは思いますが、基本的には文字クラスを処理する場合には、「IN_CH_CLS」という状態で「ソ」「ゼ」「ゾ」などの変換処理をします。 また、規則部分では、「IN_RULE」という状態で、「構」、「十」、「申」、「能」などの文字を変換します。 この自動変換は、あくまでもパターンマッチ部に対してだけ行われて、アクション部で記述されたところは、特に何もしないという仕様にしています。 このため、規則の一行中で、スキャンしているところを次の3つのステータスで表しています。
「PMAT_NODQ」(パターンマッチ部の二重引用符なし)
「PMAT_INDQ」(パターンマッチ部の二重引用符の中)
「ACTION」(アクション部)
規則の一行部分では、、「PMAT_NODQ」または「PMAT_INDQ」のときに、問題の文字をスキャンすると、コードとして出力します。 この前処理フィルターを使えば、このページ内で紹介している例題ぐらいは何とか処理できます。

そして、このFLEXプログラムtest11.lexをコンパイルしたexeファイルを「c2_fltr.exe」という名前に変更して、次のような「lcc1.bat」というバッチコマンドの中で、実行するようにしました。

c2_fltr < %1.lex | flex -o%1.c
bcc32 %1.c

このとき、前処理フィルター「c2_fltr」の出力は、パイプ「|」により、FLEXの入力へと連結されていますので、

lcc1 FLEXプログラム名

と入力するだけで、問題の文字を含むプログラムを読み込んで、必要があれば自動的にコードへ変換した上、実行可能形式のプログラムまで生成してくれます。

ここで、少しこの前処理フィルターの動作確認をしてみたいと思います。 例題FU-5の規則部分で「構」という文字を文字コードとして定義しましたが、これを文字コードではなく、普通に文字として定義したものをtest5-1.lexとしました。

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

#include "flex_test.inc"

%}

%%

"型なし"         { printf("void"); }
"整数型"         { printf("int"); }
"文字型"         { printf("char"); }
"はじまり"       { printf("main"); }
"印字"           { 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"); }

%%

void main()
{
    yylex();
}
 

このtest5-1.lexをバッチコマンドlcc1でコンパイルし、ex5.txtを読み込むと、C言語ファイルが出力されます。これをbcc32で再度コンパイルして実行すると、例題FU-5と全く同じ結果が得られたことを確認しました。 これで一応、ubuntuのutf-8コード体系で、FLEXプログラムをつくるときと、同じような環境になったのではないかと考えています。

例題F─12 自動番号の付いた規則をつくる

これまのFLEXを利用した基本的なテクニックを使用して、日本語カスタマイザーのヒントともいうべき練習をやってみます。この例題は、ubuntu版のFLEX編でも取り上げた内容です。

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

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

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

#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")


%}}

katakana_1    ア|イ|ウ|エ|オ|ァ|ィ|ゥ|ェ|ォ|カ|キ|ク|ケ|コ|サ|シ|ス|セ|ソ|タ|チ|ツ|テ|ト|ナ|ニ|ヌ|ネ|ノ|ハ|ヒ|フ|ヘ|ホ|マ|ミ|ム|メ|モ|ラ|ヤ|ユ|ヨ|ャ|ュ|ョ|リ|ル|レ|ロ|ワ|ヲ|ン|。|「|」|・|、|゙|゚
katakana_2    (ア)|(イ)|(ウ)|(エ)|(オ)|(ァ)|(ィ)|(ゥ)|(ェ)|(ォ)|(カ)|(キ)|(ク)|(ケ)|(コ)|(ガ)|(ギ)|(グ)|(ゲ)|(ゴ)|(サ)|(シ)|(ス)|(セ)|(ソ)|(ザ)|(ジ)|(ズ)|(ゼ)|(ゾ)|(タ)|(チ)|(ツ)|(ッ)|(テ)|(ト)|(ダ)|(ヂ)|(ヅ)|(デ)|(ド)|(ナ)|(ニ)|(ヌ)|(ネ)|(ノ)|(ハ)|(ヒ)|(フ)|(ヘ)|(ホ)|(バ)|(ビ)|(ブ)|(ベ)|(ボ)|(パ)|(ピ)|(プ)|(ペ)|(ポ)|(マ)|(ミ)|(ム)|(メ)|(モ)|(ヤ)|(ユ)|(ヨ)|(ャ)|(ュ)|(ョ)|(ラ)|(リ)|(ル)|(レ)|(ロ)|(ヮ)|(ワ)|(ヰ)|(ヱ)|(ヲ)|(ン)|(ヴ)|(ヵ)|(ヶ)|(ー)|(ヱ)|(ヰ)

hiragana    あ|い|う|え|お|ぁ|ぃ|ぅ|ぇ|ぉ|か|き|く|け|こ|さ|し|す|せ|そ|た|ち|つ|て|と|な|に|ぬ|ね|の|は|ひ|ふ|へ|ほ|ま|み|む|め|も|や|ゆ|よ|ゃ|ゅ|ょ|ら|り|る|れ|ろ|わ|ゐ|ゑ|を|ん|が|ぎ|ぐ|げ|ご|ざ|じ|ず|ぜ|ぞ|だ|じ|づ|で|ど|ば|び|ぶ|べ|ぼ|ぱ|ぴ|ぷ|ぺ|ぽ

sjis_code    (\x81).|(\x82).|(\x83).|(\x84).|(\x85).|(\x86).|(\x87).|(\x88).|(\x89).|(\x8a).|(\x8b).|(\x8c).|(\x8d).|(\x8e).|(\x8f).|(\x90).|(\x91).|(\x92).|(\x93).|(\x94).|(\x95).|(\x96).|(\x97).|(\x98).|(\x99).|(\x9a).|(\x9b).|(\x9c).|(\x9d).|(\x9e).|(\x9f).|(\xe0).|(\xe1).|(\xe2).|(\xe3).|(\xe4).|(\xe5).|(\xe6).|(\xe7).|(\xe8).|(\xe9).|(\xea).|(\xeb).|(\xec).|(\xed).|(\xee).|(\xef).

dlmt     [ \t]

%x  JP_DEF
%x  JP_STRING

%%

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

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

<JP_STRING>{sjis_code}  { pr_dq_text; }

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

%%

void main()
{
        pr_rule;
        yylex();
        pr_rule;

}

上記のFLEXプログラムをコンパイルするときには、例題FU-11でのところでやりました前処理フィルターを使います。 このFLEXプログラムでは、あらかじめ「JP_DEF」状態と「JP_STRING」状態の二つの状態を持っています。まず、テキスト各行の先頭で「#日本語定義」という文字列を見つけると、「JP_DEF状態」に遷移します。

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

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

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

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

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

今回は、例題FU-5の続編として、もう少し日本語で書いたC言語風プログラムを処理するための練習をやってみたいと思います。この例題も、ubuntu版のFLEX編で取り上げた内容と同じものです。

このFLEXプログラムは、日本語カスタマイザーの2パス目の処理を行うためのヒントになると考えています。

まず、次のようなテキストファイルex13.txtを準備します。ex13.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 テストプログラム13 */
%{

#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"); }

%%

void main()
{
    yylex();

}

このtest13.lexをコンパイルし、ex13.txtを読み込むと次のような結果が出力されます。上記のFLEXプログラムをコンパイルするときには、やはり例題FU-11でのところでやりました前処理フィルターを使います。

/* 個人情報を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パス目では、変換規則と共に、これらの処理ができるようにしておくことが必要となります。

例題F-14 シンタックスダイヤグラムと状態遷移図

今まで日本語文書処理や、日本語カスタマイザーのヒントともいうべき例題を取り上げてきましたが、今回は少々アカデミックな話題を取り混ぜてやってみたいと思います。

FLEXが有限オートマトンの理論によって作られた、優秀なソフトウェアツールであることは、今さら言うまでもありませんが、このオートマントの理論では、状態遷移図というものがよく使われます。 ここで、「漢数字」という文字列を認識する機械を考えたときに、この状態遷移図は下図のようになります。

このとき、S1は「漢」という文字が入力されたときに移る状態で、S2は状態S1のときに、さらに「数」という文字が入力されたときに移る状態のことです。状態がS2のときに「字」という文字が入力されると、「漢数字」という文字列が認識されたS3という最終状態に達します。

今回の例題では、「漢数字」という文字列だけではなく、「漢字」という文字列も一緒に認識させることを考えます。 この場合、状態遷移で考えると、S1状態のときに「数」という文字だけではなく、「字」という文字が入力されたときにも、状態遷移が起こるようにしてやることが必要です。

話はちょっとそれますが、今でもこの状態遷移にε(イプシロン)動作というものを導入している人達もいらっしゃるようです。このε動作というものの定義は、入力がなくても状態遷移が起こるという特殊なオートマトンのようです。 つまり、上の状態遷移図で考えると、状態S1のときに、何も入力がなくても状態S2に遷移するという考え方でのようです。

一方、私が学生時代の頃には、このε動作については教わりませんでしたが、スイスのN.Wirth博士の考案したPascal言語を習ったときに、その処理系にはシンタックスダイヤグラム(構文図)というものがよく使われていました。 このシンタックスダイヤグラムで、「漢数字」という文字列を認識した場合を考えると、下図のようになります。

すなわち、「数」という文字は、あってもなくてもよいことを示しています。FLEXでは、このような場合には「?」という記号を規則のパターンマッチ部で使います。

それでは例題の前に、以下のようなテキストex14.txtを用意します。

次に、このテキストを読み込んで、「漢字」、「漢数字」、「計算」、「計算機」、「電算」、「電算機」という文字列だけを認識するFLEXプログラムtest14.lexを示します。

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

#include "flex_test.inc"

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

%}

%%

"漢""数"?"字"            { pr_text; }
("計"|"電")"算""機"?     { pr_text; }

.                        { }

%%

void main()
{
    yylex();

}

「"数"?」は、文字「数」が、あってもなくてもよいことを表わし、「"機"?」は、文字「機」が、あってもなくてもよいことを表わしています。 また、「("計"|"電")」は、以前の例題にもあったように、「計」または「電」のどちらか一方を表わしています。 このtest14.lexをコンパイルし、ex14.txtを読み込むと、次のように「数字」以外は、すべて認識されて文字が出力されます。上記のFLEXプログラムをコンパイルするときには、やはり例題FU-11でのところでやりました前処理フィルターを使うことをおすすめします。

今回は、ε動作という特殊なものについて少し触れましたが、通常のオートマトンの考え方では、FLEXなどの文字を読み込んだときや、FA関連ですとセンサーの入力や、タイマーのタイムアップなどのイベントが起こった場合など、広い意味での入力があったときに、状態遷移が起こるのが一般的な考え方です。 しかし、この入力がないにもかかわらず、状態遷移が起こるε動作というものは、私個人的な持論ではありますが、シンタックスダイヤグラムで考えると、特にこだわる必要はないのではなかろうかと考えています。

補足 パスの設定について

Windows 10では、パスの設定方法にはいくつかの方法があります。一つの方法は、まずスタートボタンをクリックします。

表示されるメニューの中で「W」の項目に「Windowsシステムツール─コントロールパネル」がありますので、ここをクリックします。表示されたメニューの中から「システムとセキュリティ」を選択するとウィンドウが現れます。この中の「RAMの量とプロセッサの速度の表示」をクリックすると次のようなウィンドウが現れます。(この辺のメニューについてはバージョンによって多少異なるようです)

左側の項目にある「システムの詳細設定」をクリックします。するとシステムのプロパティウィンドウが出てきます。

ここで「詳細設定」タブを選択して、そのウィンドウ下部の「環境変数」というボタンをクリックします。

この変数値のところにパスを設定したいフォルダ/ディレクトリの場所を指定します。ここでは「flex.exe」がある場所(C:\Mytool\flex\GnuWin32\bin)をシステム環境変数として入力しておりますが、これはこのサイトで取り扱うさまざまなプログラム言語の利用環境で、すべてFLEXが動作するようにということで、システム変数のパスに設定しております。また、C言語の処理系 であるC/C++ Compiler 5.5のパスも、あらかじめこのシステム変数のパスに設定されております。

現在複数ユーザーでお使いのシステムにFLEXやボーランドC/C++ Compiler 5.5をディストリビューション・サイトからダウンロードしてインストールされる場合には、システム管理者にパスの設定方法について相談された方がよいと思います。また、Windows 10にあまり詳しくない方なども、一応システム管理者に相談されることをお勧めしますが、万が一パスの設定を変更したことで、システムの動作がおかしくなった場合でもこちらでは対処できませんので、あくまで自己責任でお願いいたします。

また、コマンドプロンプトでも、パスを設定する方法があります。ここで設定した場合には、コマンドプロンプトを終了すると、コマンドプロンプトで設定したパスだけは消滅します。

コマンドプロンプトを起動させて、「PATH」と入力すると、現在の設定が表示されますが、これはシステム環境変数で設定したパスとユーザー環境変数で設定したパスに追加されて、コマンドプロンプトで設定したパスが表示されます。

もし、システム環境変数やユーザー環境変数ではなく、コマンドプロンプト内でパスを設定するのであれば、次のような「SET PATH」コマンドを入力、またはバッチファイルにして実行すれば、システム変数、ユーザー変数で設定されているパスに追加された格好で、設定をすることもできます。

>SET PATH=%path%;c:\mytool\flex\GnuWin32\bin

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
アルゴリズム+データ構造=プログラム N.Wirth 片山卓也訳 日本コンピュータ協会 1979
yacc/lex 五川女健治著 啓学出版社 1992
flex, version2.5 A Fast Scannar Generator ドキュメント University of California. Vern Paxsonほか多数のAuthor編著
Cプリプロセッサー・パワー 林晴比古著 日本ソフトバンク出版社 1988