前言
通过编译原理的学习,我们知道正规文法中的一个定义式可以转化为一个终止状态只有一个的有穷状态机。而整个正规文法可以等价的表示为一个有穷状态机。而一个有穷状态机又可以转化为一个状态转移图。因此,编写一个词法分析器有两种方法:
- 根据状态转换图进行进行编写,对应的代码应该是一个Switch块;
- 首先实现一个有穷状态机类,根据自己设计的正规文法来定义一个有穷状态机对象,并使用这个有穷状态机来完成词法的分析。
我选择第二种方法,原因有:有穷状态机将操作和元素分离,并且DFA具有完备的形式化定义,实现了DFA之后除了能够用在词法分析,很多较为复杂的协议、算法可以转化为自动机,这时候这个DFA的实现就能派上用场,如:还用在TCP协议的实现上。当然,通过DFA来实现的词法分析器,由于少了硬编码的存在,执行效率上肯定不如第一种方法(当然不确定通过模板元编程能不能解决这个问题)。这实际上是一个为了增加通用性(编程效率)而牺牲运行效率的一个例子。
DFA的定义
先来复习一下DFA的形式化定义。DFA可以由一个五元组 ( Q 、 Σ 、 f 、 S 、 Z ) (Q、\Sigma、f、S、Z) (Q、Σ、f、S、Z)唯一定义。每个元素的定义分别为:
- Q:一个有穷状态集合;
- Σ \Sigma Σ:一个有穷输入状态集合;
- f:一个有Q和
Σ
\Sigma
Σ的笛卡尔乘积到Q的映射的集合。其中的一个元素应该是这样的进行描述的:
( Q i , Σ i ) = Q j w h e r e Q i 、 Q j ∈ Q (Q_i,\Sigma_i)=Q_j \quad where \quad Q_i、Q_j \in Q (Qi,Σi)=QjwhereQi、Qj∈Q
这个二元组表示当状态为Q时,输入为 Σ i \Sigma_i Σi时,当前状态应该转移到 Q j Q_j Qj处。 - S:唯一的一个初态;
- Z:一个终态的集合;
DFA的扩展定义
尽管DFA的定义已经很完备了,但是这样的定义在实际应用中稍显啰嗦。
扩展的状态转移条件
比如定义这样的一个DFA:只有三个状态,初始状态为0,终止状态为(1,2)。输入集合为英文小写字母。状态转移矩阵的自然语言描述为:当输入为a-y时,由状态0转换到状态1;否则,由状态0转换到状态2。这样的一个DFA可以用如下图进行描述。
但是如果按照这样的图进行编码的话,会发现需要使用许多个Case语句或者是If语句。但是实际上我们完全可以用一个函数来控制转移条件。这样DFA定义中的f集合修改为这样一个集合:
f
:
{
Q
i
,
F
i
(
Q
i
)
}
=
Q
j
f:\{Q_i,F_i(Q_i)\}=Q_j
f:{Qi,Fi(Qi)}=Qj
其中
F
i
F_i
Fi是一个返回值为bool的函数,表示当前状态下,如果条件
F
i
F_i
Fi满足,则转移到状态j。另外,在DFA中,对于一个确定的输入,条件F只能有一个使之满足条件。使用扩展后的DFA,上面的状态转移图完全可以等价表达为:
扩展的状态函数F
在最初的DFA中,能用DFA做什么呢?能够知道当前状态是什么,能够分析当前输入元组是否符合DFA的定义,但是当我们想基于状态来做一些事,比如在状态一,输出一个语句“I’am State 1”。就不可能了,为此我们还需要在DFA的定义中新增加一个集合:
B
:
{
Q
i
,
F
i
(
I
n
p
u
t
,
C
u
r
r
e
n
t
S
t
a
t
e
)
}
B: \{Q_i,F_i(Input,Current_State)\}
B:{Qi,Fi(Input,CurrentState)}
其中F_i的第一个参数为导致变化的输入参数,第二个参数为当前状态,并能有任意的输出。这样设计的原因是:通过状态转移图我们可以看到,DFA能够提供给回调函数的信息只有两个,也就是输入参数和当前状态。设计成任意输出的原因是在于一个DFA不能预测使用他的上层函数需要什么类型的输出,因此输出类型只能由上层函数进行定义。
完整的扩展后的DFA定义
扩展状态转移集合和状态函数之后,一个扩展的DFA的形式化定义应该为:
D
F
A
:
(
Q
、
Σ
、
f
、
S
、
Z
、
B
)
DFA:(Q、\Sigma、f、S、Z、B)
DFA:(Q、Σ、f、S、Z、B)
其中的参数含义为:
- Q:一个有穷状态集合;
- Σ \Sigma Σ:一个有穷输入状态集合;
- f:一个由Q和输入参数为 Σ \Sigma Σ,返回参数为bool的函数集合的笛卡尔乘积到Q的映射的集合。
- S:唯一的一个初态;
- Z:一个终态的集合;
- B:一个二元组的集合: { { ( Q i , F ( Σ k , Q i ) } , w h e r e ∃ E k 使 得 { Q j , F ( Σ ) } = Q i 成 立 } \{\{(Q_i,F(\Sigma_k,Q_i)\}, where \quad \exists E_k使得\{Q_j,F(\Sigma)\}=Q_i 成立\} {{(Qi,F(Σk,Qi)},where∃Ek使得{Qj,F(Σ)}=Qi成立}
扩展后的DFA的实现
描述扩展DFA的六元组
对于一个DFA,我们可以采用如下的变量来描述
template<typename T>
class Dfa {
vector<byte> Termin_State;
vector<byte> Valid_States;
byte Start_State;
std::vector<State_Transfer_Tuple<T>> State_Transfer_Matrix;
std::map<byte, State_Callback_Fun<T> > States_Fun;
std::function<bool(T&)> Input_Check;
byte Current_State;
};
其中的自定义类型为:
namespace DFA {
template <typename T>
struct a {
byte Current_State;
std::function<bool(T)> Equation;
byte Next_State;
};
template <typename T>
using State_Transfer_Tuple = a<T>;//对应状态转移集合f
template<typename T>
using State_Callback_Fun =std::function<void* (const T&, byte)> ;
}
仔细想一下,我们这里只定义了一个模板类型T,也就是说我们的DFA实际上仅仅接收一种数据类型的输入。而在DFA的定义中,输入集合
Σ
\Sigma
Σ能够接收不同类型的变量输入。但是在实际设计时我无法轻易的在C++中实现这样的思路。为了简化编程,我们这里将输入集合
Σ
\Sigma
Σ限定成同一个变量类型的子集。另外还需要注意的一点是这里的DFA仅最多支持256个状态。
状态函数F的参数和返回为:void*(T&,byte)
,设计成void*配合Dfa的Run()函数可以实现定义中的任意输出。Input_Check函数则是用于检查输入变量是否位于
Σ
\Sigma
Σ集合中。
DFA的实现
template<typename T>
bool Dfa<T>::Run(const T& Input, void** State_Fun_Result) {
Check_Run_Condion();
if (Input_Check)
if (!Input_Check((T&)Input))
return false;
bool Is_Find_Transfer_Path = false;
for (int i = 0; i < State_Transfer_Matrix.size(); i++)
{
State_Transfer_Tuple<T>& Current_Tuple = State_Transfer_Matrix[i];
if (Current_State == Current_Tuple.Current_State && Current_Tuple.Equation(Input)) {
Current_State = Current_Tuple.Next_State;
typename map<byte, State_Callback_Fun<T>>::iterator It = States_Fun.find(Current_State);
if (It != States_Fun.end()) {
State_Callback_Fun<T> Current_Function = It->second;
*State_Fun_Result = It->second(Input, Current_State);
}
Is_Find_Transfer_Path = true;
break;
}
}
if (find(Termin_State.begin(), Termin_State.end(), Current_State) != Termin_State.end()) {
Is_Termination_State = true;
}
return Is_Find_Transfer_Path;
}
代码看似比较复杂,但实际上的逻辑很简单。来总结一下DFA到底做了哪些事情:
- 检查输入的参数是否符合集合;
- 遍历状态转移矩阵,逐个调用回调函数,如果满足条件则转3;如果遍历完成之后仍没有找到,则返回错误结果,此时第二个参数无意义;
- 置指示变量为有效,检查状态函数F字典是否存在当前状态函数,存在则运行当前状态的函数F;
- 检查是否为终态,如为终态则指终态指示变量为有效;
完整代码
#pragma once
#include<vector>
#include<map>
#include<string>
#include<functional>
#include<algorithm>
#include"VarType.h" //该头文件可替换为 typedef unsigned char byte;
using namespace std;
namespace DFA {
template <typename T>
struct a {
byte Current_State;
std::function<bool(T)> Equation;
byte Next_State;
};
template <typename T>
using State_Transfer_Tuple = a<T>;
template<typename T>
using State_Callback_Fun =std::function<void* (const T&, byte)> ;
}
using namespace DFA;
template<typename T>
class Dfa {
vector<byte> Termin_State;
vector<byte> Valid_States;
byte Start_State;
std::vector<State_Transfer_Tuple<T>> State_Transfer_Matrix;
std::map<byte, State_Callback_Fun<T> > States_Fun;
std::function<bool(T&)> Input_Check;
byte Current_State;
public:
bool Is_Termination_State;
void Set_Termin_State(vector<byte>&);
void Set_State_Fun(std::map<byte, State_Callback_Fun<T> >&);
void Set_Transfer_Matrix(vector<State_Transfer_Tuple<T>>&);
void Set_Start_State(byte);
void Set_Valid_States(vector<byte>&);
void Set_Input_Check(std::function<bool(T&)>);
void Check_Run_Condion();
bool Run(const T&, void**);
void Restart();
};
template<typename T>
void Dfa<T>::Set_Termin_State(vector<byte>& Argv) {
this->Termin_State = Argv;
}
template <typename T>
void Dfa<T>::Set_Transfer_Matrix(vector<State_Transfer_Tuple<T>>& Transfer_Matrix) {
State_Transfer_Matrix = Transfer_Matrix;
}
template <typename T>
void Dfa<T>::Set_State_Fun(std::map<byte, State_Callback_Fun<T> >& a) {
States_Fun = a;
}
template <typename T>
void Dfa<T>::Set_Valid_States(vector<byte>& a) {
Valid_States = a;
}
template<typename T>
void Dfa<T>::Set_Start_State(byte a) {
Start_State = a;
Current_State = Start_State;
}
template<typename T>
void Dfa<T>::Set_Input_Check(std::function<bool(T &)> a) {
Input_Check = a;
}
template<typename T>
void Dfa<T>::Check_Run_Condion() {
if (!Termin_State.size())
throw new exception("No Termination State!\n");
if (!Input_Check)
throw new exception("No Input Check!\n");
if (!Valid_States.size())
throw new exception("No States!\n");
if (!Input_Check)
throw new exception("No Input Check!\n");
}
template<typename T>
bool Dfa<T>::Run(const T& Input, void** State_Fun_Result) {
Check_Run_Condion();
if (Input_Check)
if (!Input_Check((T&)Input))
return false;
bool Is_Find_Transfer_Path = false;
for (int i = 0; i < State_Transfer_Matrix.size(); i++)
{
State_Transfer_Tuple<T>& Current_Tuple = State_Transfer_Matrix[i];
if (Current_State == Current_Tuple.Current_State && Current_Tuple.Equation(Input)) {
Current_State = Current_Tuple.Next_State;
typename map<byte, State_Callback_Fun<T>>::iterator It = States_Fun.find(Current_State);
if (It != States_Fun.end()) {
State_Callback_Fun<T> Current_Function = It->second;
if (State_Fun_Result)
It->second(Input, Current_State);
else
*State_Fun_Result = It->second(Input, Current_State);
}
Is_Find_Transfer_Path = true;
break;
}
}
if (find(Termin_State.begin(), Termin_State.end(), Current_State) != Termin_State.end()) {
Is_Termination_State = true;
}
return Is_Find_Transfer_Path;
}
template <typename T>
void Dfa<T>::Restart() {
Current_State = Start_State;
Is_Termination_State = false;
}
总结
在实现本次DFA的过程中,还是犯了很多错误,总结如下:
- 一定要注意头文件中不能存在有函数的定义;
- 在c++ 11中新增加的
std::function
和std:bind
很好的解决了不同回调函数之间的问题。在了解到这个知识点前,曾经常使用函数指针来实现函数的回调。但是函数指针不支持模板,同时对于类中的非静态成员函数编译器也不支持自主插入this指针,仍旧记得这个问题我折腾了整整半天下午; Run
函数的void**参数似乎可以用可变模板参数来实现,不这样做的原因是心态已经被std::function
搞蹦了…- 经过这次折腾之后,似乎明白了C++的模板的实现是在编译期通过类型推导来实现的,和运行时毫无关系…
- 不得不说,C++的模板报错是真的艹蛋…