######################################## Ragel ######################################## Ragel 是一个基于状态机的字符串解析工具 构造状态机 **************************************** Ragel 状态机声明 ======================================== Ragel 的输入文件一般后缀名为 rl,其内容为包含了状态机声明的源文件。Ragel (可能)会在状态机声明的原地生成代码。输入文件中可以包含任意数量的状态机声明,单行的状态机声明以 %% 开头,多行状态机声明被包含到 %%{}%% 中。 Ragel 会自动识别注释或字符串中的 %%,因此无需在其中进行反转义。Ragel 不会进行预编译,因此 include、define 等不会被展开,因此也没法使用 #if 禁用状态机代码。 .. code-block:: ragel #include #include %%{ machine foo; main := ( 'foo' | 'bar' ) 0 @{ res = 1; }; }%% %% write data; int main( int argc, char **argv ){ int cs, res = 0; if ( argc > 1 ) { char *p = argv[1]; char *pe = p + strlen(p) + 1; %% write init; %% write exec; } printf("result = %i\n", res ); return 0; } 上面的代码分别用到了块声明和行声明。功能是检查字符串是否为 foo 或者 bar 具名 Ragel 代码块 ======================================== **machine fs_name;** **machine** 语句被用于声明一个 FSM,其必须位于 FSM 声明块的第一行,如果没有指定 machine,那么它会使用上一个 machine,如果根本没有 machine 定义,将会抛出错误。由于 include,此语句的作用范围可能会扩散到其它文件 ** = ** 声明一个 FSM 表达式。name 之后被其它表达式引用。但是声明本身不会导致生成任何代码。状态机只会在被实例化时才会生成代码,这要求此声明被其它实例化语句引用。 ** := ;** 此语句用于实例化 FSM 语句,将会导致生成一系列代码用来表示 expression。生成的语句根据 name 被放入数据部分。如果被实例化的是 main,那么此语句将会被视为 FSM 的初始状态,并通过 *write init* 写入 cs 变量中。如果没有 main 被实例化,那么最后一个实例化的 FSM 将会作为机器的初始状态。 在执行循环之外,通过将入口点分配到 cs 变量可以将控制移交到其它状态机。在执行循环之内,可以通过 fcall, fgoto 或 fnext 控制执行 **include FsmName "inputfile.rl";** 用于包含另一个状态机, FsmName 和 inputfile.rl 都是可选的,如果两者都指定,那么 inputfile.rl 将会被是为名为 FsmName 的状态机。 **import "inputfile.h";** Ragel 将形式如下的语句视为状态机定义并将其导入: - name ’=’ number - name ’=’ lit_string - ’define’ name number - ’define’ name lit_string Ragel 从当前文件位置搜索导入的文件,额外的路径可以通过 *-I* 选项添加 Ragel 块词法规则 ======================================== 在状态机中,遵顼以下语法规则 - 行注释为以 # 开头 - ``''``, ``""``, ``//``, ``[]`` 被是为字符串的分隔符。其中允许 *\0, \a, \b, \t, \n, \v, \f, \r* 这些转义字符。使用行尾的反斜杠可以将多行语句合并到一行 - ``{}`` 中的代码被视为宿主语言代码,并被视为一个 action。其中遵循宿主语言的词法规则 - 模式 *[+-]?[0-9]+* 被视为整数,只有字母表类型是有符号数时才会出现负数。用于标识优先级的数组总是可正可负的 - 模式 *0x[0-9A-Fa-f]+* 被视为一个十六进制整数 - 模式 *[a-zA-Z_][a-zA-Z_0-9]\** 被视为一个标识符 - access, action, alphtype, getkey, write, machine 和 include 是关键字 - [a-zA-Z\_][a-zA-Z_0-9]\* 被视为关键字 - 空白字符可以用来分割 tokens 基本状态机 ======================================== 基本的机器机是正则表达式,其是构造 FSM 和能够操作的最小单位。 - ``'hello'`` :关联字面量。产生一个状态机来表示单引号包裹的字符序列(大小写不敏感),5 个字符将会产生 6 种状态,其中可以包含转义序列。 ``''i`` 用来指定大小写敏感的序列 - ``"hello"`` :与单引号版本相同 - ``[hello]`` :或是表达式。重复的字符将会被合,并对字符进行排序,上述字符串只产生 e, h, l, o 四个字符的状态机,一共五个状态 - 空串:零长度的状态机,只有一个状态,开始即是结束 - 42 :数字字面量。产生两个状态,并且只有一个过渡。数字可以是十进制或者十六进制,但是必须要在字母表范围内。其最大值和最小值依宿主语言而定。例如,i386 处理器上 short 类型的字母表允许范围为 -32768-32767 - ``/simple_regex/`` :正则表达式,其中 . 代表 any, [] 代表字符集,并允许在字符集之前使用 ^ 取反。同样支持带 i 后缀的大小写敏感版本。Ragel 并不支持特别复杂的正则表达式。 - 'a'..'z' :范围。要求范围大小在字母表内,数字(十进制和十六进制)或者字符都是允许的 - variable_name:查找与 variable_name 相等的状态机定义 - 还包括了一些内建状态机: +--------+----------------------------------------------+ | 状态机 | 作用 | +========+==============================================+ | any | 字母表中的任意字符 | +--------+----------------------------------------------+ | ascii | Ascii 字符 0..127 | +--------+----------------------------------------------+ | extend | Ascii 拓展字符,范围为 -128..127 或者 0..255 | +--------+----------------------------------------------+ | alpha | [A-Za-z] | +--------+----------------------------------------------+ | digit | [0-9] | +--------+----------------------------------------------+ | alnum | [0-9A-Za-z] | +--------+----------------------------------------------+ | lower | [a-z] | +--------+----------------------------------------------+ | upper | [A-Z] | +--------+----------------------------------------------+ | xdigit | 16 进制数组 [0-9A-Fa-f] | +--------+----------------------------------------------+ | cntrl | 控制字符 0..31 | +--------+----------------------------------------------+ | graph | 可视字符 [!-~] | +--------+----------------------------------------------+ | print | 可打印字符 [ -~] | +--------+----------------------------------------------+ | punct | 标点符号 [!-/:-@[-‘{-~] | +--------+----------------------------------------------+ | space | 空白字符 [\t\v\f\n\r ] | +--------+----------------------------------------------+ | zlen | 长度为零的字符串 "" | +--------+----------------------------------------------+ | empty | ^any | +--------+----------------------------------------------+ 正则表达式操作 ======================================== +--------------+--------------------------------------------------+ | 操作符 | 作用 | +==============+==================================================+ | expr | expr | 满足两个状态机中的任一个 | +--------------+--------------------------------------------------+ | expr & expr | 同时满足两个表达式的状态机 | +--------------+--------------------------------------------------+ | expr - expr | 满足第一个状态机而不满足第二个状态机 | +--------------+--------------------------------------------------+ | expr -- expr | 满足第一个状态机而且不包含满足第二个状态机的子串 | +--------------+--------------------------------------------------+ | expr.expr | 前半部分满足 expr1,后半部分满足 expr2 | +--------------+--------------------------------------------------+ | expr* | expr 后跟任意个 expr | +--------------+--------------------------------------------------+ | expr+ | 相当于 expr.expr* | +--------------+--------------------------------------------------+ | expr? | expr 可以出现任意次 | +--------------+--------------------------------------------------+ | expr {n} | expr 出现 n 次 | +--------------+--------------------------------------------------+ | expr {,n} | expr 至多出现 n 次 | +--------------+--------------------------------------------------+ | expr {n,} | expr 至少出现 n 次 | +--------------+--------------------------------------------------+ | expr {n,m} | expr 出现 n...m 次 | +--------------+--------------------------------------------------+ | !expr | 不符合条件的状态机,相当于 any* - expr | +--------------+--------------------------------------------------+ | ^expr | 字符级否定,相当于 any - expr | +--------------+--------------------------------------------------+ 可视化 ======================================== Ragel 可以使用 -V 选项将状态机输出为 Graphviz 支持的格式,因此能够将状态机可视化。 Action **************************************** Ragel 允许用户在状态机状态改变时运行自定义 Action。 Action 的声明类似于: .. code-block:: ragel action ActionName { // 自定义代码 count += 1; } action 需要配合 action 操作符使用: +---------------+-------------------------------+ | 操作符 | 作用 | +===============+===============================+ | expr > action | 进入状态机时执行 Action | +---------------+-------------------------------+ | expr @ action | 离开状态机时执行 Action | +---------------+-------------------------------+ | expr $ action | 在状态机的所有时刻执行 Action | +---------------+-------------------------------+ | expr % action | 在状态机的最终状态执行 Action | +---------------+-------------------------------+ 例如: .. code-block:: ragel main := lower* @{ cout< | 设置下一个要处理的字符 | +---------------+-----------------------------------------------+ | fgoto