前言:关于词法分析的基础知识的介绍可以看一下这篇博客,我再累述估计也不会有这篇讲的清楚QAQ。 https://www.cnblogs.com/yanlingyin/archive/2012/04/17/2451717.html    默认大家已经对词法分析有了基本的了解了。

一:下面讨论PL/0语言的词法分析器的单词结构

1、关键字

  关键字(共11个):空格分隔列表如下

  begin  end  if  then  while  do  const  var  call  procedure  odd

2、运算符和界符

  算符和界符(14个):空格分隔列表如下

  +  -  *  /  =  #  <  >  :=  (  )  ,  .  ;

3、标示符

PL/0语言中标示符的定义为:开头可以是下划线或字母,后面可以是下划线、字母或数字。

4、常数

  整形数和浮点数

5、空白符

  PL/0语言中的空白符有:空格符、制表符、换行符,这些空白符在词法分析阶段可以被忽略。

6、注释

PL/0语言中的注释形式为//,可以多行注释(*…*)。

二:PL/0的语言的词法分析器将要完成以下工作

(1) 跳过分隔符(如空格,回车,制表符);

(2) 识别诸如begin,end,if,while等关键字;

(3) 识别非关键字的一般标识符。

(4) 识别常数数字序列。

(5) 识别前面列出的单字符操作符和:=双字符特殊符号。

(6)词法分析器的输出形式(种别,属性值)其中:种别在“2、单词的种别”中进行了定义;

属性值:若单词种别只代表唯一单词,属性值为空;

若单词种别是标识符,为该单词在标识符表中的位置;

若单词种别是常数,属性值为对应常数值。

三:代码实现

测试程序如下(我放置的路径为D:/b.txt,根据自己情况自行修改)

 // PL/0 语法示例程序

 (*
计算1~10的阶乘
多行注释
*) var n, f;
begin
n := ;
f := ;
while n # do
begin
n := n + ;
f := f * n;
end;
call print;// 用于输出结果,假设预先声明
end.

种别码如下(我放置的路径为D:/a.txt,根据自己情况自行修改)

 begin
end
if
then
while
do
const
var
call
procedure
odd
+
-
*
/
=
#
<
>
:=
(
)
,
.
;
(*多行注释*)
//单行注释 27
常数
标识符

实现代码如下

 // pL/0语言词法分析器
#include<bits/stdc++.h>
using namespace std;
struct _2tup
{
string token;
int id;
};
bool is_blank(char ch)
{
return ch == ' ' || ch == ' ';//空格或控制字符
}
bool gofor(char& ch, string::size_type& pos, const string& prog)//返回指定位置的字符
{
++pos;
if (pos >= prog.size())
{
return false;
}
else
{
ch = prog[pos];
return true;
}
} _2tup scanner(const string& prog, string::size_type& pos, const map<string, int>& keys, int& row)
{
/*
if
标示符
else if
数字
else
符号
*/
_2tup ret;
string token;
int id = ; char ch;
ch = prog[pos]; while(is_blank(ch))
{
++pos;
ch = prog[pos];
}
// 判断标示符、关键字
if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_')
{
//保证读取一个单词
while((ch >= '' && ch <= '') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_')
{
token += ch;//追加标示符、关键字
if (!gofor(ch, pos, prog))
{
break;
}
}
// 这里先看做都是其他标示符
id = keys.size(); // 验证是否是关键字
map<string, int>::const_iterator cit = keys.find(token);//根据string类型的token返回int类型的id赋值给cit
if (cit != keys.end())
{
id = cit->second;//此时是关键字,记录他的id
}
}
// 识别常数
else if ((ch >= '' && ch <= '') || ch == '.')
{
while (ch >= '' && ch <= '' || ch == '.')
{
token += ch;
if (!gofor(ch, pos, prog))
{
break;
}
}
id = keys.size() - ;
int dot_num = ;
for (string::size_type i = ; i != token.size(); ++i)
{
if (token[i] == '.')
{
++dot_num;
}
}
if (dot_num > )
{
id = -;
}
}
else
{
map<string, int>::const_iterator cit;
switch (ch)
{
case '-': // - 操作符
token += ch;
if (gofor(ch, pos, prog))
{
if (ch == '-' || ch == '=' || ch == '>') // -- 操作符
{
token += ch;
gofor(ch, pos, prog);
}
}
cit = keys.find(token);
if (cit != keys.end())
{
id = cit->second;
}
break;
case ':':
token += ch;
if (gofor(ch, pos, prog))
{
if (ch == '=') // -- 操作符
{
token += ch;
gofor(ch, pos, prog);
}
}
cit = keys.find(token);
if (cit != keys.end())
{
id = cit->second;
}
break; case '=':
token += ch;
if (gofor(ch, pos, prog))
{
if (ch == '=') // !% %= 操作符
{
token += ch;
gofor(ch, pos, prog);
}
}
cit = keys.find(token);
if (cit != keys.end())
{
id = cit->second;
}
break; case '/': // / 操作符
token += ch;
if (gofor(ch, pos, prog))
{
if (ch == '=') // /= 操作符
{
token += ch;
gofor(ch, pos, prog);
}
else if (ch == '/') // 单行注释
{
token += ch;
++pos;
while (pos < prog.size())
{
ch = prog[pos];
if (ch == '\n')
{
break;
}
token += ch;
++pos;
}
if (pos >= prog.size())
{
;
}
else
{
;
}
id = keys.size() - ;
break;
}
else if (ch == '*') // 注释
{
token += ch;
if (!gofor(ch, pos, prog))
{
token += "\n!!!注释错误!!!";
id = -;
break;
}
if (pos + >= prog.size())
{
token += ch;
token += "\n!!!注释错误!!!";
id = -;
break;
}
char xh = prog[pos + ];
while (ch != '*' || xh != '/')
{
token += ch;
if (ch == '\n')
{
++row;
}
//++pos;
if (!gofor(ch, pos, prog))
{
token += "\n!!!注释错误!!!";
id = -;
ret.token = token;
ret.id = id;
return ret;
}
//ch = prog[pos];
if (pos + >= prog.size())
{
token += ch;
token += "\n!!!注释错误!!!";
id = -;
ret.token = token;
ret.id = id;
return ret;
}
xh = prog[pos + ];
}
token += ch;
token += xh;
pos += ;
ch = prog[pos];
id = keys.size() - ;
break;
}
}
cit = keys.find(token);
if (cit != keys.end())
{
id = cit->second;
}
break;
case '+':
token += ch;
cit = keys.find(token);
if (cit != keys.end())
{
id = cit->second;
}
gofor(ch, pos, prog);
break; case '<':
token += ch;
if (gofor(ch, pos, prog))
{
if (ch == '<')
{
token += ch;
if (gofor(ch, pos, prog))
{
if (ch == '=')
{
token += ch;
gofor(ch, pos, prog);
}
}
}
else if (ch == '=')
{
token += ch;
gofor(ch, pos, prog);
}
}
cit = keys.find(token);
if (cit != keys.end())
{
id = cit->second;
}
break; case '>':
token += ch;
if (gofor(ch, pos, prog))
{
if (ch == '>')
{
token += ch;
if (gofor(ch, pos, prog))
{
if (ch == '=')
{
token += ch;
gofor(ch, pos, prog);
}
}
}
else if (ch == '=')
{
token += ch;
gofor(ch, pos, prog);
}
}
cit = keys.find(token);
if (cit != keys.end())
{
id = cit->second;
}
break;
case '(': // / 操作符
token += ch;
if (gofor(ch, pos, prog)) {
if (ch == '*') // 注释
{
token += ch;
if (!gofor(ch, pos, prog))
{
token += "\n!!!注释错误!!!";
id = -;
break;
}
if (pos + >= prog.size())
{
token += ch;
token += "\n!!!注释错误!!!";
id = -;
break;
}
char xh = prog[pos + ];
while (ch != '*' || xh != ')')
{
token += ch;
if (ch == '\n')
{
++row;
}
//++pos;
if (!gofor(ch, pos, prog))
{
token += "\n!!!注释错误!!!";
id = -;
ret.token = token;
ret.id = id;
return ret;
}
//ch = prog[pos];
if (pos + >= prog.size())
{
token += ch;
token += "\n!!!注释错误!!!";
id = -;
ret.token = token;
ret.id = id;
return ret;
}
xh = prog[pos + ];
}
token += ch;
token += xh;
pos += ;
ch = prog[pos];
id = keys.size() - ;
break;
}
}
cit = keys.find(token);
if (cit != keys.end())
{
id = cit->second;
}
break; case '*':
token += ch;
cit = keys.find(token);
if (cit != keys.end())
{
id = cit->second;
}
gofor(ch, pos, prog);
break; case ',':
case '#':
case '.':
case ';':
token += ch;
gofor(ch, pos, prog);
//++pos;
//ch = prog[pos];
cit = keys.find(token);
if (cit != keys.end())
{
id = cit->second;
}
break; case '\n':
token += "换行";
++pos;
ch = prog[pos];
id = -;
break;
default:
token += "错误";
++pos;
ch = prog[pos];
id = -;
break;
}
}
ret.token = token;
ret.id = id; return ret;
} void init_keys(const string& file, map<string, int>& keys)//读取单词符号和种别码
{
ifstream fin(file.c_str());//.c_str返回的是当前字符串的首地址
if (!fin)
{
cerr << file << " doesn't exist!" << endl;//cerr不经过缓冲而直接输出,一般用于迅速输出出错信息
// exit(1);
}
keys.clear();//清空map对象里面的内容
string line;
string key;
int id;
while (getline(fin, line))//这个函数接收两个参数:一个输入流对象和一个string对象,getline函数从输入流的下一行读取,并保存读取的内容到string中
{
istringstream sin(line);//istringstream sin(s);定义一个字符串输入流的对象sin,并调用sin的复制构造函数,将line中所包含的字符串放入sin 对象中!
sin >> key >> id;//读取里面的字符串每一行一个key id
keys[key] = id;
}
} void read_prog(const string& file, string& prog){//读取代码,并追加到prog上
ifstream fin(file.c_str());
if (!fin)
{
cerr << file << " error!" << endl;
// exit(2);
}
prog.clear();
string line;
while (getline(fin, line))
{
prog += line + '\n';
}
}
int main()
{
map<string, int> keys;
init_keys("D:/Test/a.txt", keys); string prog;
read_prog("D:/Test/b.txt", prog); vector< _2tup > tups;
string token, id; string::size_type pos = ;//size_type属于string标准库,作用可看做相当于unsigned·int
int row = ; _2tup tu;
cout << "------------------" << "要分析的代码如下"<< "---------------" << endl;
cout << prog << endl << endl; // prog += "#"; // 标识终止,其实可以检测 pos 来判别是否终止 int no = ;
cout << "------------------" << "分析的结果如下如下"<< "---------------" << endl;
do
{
tu = scanner(prog, pos, keys, row); switch (tu.id)
{
case -://返回的是错误
++no;
cout << no << ": ";
cout << "Error in row" << row << "!" << '<' << tu.token<< "," << tu.id << '>' << endl;
tups.push_back(tu);
break;
case -:
++row;
// cout << '<' << tu.token<< "," << tu.id << '>' << endl;
break;
default:
++no;
cout << no << ": ";
if(tu.id== || tu.id==){
cout << '<' << tu.id<< "," << tu.token << '>' << endl;
}
else{
cout << '<' << tu.id<< "," << "-" << '>' << endl;
}
tups.push_back(tu);
break;
}
} while (pos < prog.size()); cout << endl << "--------------------------------结果总行数------------"<<tups.size() << endl;
return ;
}

四:运行截图

PL/0语言词法分析器-LMLPHP

看懂代码之后,便可以很容易扩展写出其他语言的词法分析器。

05-06 23:27