フォト
無料ブログはココログ

« 谷津山の鉄塔 | トップページ | 風に立つライオン »

2014年9月16日 (火)

yacc/lex-プログラムジェネレータon UNIX

yacc/lex-プログラムジェネレータon UNIX 五月女健治著 啓学出版
Yacc_lex

 ずいぶん前に買った本。
啓学出版が倒産したらしく書店で久しく見なかったのだがテクノプレスから同名で出版されているようだ。

 yacc/lexを知ったのは1989年頃アスキーだったかCマガだったかで知った。 GNUにbison/flaxがあることが分かったのだが当時田舎では手に入れることがでなかった。
 数年後PCにFreeBSDをインストールしてから使えるようになった。 その頃買ったのだと思う。 今はbaison/flexがCygwinにあるので気楽に使える。 

 yacc(Yet Another Compiler Compiler)は 1970にStephen C. Johnson が書いた、BNF(Backus-Naur form)風の定義からパーサジェネレータのCソースを出力するUNIXのツール。Dennis M. Ritchie がCを書いたのが1972だから、最初はBで書いたらしい。 Stephan C. Johnsonはyaccのほかにlintやspel、Portable Cも書いている有名な人。

 電卓のサンプルはそこらじゅうにあるので、CSVのパーサを作ってみる。
CSVはたいていの言語で組み込み関数があったり、ライブラリが用意されていたりだ。自分で実装するのは難しくない。全ての文字を許すエスケープ・フィールド(’”’)の処理と、’”’のエスケープ処理に気をつければ良い。yaccを持ち出すより小さく書ける。 ^^)

 CSVはRFC1480にある(日本語訳はここ)けど、RFCに書いてある定義はABNFなので、そのままyaccに食わせることができない。ここにあるEBNF定義をyaccが喰えるように変更する。

CVSのEBNF定義(http://fileformats.archiveteam.org/wiki/CSV
<CSVFile> ::= <Record>*

<Record> ::= { <Field> (<Delimiter> <Field>)* } <EOL>

<Field> ::= <SimpleField> <QuotedField>

<SimpleField> :== AlphaNum*  ;Any sequence of alpha-numeric characters   

<QuotedField> :== <QuoteChar> <Anychar>* <QuoteChar>

<QuoteChar> :== " | '  ;but note that they generally must match

<Delimiter> :== ","

繰り返しを再帰で書き直してyaccのソースにして、lexを使わないでyaccだけでやってみる。
コンパイルは↓

$ yacc -dv csv.y && gcc -DYYDEBUG -DYYERROR_VERBOSE -o csv y.tab.c

csv.y
%{
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define BUF_SIZE 1024
int Debug = 0;
#define DEBUG(l) if ((l)<=Debug)
char buf[BUF_SIZE];
char *top = buf;
FILE *yyin;
int yylineno = 1;
int fldno=1;
void yyerror(const char *msg) ;
int yylex(void) ;
%}

%union {
char *str;
char chr;
}
%start CSVFile
%type <str> CSVFile Record FieldList Field SimpleField QuotedField
%type <str> QuotedFieldBody
%type <chr> QuoteChar Anychar AlphaNum Mark CR LF
%type   <chr>   Space SpaceChar SpaceChars

%%

CSVFile     : Record
            | CSVFile Record
            ;

Record      : EOL { $$=NULL; }
            | FieldList EOL { yylineno++; fldno=1; }
            ;

FieldList   : Field { top=buf;
  printf("%d.%d:<%s>\n",
         yylineno, fldno++, $1);
}
            | FieldList Delimiter Field { top=buf;
  printf("%d.%d:[%s]\n",
         yylineno, fldno++, $3);
}
            ;

Field       : SimpleField { $$=$1;
  DEBUG(1)printf("SFld=%s\n", $1);
}
            | QuotedField { $$=$1; }
            ;

SimpleField : AlphaNum { $$=buf; *top++=$1; *top='\0';
  DEBUG(1)
printf("AlNum=%02x\n",$1);
}
            | SimpleField AlphaNum { $$=buf; *top++=$2; *top='\0';
DEBUG(1)
printf("AlNum=%02x\n",$2);
}
            ;

QuotedField : QuoteChar QuotedFieldBody QuoteChar { $$=$2; }
            ;

QuoteChar   : '"'  { $$='"'; }
            | '\'' { $$='\''; }
            ;

QuotedFieldBody : Anychar { $$=buf; *top++=$1; *top='\0';
  DEBUG(1)
printf("QFBody=%s\n",buf);
}
            | QuotedFieldBody Anychar { $$=buf; *top++=$2; *top='\0';
  DEBUG(1)
printf("QFBody=%s\n",buf);
}
            ;

Anychar     : AlphaNum | Mark | SpaceChar | CR | LF | QuoteChar QuoteChar;

Delimiter   : Space ',' Space ;

EOL         : CR LF | LF;

CR          : '\r' { $$='\r'; } ;
LF          : '\n' { $$='\n'; } ;
            ;

AlphaNum    : '0'{$$='0';} | '1'{$$='1';} | '2'{$$='2';} | '3'{$$='3';}
            | '4'{$$='4';} | '5'{$$='5';} | '6'{$$='6';} | '7'{$$='7';}
            | '8'{$$='8';} | '9'{$$='9';}
            | 'A'{$$='A';} | 'B'{$$='B';} | 'C'{$$='C';} | 'D'{$$='D';}
            | 'E'{$$='E';} | 'F'{$$='F';} | 'G'{$$='G';} | 'H'{$$='H';}
            | 'I'{$$='I';} | 'J'{$$='J';} | 'K'{$$='K';} | 'L'{$$='L';}
            | 'M'{$$='M';} | 'N'{$$='N';} | 'O'{$$='O';} | 'P'{$$='P';}
            | 'Q'{$$='Q';} | 'R'{$$='R';} | 'S'{$$='S';} | 'T'{$$='T';}
            | 'U'{$$='U';} | 'V'{$$='V';} | 'W'{$$='W';} | 'X'{$$='X';}
            | 'Y'{$$='Y';} | 'Z'{$$='Z';}
            | 'a'{$$='a';} | 'b'{$$='b';} | 'c'{$$='c';} | 'd'{$$='d';}
            | 'e'{$$='e';} | 'f'{$$='f';} | 'g'{$$='g';} | 'h'{$$='h';}
            | 'i'{$$='i';} | 'j'{$$='j';} | 'k'{$$='k';} | 'l'{$$='l';}
            | 'm'{$$='m';} | 'n'{$$='n';} | 'o'{$$='o';} | 'p'{$$='p';}
            | 'q'{$$='q';} | 'r'{$$='r';} | 's'{$$='s';} | 't'{$$='t';}
            | 'u'{$$='u';} | 'v'{$$='v';} | 'w'{$$='w';} | 'x'{$$='x';}
            | 'y'{$$='y';} | 'z'{$$='z';}
            ;

Mark        : '!'{$$='!';}                | '#'{$$='#';}
            | '$'{$$='$';} | '%'{$$='%';} | '&'{$$='&';}
            | '('{$$='(';} | ')'{$$=')';} | '*'{$$='*';} | '+'{$$='+';}
            | '-'{$$='-';} | '.'{$$='.';} | '/'{$$='/';} | ':'{$$=':';}
            | ';'{$$=';';} | '<'{$$='<';} | '='{$$='=';} | '>'{$$='>';}
            | '?'{$$='?';} | '@'{$$='@';} | '['{$$='[';} | '\\'{$$='\\';}
            | ']'{$$=']';} | '^'{$$='^';} | '_'{$$='_';}
            | '`'{$$='`';} | '{'{$$='{';} | '|'{$$='|';} | '}'{$$='}';}
            | '~'{$$='~';}
            ;

Space       : /* empty */ {}
            | SpaceChars
            ;

SpaceChars  : SpaceChar
            | Space SpaceChar
            ;

SpaceChar   : ' '{$$=' ';} | '\t'{$$='\t';}
            ;


%%

void yyerror(const char *msg) {
fprintf(stderr, "error %s : line %d\n", msg, yylineno);
}

int yylex(void) {
int c = getc(yyin);
DEBUG(1) printf("C=%02x(%c) ", c,c);
return c;
}

void help(char *cmd) {
char *p;
if ((p=strrchr(cmd, '/'))==NULL)
p = cmd;
else p++;
fprintf(stderr, "usage: %s [-hdl][csv_file]\n", p);
exit(-1);
}

int main(int ac, char **av) {
int c;
while ((c=getopt(ac,av,"Ddh"))!=-1) {
switch (c) {
case 'd' : Debug++; break;
case 'D' : yydebug = 1; break;
case 'h' :
default  : help(av[0]); break;
}
}
ac -= optind;
av += optind;
if (0<ac) {
if ((yyin=fopen(av[0],"rb"))==NULL) {
perror("csv file");
exit(-2);
   }
} else yyin = stdin;
return yyparse();
}

 いやあ直接Cで書いたほうがラクじゃないか!!ということでlexと組み合わせてみる。

%{
/*-----------------------------------*/
/*  csv.y                            */
/*  Sep. 2014 / Yoshi                */
/*-----------------------------------*\
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define BUF_SIZE 1024
int Debug = 0;
#define DEBUG(l) if ((l)<=Debug)
FILE *yyin;
extern int yylineno;
int fldno=1;
void yyerror(const char *msg) ;
int yylex(void) ;
%}

%union { char *str; }
%start CSVFile

%token DELIMITER QUOTEDFIELD SIMPLEFIELD EOL
%type <str> CSVFile Record FieldList Field SIMPLEFIELD QUOTEDFIELD

%%

CSVFile     : Record
            | CSVFile Record
            ;

Record      : EOL { $$=NULL; }
            | FieldList EOL { yylineno++; fldno=1; }
            ;

FieldList   : Field { printf("%d.%d:<%s>\n", yylineno, fldno++, $1); }
            | FieldList DELIMITER Field
                        { printf("%d.%d:[%s]\n", yylineno, fldno++, $3); }
            ;

Field       : SIMPLEFIELD { $$=$1; }
            | QUOTEDFIELD { $$=$1; }
            ;

%%

void yyerror(const char *msg) {
fprintf(stderr, "error %s : line %d\n", msg, yylineno);
}

void help(char *cmd) {
char *p;
if ((p=strrchr(cmd, '/'))==NULL)
p = cmd;
else p++;
fprintf(stderr, "usage: %s [-hdl][csv_file]\n", p);
exit(-1);
}

int main(int ac, char **av) {
int c;
while ((c=getopt(ac,av,"Ddh"))!=-1) {
switch (c) {
case 'd' : Debug++; break;
case 'D' : yydebug = 1; break;
case 'h' :
default  : help(av[0]); break;
}
}
ac -= optind;
av += optind;
if (0<ac) {
if ((yyin=fopen(av[0],"rb"))==NULL) {
perror("csv file");
exit(-2);
   }
} else yyin = stdin;
return yyparse();
}

/*==============================================================*/
%{
/*-----------------------------------*/
/*  csv.l                            */
/*  Sep. 2014 / Yoshi                */
/*-----------------------------------*\
#include <stdio.h>
#include "y.tab.h"

int yywrap(void) { return 1; };
%}

%s QUOTE
ALPHNUM [a-zA-Z0-9]+
ANYCHAR ([^"]|(\"\"))*

%%
<INITIAL>, return DELIMITER;
<INITIAL>\r?\n return EOL;
<INITIAL>{ALPHNUM} yylval.str = strdup(yytext); return SIMPLEFIELD;
<INITIAL>\" BEGIN(QUOTE);
<QUOTE>{ANYCHAR} {  int i, j;
        for (i=j=1; i<yyleng; i++) {
           if (yytext[i]!='"' && yytext[i-1]!='"')
                                   {
               yytext[j++] = yytext[i];
           }
        }
    yytext[j] = '\0';
    yylval.str = strdup(yytext);
    return QUOTEDFIELD;
}
<QUOTE>\" BEGIN(INITIAL);

[ \t]+ ;
. return yytext[0];

%%

/*==============================================================*/
#
# csv.y csv.l makefile
# Sep. 2014 / Yoshi
#
RM = rm -f
LEX = flex
YACC = yacc -d
CC = gcc
TARGET = csv
CFLAGS = -DYYDEBUG -DYYEROR_VERBOSE
SRC = 


all : y.tab.o lex.yy.o
$(CC) $(CFLAGS) -o $(TARGET) y.tab.o lex.yy.o

y.tab.c : $(TARGET).y
$(YACC) -v $(TARGET).y

lex.yy.c : $(TARGET).l
$(LEX) $(TARGET).l

clean:
$(RM) y.tab.c lex.yy.c $(TARGET) y.output

実行したところ↓行番号.フィールド番号[データ]のように表示する。
3行目の空白(’ ’)、引用符(’”’)、改行(’\n’)がちゃんと切り出されている。

$ cat test.csv
aaa,bbb,ccc
aaa ,  bbb      ,       ccc
"a a", "b""", "c
"

$ ./csv test.csv
1.1:<aaa>
1.2:[bbb]
1.3:[ccc]
2.1:<aaa>
2.2:[bbb]
2.3:[ccc]
3.1:<a a>
3.2:[b"]
3.3:[c
]
                                                      

CSVくらいの文法なら、yacc&lexを使うよりCで実装したほうが簡単かも。

アセンブラくらいになると有難さが分かる。

« 谷津山の鉄塔 | トップページ | 風に立つライオン »

書籍・雑誌」カテゴリの記事

コメント

コメントを書く

コメントは記事投稿者が公開するまで表示されません。

(ウェブ上には掲載しません)

トラックバック

この記事のトラックバックURL:
http://app.cocolog-nifty.com/t/trackback/577514/60309967

この記事へのトラックバック一覧です: yacc/lex-プログラムジェネレータon UNIX:

« 谷津山の鉄塔 | トップページ | 風に立つライオン »