南京邮电大学编译原理——实验一:自己写一个词法生成器,第一步:一个确定有穷状态机的实现(DFA)

前言

通过编译原理的学习,我们知道正规文法中的一个定义式可以转化为一个终止状态只有一个的有穷状态机。而整个正规文法可以等价的表示为一个有穷状态机。而一个有穷状态机又可以转化为一个状态转移图。因此,编写一个词法分析器有两种方法:

  1. 根据状态转换图进行进行编写,对应的代码应该是一个Switch块;
  2. 首先实现一个有穷状态机类,根据自己设计的正规文法来定义一个有穷状态机对象,并使用这个有穷状态机来完成词法的分析。

我选择第二种方法,原因有:有穷状态机将操作和元素分离,并且DFA具有完备的形式化定义,实现了DFA之后除了能够用在词法分析,很多较为复杂的协议、算法可以转化为自动机,这时候这个DFA的实现就能派上用场,如:还用在TCP协议的实现上。当然,通过DFA来实现的词法分析器,由于少了硬编码的存在,执行效率上肯定不如第一种方法(当然不确定通过模板元编程能不能解决这个问题)。这实际上是一个为了增加通用性(编程效率)而牺牲运行效率的一个例子。

DFA的定义

先来复习一下DFA的形式化定义。DFA可以由一个五元组 ( Q 、 Σ 、 f 、 S 、 Z ) (Q、\Sigma、f、S、Z) (QΣfSZ)唯一定义。每个元素的定义分别为:

  • 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)=QjwhereQiQjQ
    这个二元组表示当状态为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ΣfSZB)
其中的参数含义为:

  • 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)},whereEk使{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到底做了哪些事情:

  1. 检查输入的参数是否符合集合;
  2. 遍历状态转移矩阵,逐个调用回调函数,如果满足条件则转3;如果遍历完成之后仍没有找到,则返回错误结果,此时第二个参数无意义;
  3. 置指示变量为有效,检查状态函数F字典是否存在当前状态函数,存在则运行当前状态的函数F;
  4. 检查是否为终态,如为终态则指终态指示变量为有效;

完整代码

#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::functionstd:bind很好的解决了不同回调函数之间的问题。在了解到这个知识点前,曾经常使用函数指针来实现函数的回调。但是函数指针不支持模板,同时对于类中的非静态成员函数编译器也不支持自主插入this指针,仍旧记得这个问题我折腾了整整半天下午;
  • Run函数的void**参数似乎可以用可变模板参数来实现,不这样做的原因是心态已经被std::function搞蹦了…
  • 经过这次折腾之后,似乎明白了C++的模板的实现是在编译期通过类型推导来实现的,和运行时毫无关系…
  • 不得不说,C++的模板报错是真的艹蛋…
  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
编译原理中的正则定义常量是指使用正则表达式来描述编译原理中所需的常量。正则表达式是一种定义字符串模式的强大工具,它可以用来匹配、搜索、替换字符串。在编译原理中,正则表达式被广泛应用于词法分析阶段,用来定义词法单元的模式。 在编译器中,常量是指程序中固定不变的值,例如整数、浮点数、字符串等。通过编正则表达式来定义常量,可以方便地在词法分析中将它们识别出来,并生成对应的词法单元。正则表达式中的特殊符号和操作符可以用来描述常量的模式,例如使用\d+匹配整数,使用[0-9]+(\.[0-9]+)?匹配浮点数,使用\".*\"匹配字符串等。 编译原理中的正则定义常量编译原理的具体步骤如下: 1. 分析语言中常量的模式和规律,确定需要定义的常量类型。 2. 使用正则表达式来描述每个常量类型的模式,使用特殊符号和操作符来表示常量的组成部分和限制条件。 3. 将每个常量类型的正则表达式编译成有限自动(DFA)或非确定有限自动(NFA)。 4. 在词法分析阶段,将输入的源代码按照正则表达式定义的常量类型进行分割和识别,生成对应的词法单元。 5. 将识别出的词法单元作为输入传递给下一个编译阶段,如语法分析、语义分析等。 总之,编译原理中使用正则定义常量的方法可以方便地识别和处理语言中的常量,并为后续的编译工作提供基础。通过适当定义和使用正则表达式,可以提高编译器的效率和准确性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值