ここでは、 Flexの使用例をさらにいくつか紹介します。 ここでの例も、 必ずしも最適な実装ではありませんが、 一般的なFlexの使い方を示してくれるはずです。
以下の定義は、 与えられたファイルの中の単語数、文字数、行数をカウントするのにFlexを使う方法を示す、 簡単な例です。 実際にFlexに関係のある部分は、 非常に少ないことに注意してください。 以下のコードのほとんどは、 コマンドライン・パラメータを処理したり、 カウントの合計を保持したりするものです。
/*
* wc.lex : wcのようなユーティリティを、
* Flexを使って作成する簡単な例
*/
%{
int numchars = 0;
int numwords = 0;
int numlines = 0;
int totchars = 0;
int totwords = 0;
int totlines = 0;
%}
/*
* ルールはここから始まる
*/
%%
[\n] { numchars++; numlines++; }
[^ \t\n]+ { numwords++; numchars += yyleng; }
. { numchars++; }
%%
/*
* 追加的なCコードがここから始まる。
* ここで、すべての引数処理等を行うコードが提供される
*/
void main(int argc, char **argv)
{
int loop;
int lflag = 0; /* 行数をカウントする場合は1 */
int wflag = 0; /* 単語数をカウントする場合は1 */
int cflag = 0; /* 文字数をカウントする場合は1 */
int fflag = 0; /* ファイル名が指定されている場合は1 */
for(loop=1; loop<argc; loop++){
char *tmp = argv[loop];
if(tmp[0] == '-'){
switch(tmp[1]){
case 'l':
lflag = 1;
break;
case 'w':
wflag = 1;
break;
case 'c':
cflag = 1;
break;
default:
fprintf(stderr,"unknown option -%c\n",tmp[1]);
}
} else {
fflag = 1;
numlines = numchars = numwords = 0;
if((yyin = fopen(tmp,"rb")) != 0){
(void) yylex();
fclose(yyin);
totwords += numwords;
totchars += numchars;
totlines += numlines;
printf("file : %25s :",tmp) ;
if(lflag){
fprintf(stdout,"lines %5d ",numlines);
}
if(cflag){
fprintf(stdout,"characters %5d ",numchars);
}
if(wflag){
fprintf(stdout,"words %5d ",numwords);
}
fprintf(stdout,"\n");
}else{
fprintf(stderr,"wc : file not found %s\n",tmp);
}
}
}
if(!fflag){
fprintf(stderr,"usage : wc [-l -w -c] file [file...]\n");
fprintf(stderr,"-l = count lines\n");
fprintf(stderr,"-c = count characters\n");
fprintf(stderr,"-w = count words\n");
exit(1);
}
for(loop=0;loop<79; loop++){
fprintf(stdout,"-");
}
fprintf(stdout,"\n");
fprintf(stdout,"total : %25s ","") ;
if(lflag){
fprintf(stdout,"lines %5d ",totlines);
}
if(cflag){
fprintf(stdout,"characters %5d ",totchars);
}
if(wflag){
fprintf(stdout,"words %5d ",totwords);
}
fprintf(stdout,"\n");
}
ここでは、 Pascalのような言語用の字句スキャナを作る方法を示します。 このスキャナ定義では、 個々のキーワードがルールとしてリストされています。 (一般的には、 すべてのキーワードをテーブルに格納してからテーブル検索を使う手法がよく見られますが、) ここでの方法は、 キーワードと識別子とを区別するための方法としては、 一般的に最も簡単なものです。 また、 識別子用にただ1つのルールがあるという点に注意してください。 多くの場合、 このルールはシンボル・テーブルを管理するためのサブルーチンを呼び出します。
もう1つ注意すべき点は、 `_FILE'と`_BEGIN'が先頭にアンダースコアを持つという点です。 Flex、 またはCで定義済みの名前は、 追加的な工夫なしでは使えないということを示すために、 このようにしてあります。 これよりももっと一般的に使われる手法は、 すべてのトークンの先頭もしくは末尾に何らかの文字列を付加するというもので、 こうすることによって問題は発生しなくなります。 `TOK'や`SYM'が一般的によく使われる拡張子です。
/*
* pascal.lex : PASCALスキャナの例
*/
%{
#include <stdio.h>
#include "y.tab.h"
int line_number = 0;
void yyerror(char *message);
%}
%x COMMENT1 COMMENT2
white_space [ \t]*
digit [0-9]
alpha [A-Za-z_]
alpha_num ({alpha}|{digit})
hex_digit [0-9A-F]
identifier {alpha}{alpha_num}*
unsigned_integer {digit}+
hex_integer ${hex_digit}{hex_digit}*
exponent e[+-]?{digit}+
i {unsigned_integer}
real ({i}\.{i}?|{i}?\.{i}){exponent}?
string \'([^'\n]|\'\')+\'
bad_string \'([^'\n]|\'\')+
%%
"{" BEGIN(COMMENT1);
<COMMENT1>[^}\n]+
<COMMENT1>\n ++line_number;
<COMMENT1><<EOF>> yyerror("EOF in comment");
<COMMENT1>"}" BEGIN(INITIAL);
"(*" BEGIN(COMMENT2);
<COMMENT2>[^)*\n]+
<COMMENT2>\n ++line_number;
<COMMENT2><<EOF>> yyerror("EOF in comment");
<COMMENT2>"*)" BEGIN(INITIAL);
<COMMENT2>[*)]
/* FILEとBEGINは、FlexやCにおいては既に定義されているため
* 使うことができない点に注意。これは、すべてのトークンの
* 先頭にTOK_やその他の接頭辞を付加することによって、より
* すっきりと克服することができる
*/
and return(AND);
array return(ARRAY);
begin return(_BEGIN);
case return(CASE);
const return(CONST);
div return(DIV);
do return(DO);
downto return(DOWNTO);
else return(ELSE);
end return(END);
file return(_FILE);
for return(FOR);
function return(FUNCTION);
goto return(GOTO);
if return(IF);
in return(IN);
label return(LABEL);
mod return(MOD);
nil return(NIL);
not return(NOT);
of return(OF);
packed return(PACKED);
procedure return(PROCEDURE);
program return(PROGRAM);
record return(RECORD);
repeat return(REPEAT);
set return(SET);
then return(THEN);
to return(TO);
type return(TYPE);
until return(UNTIL);
var return(VAR);
while return(WHILE);
with return(WITH);
"<="|"=<" return(LEQ);
"=>"|">=" return(GEQ);
"<>" return(NEQ);
"=" return(EQ);
".." return(DOUBLEDOT);
{unsigned_integer} return(UNSIGNED_INTEGER);
{real} return(REAL);
{hex_integer} return(HEX_INTEGER);
{string} return{STRING};
{bad_string} yyerror("Unterminated string");
{identifier} return(IDENTIFIER);
[*/+\-,^.;:()\[\]] return(yytext[0]);
{white_space} /* 何もしない */
\n line_number += 1;
. yyerror("Illegal input");
%%
void yyerror(char *message)
{
fprintf(stderr,"Error: \"%s\" in line %d. Token = %s\n",
message,line_number,yytext);
exit(1);
}
ここでは、 スタート状態を使って、 Flexにより生成されるスキャナの内部に小規模のパーサを作る方法の例を示します。 このコードはThe New Hackers Dictionary (`prep.ai.mit.edu'、 およびその他の多くのインターネットFTPサイトから入手可能なテキスト形式のもの) を入力として受け取り、 すぐに製版および印刷できる状態のTexinfoフォーマットのドキュメントに変換するものです。 このコードは`jargon2910.ascii'を使ってテスト済みです。
典型的な使い方は以下のとおりです。
j2t < jargon > jargon.texi tex jargon.texi lpr -d jargon.dvi
このプログラムは、
使用に耐えるinfoファイルに変換可能なファイルは作成しませんが、
こうした機能は大した困難もなく追加することが可能です。
この例は非常に長いものですが、
大して複雑でもないので、
尻込みしないで研究してみてください。
/*
* j2t.lex : スタート状態を利用(ひょっとして悪用!)する例
*/
%{
#define MAX_STATES 1024
#define TRUE 1
#define FALSE 0
#define CHAPTER "@chapter"
#define SECTION "@section"
#define SSECTION "@subsection"
#define SSSECTION "@subsubsection"
int states[MAX_STATES];
int statep = 0;
int need_closing = FALSE;
char buffer[YY_BUF_SIZE];
extern char *yytext;
/*
* このプログラムが生成する*.texinfoファイルの先頭部分を作る。
* これは標準的なTexinfoヘッダである
*/
void print_header(void)
{
printf("\\input texinfo @c -*-texinfo-*-\n");
printf("@c %c**start of header\n",'%');
printf("@setfilename jargon.info\n");
printf("@settitle The New Hackers Dictionary\n");
printf("@synindex fn cp\n");
printf("@synindex vr cp\n");
printf("@c %c**end of header\n",'%');
printf("@setchapternewpage odd\n");
printf("@finalout\n");
printf("@c @smallbook\n");
printf("\n");
printf("@c ====================================================\n\n");
printf("@c This file was produced by j2t. Any mistakes are *not*\n");
printf("@c the fault of the jargon file editors.\n");
printf("@c ====================================================\n\n");
printf("@titlepage\n");
printf("@title The New Hackers Dictionary\n");
printf("@subtitle Version 2.9.10\n");
printf("@subtitle Generated by j2t\n");
printf("@author Eric S. Raymond, Guy L. Steel, and Mark Crispin\n");
printf("@end titlepage\n");
printf("@page\n");
printf("@c ====================================================\n");
printf("\n\n");
printf("@unnumbered Preface\n");
printf("@c *******\n");
}
/*
* 生成されるTexinfoファイルの末尾の部分を作成する
*/
void print_trailer(void)
{
printf("\n");
printf("@c ====================================================\n");
printf("@contents\n"); /* 目次を表示する */
printf("@bye\n\n");
}
/*
* 後でそれを見つけることができるよう、節または章に下線を引く
*/
void write_underline(int len, int space, char ch)
{
int loop;
printf("@c ");
for(loop=3; loop<space; loop++){
printf(" ");
}
while(len--){
printf("%c",ch);
}
printf("\n\n");
}
/*
* Texinfoにおいて特殊な意味を持つ文字をチェックし、エスケープする
*/
char *check_and_convert(char *string)
{
int buffpos = 0;
int len,loop;
len = strlen(string);
for(loop=0; loop<len; loop++){
if(string[loop] == '@' ||
string[loop] == '{' ||
string[loop] == '}')
{
buffer[buffpos++] = '@';
buffer[buffpos++] = string[loop];
} else {
buffer[buffpos++] = string[loop];
}
}
buffer[buffpos] = '\0';
return(buffer);
}
/*
* 章、節、項のヘッダを書き出す
*/
void write_block_header(char *type)
{
int loop;
int len;
(void)check_and_convert(yytext);
len = strlen(buffer);
for(loop=0; buffer[loop] != '\n';loop++)
buffer[loop] = '\0';
printf("%s %s\n",type,buffer);
write_underline(strlen(buffer),strlen(type)+1,'*');
}
%}
/*
* Flexの記述情報がここから始まる
*/
%x HEADING EXAMPLE ENUM EXAMPLE2
%x BITEM BITEM_ITEM
%s LITEM LITEM2
%%
^#[^#]*"#" /* ヘッダとフッタをスキップする */
/*
* 章は、その下にアスタリスクを持ち、コロンで終わる
*/
^[^\n:]+\n[*]+\n write_block_header(CHAPTER);
^"= "[A-Z]" ="\n"="* { /* 個々のカテゴリごとに節を作成する */
if(need_closing == TRUE){
printf("@end table\n\n\n");
}
need_closing = TRUE;
write_block_header(SECTION);
printf("\n\n@table @b\n");
}
"Examples:"[^\.]+ ECHO;
"*"[^*\n]+"*" { /* @emph{}(強調された)テキスト */
yytext[yyleng-1] = '\0';
(void)check_and_convert(&yytext[1]);
printf("@i{%s}",buffer);
}
"{{"[^}]+"}}" { /* 特別な強調 */
yytext[yyleng-2] = '\0';
(void)check_and_convert(&yytext[2]);
printf("@strong{%s}",buffer);
}
"{"[^}]+"}" { /* 特別な強調 */
yytext[yyleng-1] = '\0';
(void)check_and_convert(&yytext[1]);
printf("@b{%s}",buffer);
}
/* 特殊なTexinfo文字をエスケープする */
<INITIAL,LITEM,LITEM2,BITEM,ENUM,EXAMPLE,EXAMPLE2>"@" printf("@@");
<INITIAL,LITEM,LITEM2,BITEM,ENUM,EXAMPLE,EXAMPLE2>"{" printf("@{");
<INITIAL,LITEM,LITEM2,BITEM,ENUM,EXAMPLE,EXAMPLE2>"}" printf("@}");
/*
* @exampleコードを再生成する
*/
":"\n+[^\n0-9*]+\n" "[^ ] {
int loop;
int len;
int cnt;
printf(":\n\n@example \n");
strcpy(buffer,yytext);
len = strlen(buffer);
cnt = 0;
for(loop=len; loop > 0;loop--){
if(buffer[loop] == '\n')
cnt++;
if(cnt == 2)
break;
}
yyless(loop+1);
statep++;
states[statep] = EXAMPLE2;
BEGIN(EXAMPLE2);
}
<EXAMPLE,EXAMPLE2>^\n {
printf("@end example\n\n");
statep--;
BEGIN(states[statep]);
}
/*
* @enumerateリストを再生成する
*/
":"\n+[ \t]*[0-9]+"." {
int loop;
int len;
printf(":\n\n@enumerate \n");
strcpy(buffer,yytext);
len = strlen(buffer);
for(loop=len; loop > 0;loop--){
if(buffer[loop] == '\n')
break;
}
yyless(loop);
statep++;
states[statep] = ENUM;
BEGIN(ENUM);
}
<ENUM>"@" printf("@@");
<ENUM>":"\n+" "[^0-9] {
printf(":\n\n@example\n");
statep++;
states[statep] = EXAMPLE;
BEGIN(EXAMPLE);
}
<ENUM>\n[ \t]+[0-9]+"." {
printf("\n\n@item ");
}
<ENUM>^[^ ] |
<ENUM>\n\n\n[ \t]+[^0-9] {
printf("\n\n@end enumerate\n\n");
statep--;
BEGIN(states[statep]);
}
/*
* 1種類の@itemizeリストを再生成する
*/
":"\n+":" {
int loop;
int len;
printf(":\n\n@itemize @bullet \n");
yyless(2);
statep++;
states[statep] = LITEM2;
BEGIN(LITEM2);
}
<LITEM2>^":".+":" {
(void)check_and_convert(&yytext[1]);
buffer[strlen(buffer)-1]='\0';
printf("@item @b{%s:}\n",buffer);
}
<LITEM2>\n\n\n+[^:\n] {
printf("\n\n@end itemize\n\n");
ECHO;
statep--;
BEGIN(states[statep]);
}
/*
* リビジョン・ヒストリ部からリストを作成する。
* ここで"Version"が必要なのは、そうしないと他のルール
* と衝突するからである
*/
:[\n]+"Version"[^:\n*]+":" {
int loop;
int len;
printf(":\n\n@itemize @bullet \n");
strcpy(buffer,yytext);
len = strlen(buffer);
for(loop=len; loop > 0;loop--){
if(buffer[loop] == '\n')
break;
}
yyless(loop);
statep++;
states[statep] = LITEM;
BEGIN(LITEM);
}
<LITEM>^.+":" {
(void)check_and_convert(yytext);
buffer[strlen(buffer)-1]='\0';
printf("@item @b{%s}\n\n",buffer);
}
<LITEM>^[^:\n]+\n\n[^:\n]+\n {
int loop;
strcpy(buffer,yytext);
for(loop=0; buffer[loop] != '\n'; loop++);
buffer[loop] = '\0';
printf("%s\n",buffer);
printf("@end itemize\n\n");
printf("%s",&buffer[loop+1]);
statep--;
BEGIN(states[statep]);
}
/*
* @itemize @bulletリストを再生成する
*/
":"\n[ ]*"*" {
int loop;
int len;
printf(":\n\n@itemize @bullet \n");
len = strlen(buffer);
for(loop=0; loop < len;loop++){
if(buffer[loop] == '\n')
break;
}
yyless((len-loop)+2);
statep++;
states[statep] = BITEM;
BEGIN(BITEM);
}
<BITEM>^" "*"*" {
printf("@item");
statep++;
states[statep] = BITEM_ITEM;
BEGIN(BITEM_ITEM);
}
<BITEM>"@" printf("@@");
<BITEM>^\n {
printf("@end itemize\n\n");
statep--;
BEGIN(states[statep]);
}
<BITEM_ITEM>[^\:]* {
printf(" @b{%s}\n\n",check_and_convert(yytext));
}
<BITEM_ITEM>":" {
statep--;
BEGIN(states[statep]);
}
/*
* @chapter、@section等を再作成する
*/
^:[^:]* {
(void)check_and_convert(&yytext[1]);
statep++;
states[statep] = HEADING;
BEGIN(HEADING);
}
<HEADING>:[^\n] {
printf("@item @b{%s}\n",buffer);
write_underline(strlen(buffer),6,'~');
statep--;
BEGIN(states[statep]);
}
<HEADING>:\n"*"* {
if(need_closing == TRUE){
printf("@end table\n\n\n");
need_closing = FALSE;
}
printf("@chapter %s\n",buffer);
write_underline(strlen(buffer),9,'*');
statep--;
BEGIN(states[statep]);
}
<HEADING>:\n"="* {
if(need_closing == TRUE){
printf("@end table\n\n\n");
need_closing = FALSE;
}
printf("@section %s\n",buffer);
write_underline(strlen(buffer),9,'=');
statep--;
BEGIN(states[statep]);
}
<HEADING>"@" printf("@@");
<HEADING>:\n"-"* {
if(need_closing == TRUE){
printf("@end table\n\n\n");
need_closing = FALSE;
}
printf("@subsection %s\n",buffer);
write_underline(strlen(buffer),12,'-');
statep--;
BEGIN(states[statep]);
}
/*
* @exampleテキストを再作成する
*/
^" " {
printf("@example\n");
statep++;
states[statep] = EXAMPLE;
BEGIN(EXAMPLE);
}
<EXAMPLE>^" "
. ECHO;
%%
/*
* 初期化して実行する
*/
int main(int argc, char *argv[])
{
states[0] = INITIAL;
statep = 0;
print_header();
yylex();
print_trailer();
return(0);
}
このプログラムは、
ASCIIの専門用語ファイルを読み込んで、
いくつかのよく見られるパターンを検索します。
このパターンは、
オリジナルのTexinfo形式の専門用語ファイルを単なるASCIIテキストに変換した際に作成されたものです。
この変換の過程で、
多くのマークアップ情報が失われているために、
ある出力結果の元になったオリジナルの情報が何であったか、
あるいは、
そのオリジナルの候補が2つ3つあったとしても、
そのうちのどれがその出力結果をもたらしたかを正確に決定することが困難であるという事情のため、
この検索作業はいくらか複雑なものになります。
よく見られるパターンをいくつか挙げると、
以下のようになります。
:some text:\nこの後ろに、 (章の場合は)アスタリスクによる下線、 (節の場合は)等号による下線、 (項の場合は)マイナス記号による下線が続きます。
*...*、
(強調文字(strong)の場合は){{...}}、
(太字(bold)の場合は){...}の対によって示されます。
ここでは、
この3種類を検索して、
コマンドを出力します。
...enumerated:
0.some text
1.some more text
また、
実例は以下のようになります。(14)
...example:
some text
ここでの例は、
パースされているものが何であるかを示すヒントとしてこのようなパターンを使い、
その特定のセクション用の部分的なパーサを
(ほとんどの場合、排他的)スタート状態を使って作ります。
ASCII版の専門用語ファイルを持っているのであれば、
スキャナのどの部分がそのファイル中の何にマッチするかを検証してみる値打ちがあります。
例えば、
HEADING状態において@itemを生成するルールが、
すべての専門用語のエントリを処理するルールでもあるということは、
おそらく一見しただけでは明らかではないでしょう。