Jackiesteed

www.github.com/jackiesteed

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

承接上一篇日志, 这一次实现的是创建NFA.

输入是正则表达式对应的解析树(一个二叉树).

输出是对应的NFA(一个有向图).

思路也是递归实现, 对于一个树节点, 用两个子节点创建对应的NFA, 然后再根据树节点的类型将两个子NFA拼接起来.

使用到的数据结构:

struct State
{
    int code; //状态码, 一般是自动机中边上的字符, 特殊情况是S(开始), T(结束), FAKE(伪节点,导出空转移的边)
    State* out; //出边1
    State* out1; //出边2
    int lastList; //记录上一次所在的链表, 判重用的.
};

struct Frag
{
    State* start; //子NFA的进入节点
    State* end;   //子NFA的出节点
};

State表示状态机里面的状态, 但是这里的State是标记边的, 而实际状态机理论里面的状态在这里是out和out1.

其实无所谓, 怎样表达更方便就怎样使用(从Russ Cox代码里抄来的XD).

Frag表示一个子NFA, 这个NFA的进入节点是start, 出节点是end.

对于解析树里面的一个Node, 就会生成一个Frag.

但是一个子NFA不一定只有一个出节点的, 比如"|"符号, 就会有两个处节点才对, 这样我们需要用一个链表存出节点.

我增加了一个伪节点的概念, 就是会导出ε边的State. 这种状态节点的code设置成FAKE, 让每一个字NFA的出边都指向这个Fake节点, 然后Frag的形式就可以统一了, 即一个入节点, 一个出节点.

实际上Fake节点也是必不可少的, 因为Thompson发明的NFA本来就避免不了ε边.

但是这样就会生成太多没必要的Fake节点, 所以我们还需要Clear一下.

经过Clear之后, 就生成了最终的NFA.

根据解析树节点创建子NFA的过程, 拿"|"节点举个例子.

    case ALT: //"|" 节点, 需要两个Fake State.
        {
            Frag* up = CreateFakeNFA(root->left);
            Frag* down = CreateFakeNFA(root->right);
            Frag* frag = NewFrag();
            frag->start = NewState();
            frag->end = NewState();
            frag->start->out = up->start;
            frag->start->out1 = down->start;
            up->end->out = frag->end;
            down->end->out = frag->end;
            return frag;
        }

创建两个Fake节点, 作为Frag的start和end, 然后start的出边是"|"的两个自己节点生成的Frag, end的入边也是这两个.

Clear Fake节点的操作, 大概就是如果后继节点为一个NULL+一个Fake节点, 那么就用Fake的出边来更新节点自己的出边.

 同事帮我找了一个会使python的正则表达式很慢的例子:

思路有点凌乱, 改天重新写一下.

代码放在googlecode上了: https://code.google.com/p/regex-cpp/ .

 

posted on 2013-05-21 00:21  Jackiesteed  阅读(680)  评论(1编辑  收藏  举报