baikaishiuc的博客  推荐博客
该博客的主人很懒,什么都没有留下。
我的信息
最新日志
日志分类
最新评论
我的相册
日历
我的日志
时间: 2008.06.15 14:05:00 
标签:  
    在文章的开头,我们先介绍下vczh这个人。以后简称小v。
    vczh广东汕头人士,姓v,名czh,芳龄21。初从文,三年不中。遂习武,校场发一矢,中鼓吏,逐之出。后学医,有所成。自撰一良方,服之,卒。
    07年11月份见到小v,惊为天人,遂发奋图强。
   
    词法分析是编译器设计的首要步骤,而正则表达式引擎又非常的适合进行词法分析,这也就是为什么我当时看了小v的文章后,决定自己写一个正则表达式引擎。其实小v的文章已经解释的非常清楚了,我实在想不出哪里还需要补充的,唯一需要改正的可能就是小v大错没有,小错不少的毛病。后来问起他本人,他也说都是一气呵成的,没有仔细看,就放上来了。
    关于他写的文章,大家可以到 这里 去下。
    我暂时不会把代码放上,原因主要有二
    1. 我现在暂时生成到DFA阶段,之所以急着写这篇文章,是因为期末考试实在太无聊了。没有心情写程序。而扩展正则表达式我觉的不难,主要是因为小v已经把算法给出来了。只要你一开始就考虑到纯正则表达式的需要和扩展正则表达式的需要,那么后面写来就可以事半功倍。另外小v的文章中已经给出了Status和Edge的数据结构的声明了。
    2. 最近发现一本叫OOPC的奇书,看了不胜唏嘘,发现自己以前写的东西瞬间贬值的犹如太祖登基前的法币,所以想在7月以前在重构一次。
   
    我写一些自己的感想吧,在做的过程中思考的问题,小v使用的是C++,我用的是C,所以我对他所写的东西的理解不可避免的造成偏差。但这种偏差使我不断的思考和反思。
   
    假如我们要写正则表达式引擎那么我们必须得先决定,我们遵守哪方面的规则,现在的规则一抓一大把。但主要有两种一种是POSIX另一种是.NET。POSIX自己去STFW,.NET的自己去找MSDN~~~。我也选择了遵守MSDN的大部分规定。然后大家在自己整理出需要的,这里有另一个人整理出来的,百度真是个好东西:-),然后大家可以去掉跟小v的文章对不上的,因为作为学习的话,最好对应着来,有些别的东西可以以后填加进去。
    因为我是看了小v的文章,所以我们以他的文章为主。

======================================================================================
    首先,我们对照规则,纯正则表达式规则有

       1.   字符集合
       2.   并联
       3.   串联
       4.   重复
       5.   表达式引用

这里指出一个问题,那就是表达式引用属于纯正则表达式部分,在使用正则表达式到树的转换时,专门使用一个栈保存表达式命名的节点,然后在引用某个正则表达式的时候,把这个节点接到它的子树上,就可以了。

    接着是扩展正则表达式部分

       1.   正向预查
       2.   反向预查
       3.   匿名捕获
       4.   命名捕获
       5.   命名检查
       6.   边界
       7.   非贪婪重复

    这里指出一下,当我们使用.NET规则的时候可能会碰到\B和\b这样的字符边界匹配,这要把这两种字符边界匹配转换成正向预查和反向预查。
   
    现在我们就得出了我们所要的数据结构了

·字符集合:一个范围的列表

·串联:子树列表

·并联:子树列表

·重复:最小次数,最大次数,是否无限,是否贪婪,子树

·左边界

·右边界

·功能: 子树

          功能(正常、匿名捕获、正向预查、反向预查)

          描述(表达式命名、表达式引用、命名捕获、命名检查)

              名字

    我自己对比小v的原文稍微改动了一点,在重复后多加了子树,和是否贪婪,那个子树他是写漏了,是否贪婪,我不清楚他是怎么想的。转换成C语言的结构就是:

struct reg_node_
{
    enum
    {
        char_class,                         // 字符集合
        concat,                             // 串联
        alter,                              // 并联
        repetition,                         // 重复
        lbound,                             // 左边界
        rbound,                             // 右边界
        func                                // 功能
    } type;

    union
    {
        vector  char_class;                 // vector元素类型:struct { begin, end } range
        vector  concat;                     // vector元素类型:struct reg_node_;
        vector  alter;                      // vector元素类型:struct reg_node_;
        struct
        {
            reg_node    child_tree;         // 子树
            uint16      min_times;          // 最小重复次数
            uint16      max_times;          // 最大重复次数
            bool        is_infinite;        // 是否无限
            bool        is_greed;
        } repetition;
        struct
        {
            reg_node    child_tree;         // 子树
            enum
            {
                normal,                     // 正常
                anonymous,                  // 匿名捕获
                positive,                   // 正向预查
                negative                    // 逆向预查
            } action;
            enum
            {
                expression_name,            // 表达式命名
                expression_refer,           // 表达式引用
                name_trap,                  // 命名捕获
                name_check                  // 命名检查
            } descrip;
            String      name;               // 名字
        } func;
    } data;
};

    大概就是上面这样没错了。这里我讲下为什么把正则表达式转换成树,再转换成ENFA的好处,我的感觉是非常有利于数据之间的传递。让数据的格式变的非常的标准,而有利于模块化。你假如试过没有用中间这个树就直接根据正则表达式转换ENFA的经验,那么感触会更加的真实点……,我就写过,噩梦的日子~~~。

    写到这里真的不知道该怎么写了,小v的文章真的解释的很清楚了。我讲点自己的看法吧。

    1.   非常鄙视文章中那句,回想一下我们小时候……

    2.   不推荐使用

          ·消除左递归

          ·递归下降分析法

         因为我感觉这需要了解编译器的不少知识。而且标准的中缀到后缀的转换已经够用了。不该节外生枝,但假如你做过编译器那么我前面的反对无效。

    3.   在

        struct Status

        {

        vector<Edge*> InEdges;

        vector<Edge*> OutEdges;

        bool FinalStatus;

        };

    我在这里没有使用bool FinalStatus,而是改成了String FinalStatus,在声明了全局变量

        String g_FinalStatus = L"FinalStatus";

    之所以这样做,是因为我不想在多花4个字节的空间,假如状态不是终结状态,那么FinalStatus指向的应该是NULL,否则就是终结状态,另外这种办法在做词法分析的时候有不少的好处(我自以为),比较容易处理多个终结状态,和规则优先的问题。

    4. 貌似又一处笔误的问题,那就是Edge结构中XXX描述的该是字符范围,另外的YYY该用来描述功能边。

    5. 另外就是关于求字符集合的问题也该不难,又有错别字。大家找到那一句 -- 然后我们要构造集合Bi使得……,该是Bj。 字符集合我使用了链表来实现,通过不停的比对,找到Bj的最少解。

    6. 接下去也就没什么问题了,扩展正则表达式不难,因为小v的算法已经给了出来了。我发现的不少问题又都是跟数据结构有关的。我个人感觉到了最后,时间的花费主要都消耗在了函数调用上。所以大家看下LINUX内核中关于链表的使用,把用的较多的函数改成inline。另外迭代器这个概念是我突然想通的,但是用到了很多的类型转换。幸亏整型之间的转换不比,整形和浮点型的转换……。


   我使用的数据结构的库是自己写的,不算上他们,那么关于正则表达式也有4个文件:
   
    1.   dfa.h
    2.   dfa.c
    3.   regxp.h
    4.   regxp.c

    消耗掉了3000行代码量吧,最后估计的补充完,大约要3500,一个月该可以完成了。
作者 baikaishiuc  评论() |  人气() | 引用()  | 推荐 | 问题日志 | 收藏到网摘 | 返回首页