C++ 构建可组合有向图(Thompson';的扫描生成器构造算法)
我目前正在编写一个基于的扫描生成器,用于将正则表达式转换为NFA。基本上,我需要解析一个表达式并从中创建一个有向图。我通常将有向图存储为邻接列表,但这一次,我需要能够非常有效地将现有的有向图组合成新的有向图。我不能每次读一个新字符时都复制我的邻接列表 我正在考虑创建一个非常轻量级的NFA结构,它不会拥有自己的节点/状态C++ 构建可组合有向图(Thompson';的扫描生成器构造算法),c++,graph,compiler-construction,lexer,directed-graph,C++,Graph,Compiler Construction,Lexer,Directed Graph,我目前正在编写一个基于的扫描生成器,用于将正则表达式转换为NFA。基本上,我需要解析一个表达式并从中创建一个有向图。我通常将有向图存储为邻接列表,但这一次,我需要能够非常有效地将现有的有向图组合成新的有向图。我不能每次读一个新字符时都复制我的邻接列表 我正在考虑创建一个非常轻量级的NFA结构,它不会拥有自己的节点/状态 struct Transition { State* next_state; char transition_symbol; }; struct State { s
struct Transition {
State* next_state;
char transition_symbol;
};
struct State {
std::vector<Transition> transitions;
};
struct NFA {
State* start_state;
State* accepting_state;
};
在这里,我将使用移动语义来明确nfa0和nfa1不再用作独立的NFA(因为我修改了它们的内部状态)
这种方法有意义吗?还是有一个我还没有预料到的问题?如果真的有道理,那么这些州的所有者应该是什么?我还预计我的过渡会出现填充问题。在矢量中打包时,转换的大小将为16字节,而不是9字节(在64位体系结构上)。这是我应该担心的事情,还是这只是事情大计划中的噪音?(这是我的第一个编译器。我如下)汤普森结构的本质是它创建了一个具有以下特征的NFA:
2个| R |
状态,其中|R |
是正则表达式的长度struct State {
std::vector<std::tuple<char, State*>> transitions;
}
(该公式不存储next
中的跃迁数量,前提是我们可以使用哨兵值来表示next[1]
不适用。或者,在这种情况下,我们可以只设置next[1]=next[0];
。记住,它只对ε状态重要。)
此外,由于我们知道NFA中只有2个| R |状态
对象,因此我们可以用小整数替换状态*
指针。这将对可以处理的正则表达式的大小设置某种限制,但是遇到千兆字节正则表达式是非常罕见的。使用连续整数而不是指针也将使某些图算法更易于管理,特别是对子集构造至关重要的传递闭包算法
关于由汤普森算法构造的NFA的另一个有趣的事实是,状态的in度也被限制为2(同样,如果有两个in跃迁,则两者都将是ε跃迁)。这使我们能够避免过早地创建子机的最终状态(如果子机是连接的左侧参数,则不需要最终状态)。相反,我们可以用三个索引来表示子机器:开始状态的索引,以及最多两个内部状态的索引,一旦添加,这些状态将转换为最终状态
我认为以上内容与Thompson最初的实现相当接近,尽管我确信他使用了更多的优化技巧。但值得一读的是Aho,Lam,Sethi&Ullman(“龙之书”)的第3.9节,该节描述了优化状态机结构的方法
与理论简化无关,值得注意的是,除了关键字模式的trie之外,词汇分析中的大多数状态转换涉及字符集而不是单个字符,而且这些字符集通常相当大,尤其是如果词法分析的单位是Unicode码点而不是ascii字符。使用字符集而不是字符确实会使子集构造算法复杂化,但通常会显著减少状态计数。汤普森构造的本质是它创建了具有以下特征的NFA:
2个| R |
状态,其中|R |
是正则表达式的长度struct State {
std::vector<std::tuple<char, State*>> transitions;
}
(该公式不存储next
中的跃迁数量,前提是我们可以使用哨兵值来表示next[1]
不适用。或者,在这种情况下,我们可以只设置next[1]=next[0];
。记住,它只对ε状态重要。)
此外,由于我们知道NFA中只有2个| R |状态
对象,因此我们可以用小整数替换状态*
指针。这将对可以处理的正则表达式的大小设置某种限制,但是遇到千兆字节正则表达式是非常罕见的。使用连续整数而不是指针也将使某些图算法更易于管理,特别是对子集构造至关重要的传递闭包算法
关于由汤普森算法构造的NFA的另一个有趣的事实是,状态的in度也被限制为2(同样,如果有两个in跃迁,则两者都将是ε跃迁)。这使我们能够避免过早地创建子机的最终状态(如果子机是连接的左侧参数,则不需要最终状态)。相反,我们可以用三个索引来表示子机器:开始状态的索引,以及最多两个内部状态的索引,一旦添加,这些状态将转换为最终状态
我认为以上内容与Thompson最初的实现相当接近,尽管我确信他使用了更多的优化技巧
enum class StateType { EPSILON, IMPORTANT };
struct State {
StateType type;
char label;
State* next[2];
};