PL/0语言
〈程序〉→〈分程序>.
〈分程序〉→ [<常量说明部分>][<变量说明部分>][<过程说明部分>]〈语句〉
<常量说明部分> → CONST<常量定义>{ ,<常量定义>};
<常量定义> → <标识符>=<无符号整数>
<无符号整数> → <数字>{<数字>}
<变量说明部分> → VAR<标识符>{ ,<标识符>};
<标识符> → <字母>{<字母>|<数字>}
<过和说明部分> → <过程首部><分程序>;{<过程说明部分>}
<过程首部> → procedure<标识符>;
<语句> → <赋值语句>|<条件语句>|<当型循环语句>|<过程调用语句>|<读语句>|<写语句>|<复合语句>|<空>
<赋值语句> → <标识符>:=<表达式>
<复合语句> → begin<语句>{ ;<语句>}
<条件> → <表达式><关系运算符><表达式>|ood<表达式>
<表达式> → [+|-]<项>{<加减运算符><项>}
<项> → <因子>{<乘除运算符><因子>}
<因子> → <标识符>|<无符号整数>|(<表达式>)
<加减运符> → +|-
<乘除运算符> → *|/
<关系运算符> → =|#|<|<=|>|>=
<条件语句> → if<条件>then<语句>
<过程调用语句> → call<标识符>
<当型循环语句> → while<条件>do<语句>
<读语句> → read(<标识符>{ ,<标识符>})
<写语句> → write(<标识符>{,<标识符>})
<字母> → a|b|c…x|y|z
<数字> → 0|1|2…7|8|9
需求:为PL/0语言建立一个词法分程序
首先要理解词法分析的过程,选择合适的数据结构存储值,然后理解PL/0语言的词法,可以选择使用if-else判断结构,也可以自己构造一个DFA进行状态转换。
下面进行思路讲解:
1.首先设置了三个队列来存储相应信息:
queue<string> SYM;
queue<string> ID;
queue<string> NUM;
SYM用来存放每个单词的类别,每个关键字一个类别,比如if我们就存储IFSSYM,var我们就存储VARSYM,标识符的类别是IDENT,然后数字的类别是NUMBER,在我们程序进行识别的过程中,边识别边存储。
ID是存储标识符的具体内容,和SYM的存储同步,当SYM存上IDENT时对应的ID中就存储上标识符具体内容。
NUM与ID类似,用来存储数字的值。
选用队列来存储这三种数据主要是为了使用它先进先出的特性,方便最后输出词法分析器识别的结果。
2.设置了两个map结构,键值都是string类型。其中keywords用来存储关键字以及相应的SYM存储的内容。然后SYM_OUT是存储了包括关键字,标识符,数字以及各种运算符边界符的SYM和符号的对应关系,为了方便最后统一输出词法分析的结果。
map<string,string> Keywords;
map<string,string> SYM_OUT;
Keywords["begin"]="BEGINSYM";
Keywords["call"]="CALLSYM";
Keywords["const"]="CONSTSYM";
Keywords["do"]="DOSYM";
Keywords["end"]="ENDSYM";
Keywords["if"]="IFSYM";
Keywords["odd"]="ODDSYM";
Keywords["procedure"]="PROCEDURESYM";
Keywords["read"]="READSYM";
Keywords["then"]="THENSYM";
Keywords["var"]="VARSYM";
Keywords["while"]="WHILESYM";
Keywords["write"]="WRITESYM";
SYM_OUT["ADDSYM"]="+";
SYM_OUT["MINUSSYM"]="-";
SYM_OUT["MULTIPLYSYM"]="*";
SYM_OUT["DIVIDESYM"]="/";
SYM_OUT["EQUALSYM"]="=";
SYM_OUT["GEQSYM"]=">=";
SYM_OUT["BECOMESYM"]=":=";
SYM_OUT["GTRSYM"]=">";
SYM_OUT["LEQSYM"]="<=";
SYM_OUT["LSSSYM"]="<";
SYM_OUT["LEFT_PARENSYM"]="(";
SYM_OUT["RIGHT_PARENSYM"]=")";
SYM_OUT["COMMASYM"]=",";
SYM_OUT["SEMICOLONSYM"]=";";
SYM_OUT["SPOTSYM"]=".";
3.编写了进行字符类型判断的函数:
主要用于截取的第一个字符的判断,只有知道第一个字符是什么我们才能进一步判断这个字符的类型。
其中包括isNumber用来判断该位置的符号是否是数字,isLatter检测这里的符号是否是字母,大小写都可以检测出来,isOperator检测这里的符号是否是运算符,ifBandSymbol检测字符是否是边界符号,比如括号,逗号,分号等等。
//判断该位置的符号是否是数字
bool isNumber(char ch) {
if(ch>='0' && ch<='9') {
return true;
} else {
return false;
}
}
//判断该位置的符号是否是字母
bool isLatter(char ch) {
if((ch>='a'&&ch<='z')||(ch>='A'&&ch<='Z')){
return true;
}
return false;
}
//判断该位置是否是运算符的基本单位
bool isOperator(char ch)
{
if(ch=='+'||ch=='-'||ch=='*'||ch=='/'||ch=='>'||ch=='<'||ch=='='||ch=='#'||ch==':'){
return true;
}
return false;
}
//判断该位置是否是边界符
bool isBandSymbol(char ch)
{
if(ch=='('||ch==')'||ch==','||ch==';'||ch=='.'){
return true;
}
return false;
}
4.对于连续输入的运算符,进行循环判断,根据字符类型来处理,比如说如果我们判断出这个字符是+,就将对应的ADDSYM类型push进SYM队列,如果是-就push MINUSSYM进入SYM队列,这种没有什么歧义的一元运算符到这里就可以判断完毕。但是有一个产生式运算符比较特殊,就是“:=”,因此对于这个符号,我们就要先识别冒号,然后进行判断,如果冒号后面跟着的是=,那么这个符号合法,将BECOMESYM push进SYM队列,否则输出非法字符。同理还有大于等于和小于等于的处理,也类似这样进行判断。
else if(str[i]==':')//如果i位置是 ':',i+1位置不是 '=',那么该符号非法
{
if((i+1)<a&&str[i+1]=='=')
{
SYM.push("BECOMESYM");
i++;
} else printf("(%c,非法字符)\n",str[i]);
}else if(str[i]=='>')
{
if((i+1)<a&&str[i+1]=='=')
{
SYM.push("GEQSYM");
i++;
}else{
SYM.push("GTRSYM");
}
}
5.处理完运算符开始处理边界符,使用switch case进行判断,比如(,对于SYM就push相应的类别,其他边界符都是同理。
case '(':
SYM.push("LEFT_PARENSYM");
break;
6.词法分析过程,首先从文件读取对于的字符串,设置了一个flag判断是否存在一个字符串他的长度大于10。设置两个char类型的数组进行存储对应要分析的字符串单词。第一步去掉开头的空格,将对应的标识i++,分析下面的字符,截取两个空格之间的字符串,存到tempStr数组中,对里面的单词进行判断,首先看一下是否超出10个字符的长度。然后使用选择分支进行判读,第一个字符是字母的话,获取都是字母和数字的串,去keyword的map进行迭代器判断,如果是关键字进行SYM中push相应关键字的操作,不是关键字就是标识符了,进行标识符存储的操作。如果以数字开头,首先判断数字后面跟着的是不是字母,是的话输出非法符号,不能出现以数字开头的标识符。
以运算符开头就按照编写的运算符处理函数来处理,边界符同理,按照SYM对应的值进行push就行。
7.最后main函数里面,初始化两个map,定义字符数组存储字符串,然后进行词法分析函数运行,最后统一输出SYM,ID,NUM中的元素。
while (!SYM.empty())
{
map<string,string>::iterator sym_iter;
sym_iter=SYM_OUT.find(string(SYM.front()));
if(sym_iter!=SYM_OUT.end()){
cout<<""<<SYM.front()<<" "<<sym_iter->second<<endl;
}else{
if(SYM.front()=="IDENT"){
if(!ID.empty()){
cout<<""<<SYM.front()<<" "<<ID.front()<<endl;
ID.pop();
}
}else if(SYM.front()=="NUMBER"){
if(!NUM.empty()){
cout<<""<<SYM.front()<<" "<<NUM.front()<<endl;
NUM.pop();
}
}
}
SYM.pop();
}
这里不是完整的代码实现,有需要的同学可以根据思路尝试写一下