ここでは、 スタート状態を使って、 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
また、 実例は以下のようになります。1
...example:
some text
ここでの例は、
パースされているものが何であるかを示すヒントとしてこのようなパターンを使い、
その特定のセクション用の部分的なパーサを
(ほとんどの場合、排他的)スタート状態を使って作ります。
ASCII版の専門用語ファイルを持っているのであれば、
スキャナのどの部分がそのファイル中の何にマッチするかを検証してみる値打ちがあります。
例えば、
HEADING状態において@itemを生成するルールが、
すべての専門用語のエントリを処理するルールでもあるということは、
おそらく一見しただけでは明らかではないでしょう。