从今天开始我把我前一段时间用到的状态机工具Ragel的使用方法做一些总结,希望大家斧正!
(如果大家对状态机概念有模糊的话,请参考<<编译原理>>一书,基本上有详尽的介绍)
好闲言少叙,言归正传
Ragel可以从正规表达式生成可执行有限状态机,它可以生成C,C++,Object-C,D,Java和Ruby可执行代码
官方网站:http://www.cs.queensu.ca/home/thurston/ragel/
第一回
Ragel是一个可以生成协议处理代码的工具.
先举个例子,简简单单的几行代码,实现的功能为将一个数字字符串转换成整数:
[Copy to clipboard] [ - ]
CODE:
int atoi( char *str )
{
char *p = str;
int cs, val = 0;
bool neg = false;
%%{ //Ragel 的关键字,用于声明状态机代码段的开始
action see_neg {
neg = true;
}
action add_digit {
val = val * 10 + (fc - '0');
}
main :=
( '-'@see_neg | '+' )? ( digit @add_digit )+
'\n' @{ fbreak; };
# Initialize and execute.
write init; //状态机关键字,这个会再接下来的内容中介绍
write exec noend; //同上
}%% //状态机代码段结束标记
if ( neg )
val = -1 * val;
if ( cs < atoi_first_final )
cerr << "atoi: there was an error" << endl;
return val;
};
比c里面那500多行实现的atoi函数更加高效
上面这段代码,生成的C语言代码如下:
[Copy to clipboard] [ - ]
CODE:
int atoi( char *str )
{
char *p = str;
int cs, val = 0;
bool neg = false;
#line 27 "atoi.c"
{
cs = atoi_start;
}
#line 31 "atoi.c"
{
switch ( cs )
{
case 1:
switch( (*p) ) {
case 43: goto st2;
case 45: goto tr2;
}
if ( 48 <= (*p) && (*p) <= 57 )
goto tr3;
goto st0;
st0:
goto _out0;
tr2:
#line 23 "atoi.rl"
{
neg = true;
}
goto st2;
st2:
p += 1;
case 2:
#line 52 "atoi.c"
if ( 48 <= (*p) && (*p) <= 57 )
goto tr3;
goto st0;
tr3:
#line 27 "atoi.rl"
{
val = val * 10 + ((*p) - '0');
}
goto st3;
st3:
p += 1;
case 3:
#line 63 "atoi.c"
if ( (*p) == 10 )
goto tr4;
if ( 48 <= (*p) && (*p) <= 57 )
goto tr3;
goto st0;
tr4:
#line 33 "atoi.rl"
{ goto _out4; }
goto st4;
st4:
p += 1;
case 4:
#line 74 "atoi.c"
goto st0;
}
_out0: cs = 0; goto _out;
_out4: cs = 4; goto _out;
_out: {}
}
#line 38 "atoi.rl"
if ( neg )
val = -1 * val;
if ( cs < atoi_first_final )
cerr << "atoi: there was an error" << endl;
return val;
};
对应的状态图如下图所示:
正则表达式广泛应用于解析器中。它们通常被用来作为“黑盒”与程序逻辑联系在一起。对正则表达式引擎在执行某些解析工作之后,调用用户自定义行为。加入新的自定义行为,需要重新定义原来的格局,然后粘贴到程序逻辑中。自定义行为越多,正规表达式的优势越小。
Ragel是一个可以根据用户定义的正则表达式或是由正则表达式生成的状态图来生成健壮的,无依赖的可执行代码,包括C,C++,Object-C, Java, Ruby 等等. 可以灵活控制已经生成状态机的变动,利用已经嵌入自定义行为的模式重构扫描器
Ragel基于任何正规语言能被转化为有限状态自动机的原理
基本的使用步骤如下:
首先,根据你的业务流程逻辑与需要,按照ragel提供的语法与关键字编写.rl文件
1. 编写.rl文件, 如下所示: [Copy to clipboard] [ - ] CODE: /* * to parse a string started with “table” or “div” */
#include <stdlib.h> #include <string.h> #include <stdio.h>
%%{ #状态机的名字(必须有一个名字而且必须符合命名规则,和变通的变量声明一样) machine par_str; write data; }%% //函数声明,用于在用户自定义行为中调用,用到了参数的传递,此函数也可以是类成员函数 void printtable(int len) { printf("there is a table,length is:%d\n",len); }
//另外一个函数,功能同上,只是参数传递用的是引用传递 void printdiv(char *p) { printf("%s\n",(*p)); } //主处理函数 void par_str( char *str,int len ) { char *p = str, *pe = str + strlen( str ); int cs; //状态机关键字,用于说明当明状态,以整型值标识(current status的缩写) //状态机自定义行为块 %%{ #调用自定义函数 action see_table { printtable(len); } #invoke prindiv function to show the table information, as same as above action see_div { printdiv(p); } #正则表达式声明,用于在状态机初始化时,按照此规则匹配目标 main := ([t][a][b][l][e]@see_table) ([d][i][v]@see_div)+'\n'; # 初始化 write init; write exec; }%%
if ( cs < par_str_first_final ) fprintf( stderr, "par_str: there was an error\n" ); };
#define BUFSIZE 1024 //主函数,用于测试生成的状态机 int main() { char buf[BUFSIZE]; while ( fgets( buf, sizeof(buf), stdin ) != 0 ) { par_str(buf,10); } return 0; } 接下来,用ragel命令生成目的语言文件 CODE: ragel -o test.cpp test.rl 用代码生成工具,直接生成可执行代码 CODE: rlcodegen -o hello.cpp test.cpp 最后编写对此代码的Makefile,make........
|
|