您的位置:首页 > 其它

使用lex&yacc实现一个xml解析器 .

2012-04-04 10:44 579 查看
在开始编写xml解析器之前我们先来简单介绍一下lex ,yacc。

 

Lex:

Lex工具是一种词法分析程序生成器,它可以根据词法规则说明书的要求来生成单词识

别程序,由该程序识别出输入文本中的各个单词。一般可以分为<定义部分><规则部

分><用户子程序部分>。其中规则部分是必须的,定义和用户子程序部分是任选的。 

(1)定义部分

定义部分起始于 %{ 符号,终止于 %} 符号,其间可以是包括include语句、声明语句

在内的C语句。这部分跟普通C程序开头没什么区别。

%{

#include "stdio.h"

int linenum;

%}

(2) 规则部分

规则部分起始于"%%"符号,终止于"%%"符号,其间则是词法规则。词法规则由模式和

动作两部分组成。模式部分可以由任意的正则表达式组成,动作部分是由C语言语句组

成,这些语句用来对所匹配的模式进行相应处理。需要注意的是,lex将识别出来的单

词存放在yytext[]字符数据中,因此该数组的内容就代表了所识别出来的单词的内容。

类似yytext这些预定义的变量函数会随着后面内容展开一一介绍。动作部分如果有多

行执行语句,也可以用{}括起来。

%%

title                 showtitle();

[\n]                  linenum++;

[0-9]+                printf("Int     : %s\n",yytext);

[0-9]*\.[0-9]+        printf("Float   : %s\n",yytext);

[a-zA-Z][a-zA-Z0-9]*  printf("Var     : %s\n",yytext);

[\+\-\*\/\%]          printf("Op      : %s\n",yytext);

.                     printf("Unknown : %c\n",yytext[0]);

%%

lex规则部分具有优先级的概念:

1  Lex会选择最长的字符匹配规则。

2  当存在多个规则同事满足时,这时Lex只会选择第一个规则

(3) 用户子程序部分

最后一个%%后面的内容是用户子程序部分,可以包含用C语言编写的子程序,而这些子

程序可以用在前面的动作中,这样就可以达到简化编程的目的。

Lex(Lexical Analyzar) 一些的内部变量和函数

内部预定义变量:

yytext   char *  当前匹配的字符串

yyleng   int     当前匹配的字符串长度

yyin     FILE *  lex当前的解析文件,默认为标准输出

yyout    FILE *  lex解析后的输出文件,默认为标准输入

yylineno int     当前的行数信息

内部预定义宏:

ECHO     #define ECHO fwrite(yytext, yyleng, 1, yyout)  也是未匹配字符的默认动作

内部预定义的函数:

int yylex(void)    调用Lex进行词法分析

int yywrap(void)   在文件(或输入)的末尾调用。如果函数的返回值是1,就停止解析。因此它可以用来解析多个文件。

注意lex库提供一些默认的函数,例如main,yywrap函数等。 当编译时不带-ll选项时,必须自行实现main函数和yywrap等函数。

在linux中假设现在有lex规则文件a.l。运行lex a.l  便自动生成lex.yy.c

 cc -o test lex.yy.c -ll 便生成了可执行程序。注意当编译时不带-ll选项时,必须自行实现main函数和yywrap等函数。

 

Yacc

如同 Lex 一样, 一个 Yacc 程序也用双百分号分为三段。 它们是:声明、语法规则和 C 代码

用 Yacc 来创建一个编译器包括四个步骤:

1 编写一个 .y 的语法文件(同时说明 C 在这里要进行的动作)。

2 编写一个词法分析器来处理输入并将标记传递给解析器。 这可以使用 Lex 来完成。

3 编写一个函数,通过调用 yyparse() 来开始解析。

4 编写错误处理例程(如 yyerror())。

5 通过yacc工具对.y生代码并编译它。

yacc 必须与lex一起使用,而lex可以单独使用。因为yacc需要使用lex来返回标记。

在linux中假设现在有lex规则文件a.l, a.y。

运行lex a.l  便自动生成lex.yy.c

运行 yacc -d a.y  便自动生成了y.tab.c  y.tab.h   -d表示生成头文件

 cc -o test lex.yy.c y.tab.c -ll -ld 便生成了可执行程序 

同样 yacc库也提供了一些默认的函数 例如main,yyerror等,如果不连接yacc库,需要自行实现这些函数。

 

 

yacc的一些细节 

1、嵌入式动作在规则内变成一个符号,所以它的值($$)像任何其他的符号一样对于规则末端的动作都是可以用的。

如:

thing: A {$$=17;} B C

{printf(%d"",$2);}

在使用嵌入式动作时,如果正在使用“%union”和有类型的符号值,则在引用动作值时就得将值放入尖括号内,例如将它放入嵌入式动作时的$<type>$,以及在规则末端动作中引用它的$<type>3。

2、明确的符号类型

通过在¥和符号数字之间使用或在2个$之间的尖括号内插入类型名,如$<xxx>3或$<xxx>$,YACC允许为符号值引用声明一个明确的类型。3、标记值

YACC语法分析程序中每个符号都有一个相关的值。在语法分析程序中,一定要声明所有具有值的标记的值类型。

%union声明标识符号值的所有可能的C类型。将拷贝到输出文件的YYSTYPE类型的C联合类型声明中。

4、yyerror()提供错误报告例程

5、YYABORT使得语法分析例程yyparse()以一个非零值立即返回,显示失败

6、YYACCEPT:yyparse()以一个零值返回,显示成功。

7、yyparse()语法分析程序的入口点,成功返回零,失败返回非零

8、YACC的冲突,YACC只能向前看一个标记。如下的没有冲突:tgt

start:x B

|y C;

x: A;

y:A;

9、移进/归约冲突

stmt:if '(' cond')' stmt

|if '(' cond ')' stmt ELSE stmt

if (cond) if (cond) stmt ELSE stmt如何理解

1)if (cond){if (cond) stmt ELSE stmt}

2)if (cond){if (cond) stmt }ELSE stmt

设置明显的优先级阻止YACC发布警告

%nonassoc LOWER_THEN_ELSE或%nonassoc THEN

%nonassoc ELSE

ELSE优先级比LOWER_THEN_ELSE高,因为移进优先级必须高于归约优先级

1、继承的属性

属性以标记值开始,即从语法分析树的叶子开始。每次规则被归约时,信息概念性地在语法分析树中上移。并且它的动作根据规则右侧符号值勤合成符号($$)的值。

例如:

declaration:class type namelist;

class: GLOBAL {$$=1;}

|LOCAL {$$=2;}

;

type:REAL {$$=1;}

|INTEGER {$$=2;}

;

namelist:NAME {mksymbol($0,$-1,$1);}

|namelist NAME {mksymbol($0,$-1,$2);}

yacc允许访问它的左侧的内部堆栈上的符号($0,$-1,$-2....),上例中$0指在namelist产生式的符号之前被堆栈的type的值。$0指class。

继承的
4000
符号类型必须在动作代码中采用明确的类型提供类型名。

2、文字块

%{

.....

%}

内容被原封不动地拷贝到生成的C源文件中靠近开头的部分。

3、优先级和结合性

结合性:%left左结合,%right右结合,%nonassoc非结合操作

通过声明结合性的顺序决定优先级,声明在后的优先级高

4、左、右递归,左递归更适合于YACC处理

exprlist:exprlist ','expr;

exprlist:expr ','exprlist;

5、规则尾端有明确的优先级:

expr:expr '*' expr

|expr '-' expr

|'-'expr %prec UMINUS;

6、符号类型

YACC在内部将每个值做为包括所有类型的C联合类型来声明。在%union声明中列出所有的类型,YACC将其转变为称为YYSTYPE的联合类型的类型定义。对于值已在动作代码中设置和使用的符号,必须声明它的类型。对非终结符使用%type(如%type<dval> expression),对标记即终结符使用%token,%left,%right,%nonassoc,(如:%token<dval>REAL)。在使用$$、$1等引用值,YACC会自动使用联合的适当字段。

 

使用lex&yacc实现一个xml解析器

lex & yacc  可以使用的地方特别多,例如语法解析, 设计自定义的编程语言,解析配置文件等等。下面就给出一个简单例子的范例。

假设我们有一套成熟的游戏系统,这套系统服务端跟客户端都通过c编写,通信方式直接采用c  struct + socket 方式。

有一天我们的游戏系统需要扩展给第3方公司使用,而第3方公司采用flash开发客户端,他们要求我们提供给他们的接口都采用标准的xml格式协议。

这时我们就自然会想到要编写一个xml 与c struct相互转换的接口程序。

例如我们有一个接口的c struct定义是这样的:

struct request

{

   int delay;

   int pid;

   int threadid;

  char ip [20];

  int usetime;

}

而我们接收第3方公司开发的协议格式要求如下:

<?xml version="1.0"?>

<request >

 <delay>5</delay>

 <serviceinfo pid="667" threadid="554" ip="192.168.5.55" usetime="77"/>

</request>

这时我们需要将xml 与c stuct进行相互转换,怎么转换呢,我们可以采用配置文件的方式,

配置好每一个要转换的c struct的转换成xml的格式,或xml转换回struct的格式,我们可以设计上面例子要转换的配置文件如下:

<?xml version="1.0"?>

<request >

 <delay>int</delay>

 <serviceinfo pid="int" threadid="int" ip="string" usetime="int"/>

</request>

 

当我们收到一串xml协议时,我们就可以根据我们配置的格式进行每个字段的解析,并按照格式要求转换为c struct。

 

这时我们在编写程序之前就得先自行解析我们每一个xml的结构,为了方便阐述,xml里面的数据类型只支持 int ,string,double

我们采用lex & yacc 来解析xml方式配置的的结构,具体步骤如下:

 

1 先编写好需要的数据结构文件: xmlstruct.h


struct XMLNODEATTR

{

 char * attr_name;           // 属性名字

 char * attr_value;           // 属性的数据类型

 struct XMLNODEATTR* next; //指向下一个属性

} ;

struct XMLNODE

{

 struct XMLNODE* parent;  //指向父节点

 struct XMLNODE* child;   //指向第1个孩子节点

 struct XMLNODE* next;   //指向下一个兄弟节点

 struct XMLNODEATTR* attrs; //指向节点属性

 short isbasenode;  //是否叶子节点 0/否,1/是

 

 char * value_type;  //节点的数据类型,当不是叶子节点时,此字段忽略

 char * node_name; // 节点名

};

2  编写词法分析文件 parsexml.l

%{

#include "y.tab.h"

#include "xmlstruct.h"

%}

%%

"<?xml version=\"1.0\"?>" {

       return XMLHEADER;

        }

"int" |

"string" |

"double" { yylval.szname=strdup(yytext); return TYPE; }

[A-Za-z][A-Za-z0-9]* { yylval.szname=strdup(yytext); return NAME;}

"</"  |

"/>"  {  yylval.szname=strdup(yytext); return NODEENDTAG; }

"=" |

"<" |

">"    { return yytext[0]; }

[ \t] ;  /* ignore white space */

\n     ;

\"      ;

. { yyerror("unknown char"); return 0; }

%%

 

2  编写语法分析文件 parsexml.y

 

%{

#include "xmlstruct.h"

#include "stdio.h"

struct XMLNODE *pCurNode=NULL;

void printXml(struct XMLNODE *pCurrentXml);

%}

%union {

 char * szname;

 struct XMLNODEATTR * node_attr;

 struct XMLNODE * xml_node;

}

%token <szname> NAME

%token <szname> TYPE

%token <szname> NODEENDTAG

%token XMLHEADER

%type <node_attr> attr

%type <node_attr> attr_list

%type <xml_node> node_begin

%type <szname> node_end

%type <xml_node> node_novalue

%type <xml_node> node

%type <xml_node> node_list

%%

xml: xmlhead node   { printf("xml\n");  printXml($2); }

 ;

xmlhead: XMLHEADER    { printf("xmlheader\n"); }

 ;

node_list: node          { $$=$1  ;   printf("node_list\n"); }

 |  node  node_list  { $1->next=$2;  $$=$1; printf("node_list1\n"); }

 ;

node : node_begin TYPE node_end             {   $$=$1  ;

            $$->isbasenode=1;

            $$->value_type=strdup($2);

            printf("node1 type=%s\n", $2);

           }

 |      node_novalue      { $$=$1  ;   printf("node2\n"); }

 |      node_begin node_list node_end  { $$=$1  ; $$->child=$2;

            pCurNode=$2;  while(pCurNode) {pCurNode->parent=$1;pCurNode=pCurNode->next;}

            printf("node3\n");

           }

 ;

node_novalue: '<' NAME attr_list NODEENDTAG {

            $$=(struct XMLNODE *) malloc(sizeof(struct XMLNODE)) ;

            $$->parent=NULL;$$->child=NULL;$$->next=NULL;$$->attrs=NULL;$$->isbasenode=0;$$->value_type=NULL;

            $$->node_name=strdup($2) ;

            $$->attrs=$3 ;

            $$->isbasenode=1;

            $$->value_type=NULL;

            printf("node_novalue nodename=%s\n", $2);

           }

 ;

node_begin: '<' NAME attr_list '>'  {   $$=(struct XMLNODE *) malloc(sizeof(struct XMLNODE)) ;

          $$->parent=NULL;$$->child=NULL;$$->next=NULL;$$->attrs=NULL;$$->isbasenode=0;$$->value_type=NULL;

          $$->node_name=strdup($2) ;

          $$->attrs=$3 ;

          printf("node begin1=%s\n", $2);

         }

 |       '<' NAME '>'   {

          $$=(struct XMLNODE *) malloc(sizeof(struct XMLNODE)) ;

          $$->parent=NULL;$$->child=NULL;$$->next=NULL;$$->attrs=NULL;$$->isbasenode=0;$$->value_type=NULL;

          $$->node_name=strdup($2) ;

          printf("node begin2=%s\n", $2);

         }

 ;

node_end:  NODEENDTAG NAME '>'    { $$=strdup($2);  printf("node end=%s\n", $2); }

 ;

attr_list: attr          { $$=$1  ;  printf("attr name=%s,type=%s\n",$1->attr_name,$1->attr_value); }

 |  attr attr_list   { $1->next=$2;  $$=$1;  printf("attr1\n"); }

 ;

attr: NAME '=' TYPE  { $$=(struct XMLNODEATTR *) malloc(sizeof(struct XMLNODEATTR));  $$->next=NULL;

       $$->attr_name=strdup($1); $$->attr_value=strdup($3);

       printf("%s = %s\n", $1,$3);

       }

 ;

%%

void printXml(struct XMLNODE *pCurrentXml)

{

 if(NULL==pCurrentXml)

 {

  printf("NULL XML! \n");

  return ;

 }

 printf("Node:%p, name=%s,isbasenode=%d ",pCurrentXml,

         pCurrentXml->node_name,pCurrentXml->isbasenode );

 if(pCurrentXml->value_type !=NULL)

 {

  printf("type=%s",pCurrentXml->value_type);

 }

 printf("\n");

 if(pCurrentXml->attrs !=NULL)

 {

  struct XMLNODEATTR* pAttrNext=pCurrentXml->attrs; 

  printf("Node:name=%s,Attr:",pCurrentXml->node_name);   

  while( pAttrNext !=NULL )

  {

   printf("%s=%s,",pAttrNext->attr_name,pAttrNext->attr_value);

   pAttrNext=pAttrNext->next;

  }

  printf("\n");

 }

 

 if(pCurrentXml->child !=NULL)

 {

  struct XMLNODE *pNode=pCurrentXml->child;

  while( pNode !=NULL )

  {

   printXml(pNode);

   pNode=pNode->next;

  }

 }

}

3 编写main : main.c

#include <stdio.h>

extern int yyparse();

extern FILE *yyin;

int nCount=1;

int yywrap()

{

 return 1;

}

void yyerror(char *s)

{

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

}

int main()

{

 yyin = fopen("test.xml", "r");

 if (yyin == NULL)

 {

  printf("open file error!\n");

 }

 else

 {

  yyparse();

 }

 return 0;

}

 

lex  parsexml.l  生成 lex.yy.c

yacc -d parsexml.y 生成 y.tab.h y.tab.c

cc -o test lex.yy.c y.tab.c main.c  

找一个xml结构体放入test.xml    

运行test  无论xml多么复杂,嵌套还非嵌套都会将xml的每一个节点,节点的属性名,属性类型,节点的类型 等结构准确无误地打印输出。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  yacc xml struct null list token
相关文章推荐