桃源谷

心灵的旅行

人生就是一场旅行,不在乎旅行的目的地,在乎的是沿途的风景和看风景的心情 !
posts - 32, comments - 42, trackbacks - 0, articles - 0
  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

用lex/yacc来解析配置文件

Posted on 2009-02-02 12:31 lymons 阅读(4672) 评论(0)  编辑 收藏 引用 所属分类: CUnix/Linux
From 2008精选

linux平台下的较为庞大的命令一般都带有一个配置文件,用于存储该命令启动时要设置的参数,用户还可以变更该配置文件中的某些域的值。因此,在命令中就要考虑怎么来存取这些文件里的值。一般情况下,大多数程序员都愿意自己编写一段程序来解析配置文件里内容,在配置文件比较小的情况下,该中方法也非常方便适用,我平时也喜欢这么作。但是,当在配置文件非常大情况下,配置文件里面的sectionfield非常多的情况下,自己编写程序来读取的话就变得非常麻烦和挠头了。幸亏,linux下还有lex/yacc这么一套工具来帮助程序员来完成这样的工作。

在互联网上,搜索了lexyacc这两个关键字,可以发现很多的相关文章,但这些文章大都是千篇一律,讲了那么一大堆理论,看完之后还是不知道在C程序里怎么使用lex/yacc。只有这些文章《LexYacc从入门到精通》还比较贴近实际编码,对我有一些帮助,建议大家可以先看看。

在这里,我就从配置文件解析这个日常编程经常要碰到的问题来作为例子,来告诉大家lex/yacc这玩意儿的使用方法。

首先,说明一下大概的原理。先看一下下面的图.

注:本文中的图摘自《Lex and Yacc for Embedded Programmers

 

 

 

 

 

 

 

 

 

Figure1: Execution path and data flow in a typical lex/yacc derived parser

1显示了在一个典型的扫描器/解析器的应用中的控制和数据流程。这个数据流通过扫描器规约成很多符号并且标识成有效的符号组合聚集在解析器中。也就是说,输入流(或者说是配置文件)中的所有单词通过lex工具的扫描生成很多的符号(token--- 就是资料上常说的词法分析,然后,这些符号再通过yacc工具进行语法解析(按照,编译原理中的巴克斯范式)。 

2描述了一个基于lex/yacc的应用程序的流程。一般情况下,做成一个读取配置文件的程序都要包含两个脚本文件,第一个是lex脚本文件(文件名的后缀一般都是.l形式,不过文件名的形式都没有作特别严格的定义),该脚本文件主要是利用正则表达式的形式对文件进行对配置文件进行正则匹配,这一点非常类似linuxawk工具。另外一个脚本就是yacc脚本(后缀名一般为.y形式),它主要是根据lex的分析结果进行语法解析。

Figure 2: Development sequence when using lex and yacc 

3表示了配置文件解析的数据流。正如下图描述的那样,配置文件里的内容首先是作为字符流的形式,通过lexyylex函数进行正则匹配后形成许多符号,然后作为yacc的输入,通过yyparse函数对这些符号流进行语法解析。


Figure 4: Data flow in a configuration file parser application

在上面提到的lex脚本,yacc脚本文件作成后,只需要在你自己的C程序中,手动调用yaccyyparse函数就可以了,只不过,还需要在C程序中添加一些存储yacc解析结果的处理函数,这得根据程序中的实际情况来定。 

下面是两个脚本文件和一个C程序文件的样例:
http://lymons.googlepages.com/yacc.html

lex脚本: test.conf.l
yacc脚本:test.conf.y
c源文件:test_conf.c
       test_conf.c第二版
编译方法及执行:

cvs parser #lex test_config.l

cvs parser #yacc -d test_config.y

cvs parser #cc lex.yy.c y.tab.c test_config.c -o test_config<!--[endif]-->

cvs parser # ./test_config test.conf.example

用lex/yacc来解析配置文件---test.conf.l
%{

#
include <string.h>

#include "y.tab.h"


 

extern 
int line;      //当前正在解析字符串的行号

%
}

%%


//下面定义配置文件里各关键字的正则,注意各关键字返回的token必须是在yacc的脚本文件中有定义

//
首先是定义section标题和field名的正则表达式

share                                   
return
 T_SHARE;

temp                                    
return
 T_TEMP;

public                                  
return
 T_PUBLIC;

comment                                 
return
 T_COMMENT;

writeable                               
return
 T_WRITEABLE;

path                                    
return
 T_PATH;

guest[ ]ok                              
return
 T_GUESTOK;

valid[ ]users                           
return
 T_VALID_USERS;

write[ ]list                            return
 T_WRITE_LIST;

create[ ]mode                           
return
 T_CREATE_MODE;

directory[ ]mask                        
return
 T_DIRECTORY_MODE;

<!--[if !supportEmptyParas]--> <!--[endif]-->


//其次是field的值的表达式

y
|Y|yes|Yes|YES    yylval.string = strdup(yytext); return
 T_STATE;

n
|N|no|No|NO       yylval.string = strdup(yytext); return
 T_STATE;

\"[^\n]+\"         yylval.string = strdup(yytext); return T_STRING; //要求字符串以""
为开头和结尾

[
0-9]+             yylval.number = strtol(yytext,NULL,8); return T_NUMBER; //因为是权限,
故要转化成8进制

\n                 line++//
遇到回车行号加一

\t]+             ;       //
忽略空格和tab

\#[^\n]*           ;       //忽略#开头的行


\;[^\n]*           ;       //忽略;开头的行

.                  return (int)yytext[0
];


用lex/yacc来解析配置文件---test.conf.y
//test.conf.y
%
{
void section_print(
int
);
void parameter_print(
int
);
%
}
//
定义每个关键字对应的token
%token
 T_SHARE
%token
 T_TEMP
%token
 T_PUBLIC
%token
 T_COMMENT
%token
 T_WRITEABLE
%token
 T_PATH
%token
 T_GUESTOK
%token
 T_VALID_USERS
%token
 T_WRITE_LIST
%token
 T_CREATE_MODE
%token
 T_DIRECTORY_MODE

//定义配置文件里field值的基本类型,
一般有两种;数字和字符串
%union

{
        
int number;
        char 
*
string;
}
 
%token <string> T_STRING //普通字符串,
例如路径
%token <number> T_NUMBER  //数字,
例如端口号
%token <string> T_STATE  //状态,例如yes,no


//定义语法解析树
//
为每一个section标题和field名定义相应的动作(函数)
//该动作(函数)的实现在.c文件里定义.

%%
parameters
:
        
| parameters parameter
        ;
parameter
:

        section_share
        
|
        comment
        
|
        writeable
        
|
        path
        
|
        guestok
        
|
        public
        
|
        valid_users
        
|
        write_list
        
|
        section_temp
        
|
        create_mode
        
|
        directory_mode
        ;
section_share
:
        
'[' T_SHARE ']'
        {
                section_print(T_SHARE);
        }
        ;
comment
:
        T_COMMENT 
'=' T_STRING
        {
                parameter_print(T_COMMENT);
        }
        ;
writeable
:

        T_WRITEABLE 
'=' T_STATE
        {
                parameter_print(T_WRITEABLE);
        }
        ;
path
:

        T_PATH 
'=' T_STRING
        {
                parameter_print(T_PATH);
        }
        ;
guestok
:

        T_GUESTOK 
'=' T_STATE
        {
                parameter_print(T_GUESTOK);
        }
        ;
public
:

        T_PUBLIC 
'=' T_STATE
        {
                parameter_print(T_PUBLIC);
        }
        ;
valid_users
:

        T_VALID_USERS 
'=' T_STRING
        {
                parameter_print(T_VALID_USERS);
        }
        ;
write_list
:

        T_WRITE_LIST 
'=' T_STRING
        {
                parameter_print(T_WRITE_LIST);
        }
        ;
section_temp
:

        
'[' T_TEMP ']'
        {
                section_print(T_TEMP);
        }
        ;
create_mode
:
        T_CREATE_MODE 
'=' T_NUMBER
        {
                parameter_print(T_CREATE_MODE);
        }
        ;
directory_mode
:

        T_DIRECTORY_MODE 
'=' T_NUMBER
        {
                parameter_print(T_DIRECTORY_MODE);
        }
        ;
%% 

用lex/yacc来解析配置文件---test_conf.c
//test_conf.c

#include 
<stdio.h>

#include 
<string.h>

#include 
<errno.h>

#include 
"y.tab.h"

 

extern FILE *yyin;

 

int line = 1//定义当前正在解析字符串的行号


 

void yyerror(const char *str) {

        fprintf(stderr,
"config: parse error at %d"
, line);

        fprintf(stderr,
" : %s\n"
,str);

        exit(
1
);

}


 

int yywrap() {

        
return 1
;

}


 

int main(int argc, char **argv) {

        
if (argc > 1
{

                yyin 
= fopen(argv[1], "r"
);

        }
 else {

                fprintf(stdout, 
"Usage: config <file_path>\n"
);

                exit(
1
);

        }


        
if (yyin == NULL) {

                fprintf(stdout, 
"config: file access error. func=%s ""fopen()"
);

                fprintf(stdout, 
"errno=%d(%s) name=%s\n", errno, strerror(errno), argv[0
]);

                exit(
1
);

        }


 

        printf(
"### %s ###\n", argv[1]);

 

        yyparse();

 

        fclose(yyin);

 

        exit(
0
);

}


<!--[if !supportEmptyParas]--> <!--[endif]-->

//定义当解析到每一个section标题时,yacc所采取的动作.

void section_print(int section) {

        
static int ishare = 0
;

        
static int itemp = 0
;

 

        
switch (section) 
{

        
case
 T_SHARE:

                ishare
++
;

                printf(
"[share:%d]\n"
, ishare);

                
break
;

        
case
 T_TEMP:

                itemp 
++
;

                printf(
"[temp:%d]\n"
, itemp);

                
break
;

        
default
:

                
break
;

        }


}


<!--[if !supportEmptyParas]--> <!--[endif]-->

//定义解析到每一个field时,yacc所采取的动作

void parameter_print(int parameter) {

        
switch (parameter) 
{

        
case
 T_PUBLIC:

                printf(
"\tpublic = %s\n", yylval.string
);

                
break
;

        
case
 T_COMMENT:

                printf(
"\tcomment = %s\n", yylval.string
);

                
break
;

        
case
 T_WRITEABLE:

                printf(
"\twriteable = %s\n", yylval.string
);

                
break
;

        
case
 T_PATH:

                printf(
"\tpath = %s\n", yylval.string
);

                
break
;

        
case
 T_GUESTOK:

                printf(
"\tguest ok = %s\n", yylval.string
);

                
break
;

        
case
 T_VALID_USERS:

                printf(
"\tvalid users = %s\n", yylval.string
);

                
break
;

        
case
 T_WRITE_LIST:

                printf(
"\twrite list = %s\n", yylval.string
);

                
break
;

        
case
 T_CREATE_MODE:

                printf(
"\tcreate mode = %o\n"
, yylval.number);

                
break
;

        
case
 T_DIRECTORY_MODE:

                printf(
"\tdriectory mask = %d\n"
, yylval.number);

                
break
;

        
default
:

                
break
;

        }


}
 


yacc读取配置文件内容 --- c源文件改进版
为了读取配置文件某一个区域的数据,特在C源文件里
把读取某区域的动作封装成一个函数,方便调用.
例如:
read_share_section,read_server_section
注意
yylval.string的内存实在test_conf.l文件中生成的,
所以在
parameter_print使用完毕后要释放该内存

因为该程序事先把配置文件中的内容按照类型存储到
全局变量的数组里,等到用户指定要读取那一段区域
的内容是在把该段的内容返回给用户.
因为说明的重点并不是怎么释放内存,
程序里并没有加入释放该全局数组的内存的代码.
  1#include <stdio.h>
  2#include <string.h>
  3#include <stdlib.h>
  4#include <errno.h>
  5#include "y.tab.h"
  6
  7extern FILE *yyin;
  8
  9int line = 1;
 10
 11void yyerror(const char *str) {
 12  fprintf(stderr,"config: parse error at %d", line);
 13  fprintf(stderr," : %s\n",str);
 14  exit(1);
 15}

 16 
 17int yywrap() {
 18  return 1;
 19}

 20
 21#define LEN 129
 22#define UGNUM 128
 23#define NAMELEN 9
 24#define PASSLEN 17
 25#ifndef PATH_MAX
 26#define PATH_MAX 4096
 27#endif
 28
 29typedef struct share_section {
 30  char comment[LEN];
 31  int writeable;
 32  char path[PATH_MAX];
 33  int guestok;
 34  int public;
 35  int create_mode;
 36  int directory_mask;
 37  char *valid_users;
 38  char *write_list;
 39}
share_section_t;
 40
 41typedef struct server_section {
 42  char comment[LEN];
 43  char hostname[LEN];
 44  char ipaddr[16];
 45  char loginname[NAMELEN];
 46  char loginpass[PASSLEN];
 47  int port;
 48  char encoding[NAMELEN];
 49}
 server_section_t;
 50
 51
 52share_section_t *share_sections[16];
 53server_section_t *server_sections[16];
 54int ishare = 0;
 55int iserver = 0;
 56
 57int main(int argc, char **argv) {
 58  share_section_t share;
 59  server_section_t server;
 60
 61  if (argc > 1{
 62    yyin = fopen(argv[1], "r");
 63  }
 else {
 64    fprintf(stdout, "Usage: config <file_path>\n");
 65    exit(1);
 66  }

 67  if (yyin == NULL) {
 68    fprintf(stdout, "config: file access error. func=%s ""fopen()");
 69    fprintf(stdout, "errno=%d(%s) name=%s\n", errno, strerror(errno), argv[0]);
 70    exit(1);
 71  }

 72
 73  yyparse();
 74
 75  fclose(yyin);
 76
 77  read_share_section(0&share);//读取配置文件中第一个share区的内容.
 78
 79  exit(0);
 80}

 81
 82void section_print(int section) {
 83
 84  switch (section) {
 85  case T_SHARE:
 86    if (ishare > 16)
 87      return;
 88    share_sections[ishare ++= 
 89      (share_section_t *)malloc(sizeof(share_section_t));
 90
 91    //printf("[share:%d]\n", ishare);
 92    break;
 93  case T_SERVER:
 94    if (iserver > 16)
 95      return;
 96    server_sections[iserver ++= 
 97      (server_section_t*)malloc(sizeof(server_section_t));
 98    //printf("[temp:%d]\n", itemp);
 99    break;
100  default:
101    break;
102  }

103}

104
105void parameter_print(int parameter) {
106  if (ishare <= 0 || ishare > 16 || iserver <= 0 || iserver > 16)
107    return;
108
109  share_section_t *sharesec = share_sections[ishare-1];
110  server_section_t *serversec = server_sections[iserver-1];
111
112  switch (parameter) {
113  case T_PUBLIC:
114  //  printf("\tpublic = %s\n", yylval.string);
115    sharesec->public = yylval.number;
116    break;
117  case T_COMMENT:
118    //printf("\tcomment = %s\n", yylval.string);
119    strncpy(sharesec->comment, yylval.string, LEN);
120    free(yylval.string);
121    break;
122  case T_WRITEABLE:
123    //printf("\twriteable = %s\n", yylval.string);
124    sharesec->writeable = yylval.number;
125    break;
126  case T_PATH:
127    //printf("\tpath = %s\n", yylval.string);
128    strncpy(sharesec->path, yylval.string, PATH_MAX);
129    free(yylval.string);
130    break;
131  case T_GUESTOK:
132    //printf("\tguest ok = %s\n", yylval.string);
133    sharesec->guestok = yylval.number;
134    break;
135  case T_VALID_USERS:
136    //printf("\tvalid users = %s\n", yylval.string);
137    sharesec->valid_users = strdup(yylval.string);
138    free(yylval.string);
139    break;
140  case T_WRITE_LIST:
141    //printf("\twrite list = %s\n", yylval.string);
142    sharesec->write_list = strdup(yylval.string);
143    free(yylval.string);
144    break;
145  case T_CREATE_MODE:
146    //printf("\tcreate mode = %o\n", yylval.number);
147    sharesec->create_mode = yylval.number;
148    break;
149  case T_DIRECTORY_MODE:
150    //printf("\tdriectory mask = %d\n", yylval.number);
151    sharesec->directory_mask = yylval.number;
152    break;
153  case T_HOSTNAME:
154    strncpy(serversec->hostname, yylval.string, LEN);
155    free(yylval.string);
156    break;
157  case T_IPADDR:
158    strncpy(serversec->ipaddr, yylval.string15);
159    free(yylval.string);
160    break;
161  case T_LOGINNAME:
162    strncpy(serversec->loginname, yylval.string, NAMELEN);
163    free(yylval.string);
164    break;
165  case T_LOGINPASS:
166    strncpy(serversec->loginpass, yylval.string, NAMELEN);
167    free(yylval.string);
168    break;
169  case T_PORT:
170    serversec->port = yylval.number;
171    break;
172  case T_ENCODE:
173    strncpy(serversec->encoding, yylval.string, NAMELEN);
174    free(yylval.string);
175    break;
176  default:
177    break;
178  }

179}
 
180
181int read_share_section(int offset,  share_section_t *section)
182{
183  if (offset >= ishare)
184    return -1;
185
186  section = share_sections[offset]; 
187  
188  return 0;
189}

190
191int read_server_section(int offset, server_section_t *section)
192{
193  if (offset >= iserver)
194    return -1;
195
196  section = server_sections[offset];
197
198  return 0;
199}

200



只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


我的个人简历第一页 我的个人简历第二页