1 文法文件的定义
语法文件使用的是LL分析方法。LL分析最简便的就是使用递归程序,或构造自动机。 WACC中,采取的硬编码的方式构造自动机。所谓硬编码,是与上章讲的按表格驱动实现自动机相对的。表格驱动往往采用一种二维数组(有可能是一种压缩的)构成状态转换表。程序读取当前符号和当前状态,查询表格来决定下个状态。代码相对比较短,对于不同的问题,仅仅需要修改表格的内容,而不需要修改代码就能解决。硬编码方式就是直接采用代码实现自动机,如果自动机不同,实现代码也会不同。
利用状态变量和case语句便可实现DFA。
给定一个识别标志符自动机如下:
图表 51
伪代码如下:
state = 1
while state ==1 ,2
BeginWhile
switch(state)
BeginSwitch
case 1:
if get character is letter then
state = 2
Else
report it has error
case 2:
if get character is letter or digit then
state = 2
Else
report accept and return
EndSwitch
EndWhile
语法文件的定义很简单,在前言中就讲过:
语法定义:
非终结符 -> 产生式{规约动作};
产生式由0个或多个symbol(符号)组成。在上面的定义的基础上,我们稍作修改,成为下面的一个文法了:
LEFT ARROW RIGHT
其中,Left和Arrow是必须的,Right可空。LEFT,Right定义如下:
LEFT:name
ARROW:conductor
RIGHT:{name}{block}
其中,conductor指箭头,block指规约动作。
绘制出DFA状态图
可以看到,该DFA十分简单,这也就是为什么使用硬编码来实现语法文件的解释的原因。Block(0-1)表示Block可以出现0-1次,Name(0-+)表示Name可以出现0次到多次。
1.1 CInterpreter类
CInterpreter类可以说是用户与系统的一个交互类。用户定义语法文件,便是系统的输入。通过CInterpreter解释语法文件,同时,CInterpreter也根据语法文件生成的LALR(1)分析表生成分析程序。
CIneterpreter类如下定义:
class CInterpreter
{
public:
void InterBlock();
void FormCase(CLanguage &L,FILE* f);
CInterpreter();
virtual ~CInterpreter();
TokenType getToken(CLanguage &L);
std::string string_value;
//取得rules
void getRules(CLanguage &L);
string action_string;
int StartOfReduce ;
/* -1 为 出错,0 ~ StartOfReduce -1 表示shift 或 goto
StartOfReduce ~为ruduce
*/
vector<std::string> ActionVec;
std::map<std::string,int> strMap;
void FormAnsyTable(CIset& Iset, CLanguage &L);
private:
void getDefine(CLanguage &L);
static int value;
void createNewSym(CLanguage &L,string &strValue);
void initLine(vector<int> &line);
// fetch index of symbol input ,if error return -1,else return the index
int fetchIndSym(CSymbol *sy,CLanguage &L);
void error();
void createNewRul(CLanguage &L, RULTYPE & aRule);
inline void checkReduce( CLanguage &L,vector<int> &line,CItem &Item );
};
与解释器有关的几个函数有getToken和getRules函数,其中getToken供getRules函数调用的。FormAnsyTable函数是用来生成分析程序的。string_value变量用来记录每次getToken操作所取得的字符串值。而strMap变量是一个符号表。文法每次定义出来的变量,均交由strMap存储,并且由strMap定义索引值。这些变量名称和索引值可以生成文法所需的Symbol。
1.2 getRules函数
函数getRules并不长,基本是按图表 52所示的自动机的实现的,实现方式也就是swich case的代码。与上面提到的伪代码使用相同的模式,没有太多的技巧。它调用的getToken函数同样采用编码实现自动机,同样没有采用太多的技巧。
void CInterpreter::getRules(CLanguage &L)
{
enum STATE
{ LEFT, ARROW,RIGHT,ACTION} state = LEFT;
TokenType ty;
vector<RULTYPE> RuleSet;
//以下进行反复
RULTYPE aRule;
aRule.rightpart.clear();
getDefine(L);
while ( END != (ty = getToken(L)))
{
switch( state )
{
case LEFT:
//match name, NOT match return error
if ( ty != NAME &&
ty != PRINT)
{
cout<<"LEFT"<<endl;
error();
return ;
}
break;
case ARROW:
//match conductor, NOT match return error
if ( ty != CONDUCTOR )
{
cout<<"CONDUCTOR"<<endl;
error();
return ;
}
state = RIGHT;
break;
case RIGHT:
//match name, NOT match return error
if ( ty != NAME && ty != PRINT && ty !=BLOCK )
{
cout<<"RIGHT"<<endl;
error();
return ;
}
if ( ty == NAME )
{
//get RightPart
aRule.rightpart.push_back(strMap[string_value]);
state = RIGHT;
}
else if ( ty == BLOCK || ty == PRINT)
{
if ( ty == BLOCK )
{
//将action 表插入式中
this->ActionVec.push_back(this->action_string);
if ( PRINT ==
( ty = getToken( L) ) )
{
RuleSet.push_back(aRule);
aRule.rightpart.clear();
}
else
{
cout<<"ty is:"<<ty<<endl;
cout<<"BLOCK"<<endl;
error();
}
}
else if ( ty == PRINT )
{
//在ActionVec 填入空action_string
ActionVec.push_back("");
RuleSet.push_back(aRule);
aRule.rightpart.clear();
}
//set state to LEFT
state = LEFT;
}
break;
default:
cout<<"default"<<endl;
error();
break;
}
}
//根据ruleset 产生Language 的rules
for ( int i = 0 ; i < RuleSet.size(); i++ )
{
//取出aRule 产生rules插入L
createNewRul(L,RuleSet[i]);
}
}
1.3 FormAnsyTable函数
FormAnsyTable函数更简单,它采用fprintf函数向文件写代码。这里不再赘述,请看附录II所列出的源代码。
本章的测试代码有Test9,Test10,Test11。