注釈付きでマークされた文字列の解析 #10
書いたBNFを元に構文解析器と字句解析器のソースを書いてみる。
まず、構文解析器。
parser.y
%{ #define YYSTYPE char* #include <stdio.h> #include <stdlib.h> #include <string.h> int yylex(void); void yyerror(char const *); static char *estrdupcat(const char *s1, const char *s2); %} %token STRING_EXCEPT_SPECIAL %% text : string_headed | marked_string_headed ; string_headed : string_except_parenthesis { printf("string [%s]\n", $1); free($1); } | string_except_parenthesis { printf("string [%s]\n", $1); free($1); } marked_string_headed ; marked_string_headed : marked_string | marked_string marked_string_headed | marked_string string_headed ; marked_string : '(' marked_string_body_with_annotation ')' ; marked_string_body_with_annotation : STRING_EXCEPT_SPECIAL { printf("marked-string [%s]\n", $1); free($1); } | STRING_EXCEPT_SPECIAL ':' string_except_parenthesis { printf("marked-string [%s]: annotation[%s]\n", $1, $3); free($1); free($3); } ; string_except_parenthesis : STRING_EXCEPT_SPECIAL { $$ = estrdupcat(NULL, $1); free($1); } | ':' { $$ = estrdupcat(NULL, ":"); } | string_except_parenthesis STRING_EXCEPT_SPECIAL { $$ = estrdupcat($1, $2); free($1); free($2); } | string_except_parenthesis ':' { $$ = estrdupcat($1, ":"); free($1); } ; %% int main(void) { return yyparse(); } void yyerror(char const *s) { fprintf(stderr, "%s\n", s); } static char *estrdupcat(const char *s1, const char *s2) { size_t len = (s1 == NULL ? 0 : strlen(s1)) + strlen(s2) + 1; char *p = malloc(len); if (p == NULL) { fputs("error: estrdupcat cannot allocate any memories.\n", stderr); exit(1); } return s1 == NULL ? strcpy(p, s2) : strcat(strcpy(p, s1), s2); }
colon_forbidden
関係は必要なくしたので、
規則中のアクションのようにソースを見づらくするものが最小限に戻った。
STRING_EXCEPT_PARENTHESIS
は終端記号として字句解析器から供給されないので、
%token
での宣言を無くし、非終端記号string_except_parenthesis
として規則部で定義している。
定義した関数estrdupcat
はこの括弧無し文字列を生成していくための補助関数である。
この関数は、新たに割り当てた領域に、引数で与えられた二つの文字列を連接してコピーする。
第一引数がNULLの場合は第二引数の文字列を割り当てた領域にコピーするだけである。
標準関数のstrcatやstrcpyと違い、引数とは別に領域を割り当てるので引数の文字列はどちらもconst修飾している。
lexer.l
...snip static char *estrdup(const char *s); %} ...snip %% [^():]+ { yylval = estrdup(yytext); return STRING_EXCEPT_SPECIAL; } [():] { return yytext[0]; } %% ...snip
字句解析器については、colon_forbidden
のextern宣言と、
括弧を含まない文字列に対応するパターン[^()]+
の行を削除しただけである。
関数yylexは単純に括弧やコロンを含まない文字列を返すか、括弧やコロンを一文字ずつ返すかだけを行う。
非常にシンプルになったし、#8で使ったREJECTは高コストなので使わないならそれに越したことはない。
構文解析系と字句解析系の分担としては、このくらいのバランスが妥協点なのかもしれない。