文章目录
词法分析器
词法分析器的实现参考了 cppreference翻译阶段
词法分析分为以下两个阶段
- 字符,注释及字符串预处理
- 符号处理
字符,注释及字符串预处理
字符处理
为了解决转义字符可能对编译产生的影响,所以先进行一次处理,只保留如下四类字符
- 保留10个数字字符(
0-9
) - 26个小写字母(
a-z
),26个大写字母(A-Z
) - 29个标点字符
_ { } [ ] # ( ) < > % : ; . ? * + - / ^ & | ~ ! = , \ " '
- 换行符
\n
除此之外的所有符号均视为不合法字符,报错 UNAVALIABLE CHARACTER
。
注释和字符串处理
首先先对三类内部无规则的代码进行处理:
- 单行注释,由
//
开始,由回车符\n
终止 - 多行注释,由
/*
开始,由*/
终止 - 字符串,由
“
开始,由”
终止 - 字符,由
‘
开始,’
终止
其结构较为简单,容易构造一个自动机求解,处理时将单/多行注释用一个空格代替,字符串用一个特殊字符代替,字符用另一个特殊字符代替,然后存入常量区。
但是其中有一个较难操作的地方,即上述四类状态中可能出现转义字符,因此有如下转义字符形式:
- 由
\
开始,接下来跟若干位数字 - 由
\
开始,接下来跟一位其他字符
当进入转义字符状态时,按照原来的文本读入并存储,且不会在自动机上进行转移。需要说明的是,因为转义字符只可能在字符或者字符串类型中出现,所以不必对其进行转化,只要按照字符串格式存储下来即可,其具体的含义不由词法分析器进行解析,所以只要在读取到 \
时控制其后一位不会使自动机发生状态转移即可。
构造自动机如图所示:
- 0 表示初状态
- 1 表示前缀
/
- 2 表示前缀
//
- 3 表示前缀
/*
- 4 表示前缀
/*......*
- 5 表示前缀
'
- 6 表示前缀
"
为了减少状态,转移字符的吞字符操作单独处理。
方便起见,先在待处理串后方插入一个换行符 \n
,所以到本步骤结束时:
- 若停留在0状态中,说明所有注释及字符串都得到匹配,合法;
- 若停留在2,3,4说明处于注释区中,合法;
- 若停留在5,6状态,说明有未匹配的引号,不合法,返回
UNMATCHED DELIMITER
; - 因为在末尾插入了一个
\n
所以不可能停留在1状态。
实现
因为上述两种操作可以一起进行,所以通过一趟遍历进行处理:
string filterResource(const string &s){
int u=0,v=0,line=1;
string ret="",tmp="";
for(int i=0;i<(int)s.size();++i){
if(s[i]<33&&s[i]>126&&s[i]!='\n'){
showError(0,line);//unavaliable character
}else if(s[i]=='`'&&u==0){
showError(0,line);//unavaliable character
}else if(s[i]=='\\'){
tmp.push_back(s[i]);
if(++i<(int)s.size())tmp.push_back(s[i]);
}else{
tmp.push_back(s[i]);
char c=s[i];
switch(u){
case 0:
if(c=='/')v=1;
if(c=='\'')v=5;
if(c=='\"')v=6;
break;
case 1:
if(c=='/')v=2;
else if(c=='*')v=3;
else v=0;
break;
case 2:
if(c=='\n')v=0;
break;
case 3:
if(c=='*')v=4;
break;
case 4:
if(c=='/')v=0;
else if(c=='*')v=4;
else v=3;
break;
case 5:
if(c=='\'')v=0;
break;
case 6:
if(c=='\"')v=0;
break;
default:
assert(0);
}
}
if(v==0){
if(u>=5){
constZone.push_back(tmp);
ret.append(" ` ");
for(auto x:tmp)if(x=='\n'){
++line;
ret.push_back('\n');
}
}else if(u>1){
ret.append(" ");
for(auto x:tmp)if(x=='\n'){
++line;
ret.push_back('\n');
}
}else{
ret.append(tmp);
for(auto x:tmp)if(x=='\n'){
++line;
}
}
tmp.clear();
}
u=v;
}
if(u>=5){
showError(1,line);//unmatched quotes
}else if(u>0){
ret.append(" ");
}
return ret;
}
样例程序
正常样例
- 输入
#include<iostream>
using namespace std;
int main(){
string s="2313\nxi`xixi\"";
int x=0/5;
char c= '\n';
int y = /*just for `test \" \n */ 1;
cout<<x+y<<endl;
cout<<s/*
niu `\n` 12
*/<<endl;
s.push_back(c);
// 牛`
cout<<c<<endl;//`
}
- 输出
#include<iostream>
using namespace std;
int main(){
string s= ` ;
int x=0/5;
char c= ` ;
int y = 1;
cout<<x+y<<endl;
cout<<s
<<endl;
s.push_back(c);
cout<<c<<endl;
}
在第六行加入非法字符
- 输入
#include<iostream>
using namespace std;
int main(){
string s="2313\nxi`xixi\"";
int x`=0/5;
char c= '\n';
int y = /*just for `test \" \n */ 1;
cout<<x+y<<endl;
cout<<s/*
niu `\n` 12
*/<<endl;
s.push_back(c);
// 牛`
cout<<c<<endl;//`
}
- 输出
Unavaliable character
on line: 6
不进行完全匹配
- 输入
#include<iostream>
using namespace std;
int main(){
string s="2313\nxi`xixi\"";
int x=0/5;
char c= '\n';
int y = /*just for `test \" \n */ 1;
cout<<x+y<<endl;
cout<<s/*
niu `\n` 12
*/<<endl;
s.push_back(c);
// 牛`
cout<<c<<endl;//`
} "
- 输出
Unmatched
on line: 16
符号处理
字符分类
先按照ascii码对所有已有字符进行分类:
- 数字
digit (48~57)
- 字母
letter (65~90, 95,97~122)
, 即将下划线_
视为字母 - 符号
sgn (33~45,47,58~64,91~94,123~126)
- 小数点
dot(46)
- 字符串常量
str (96)
- 空白符:其他所有字符
目标定义
我们需要识别出如下几种数据:
- 保留字
- 变量
(_|letter)(_|letter|digit)*
- 运算符
< <= > >= = + - * / ^ .
等 - 界符
() { } [ ]
- 常量
- 整形
(digit)+
- 浮点型
(digit)*.(digit)+
- 字符/字符串 (已预处理)
- 整形
同理,可以构造一个自动机进行求解。
编码方式
先对所有上述题及的类型进行编码:
<auto,1>
<break,2>
<case,3>
<char,4>
<const,5>
<continue,6>
<default,7>
<do,8>
<double,9>
<else,10>
<enum,11>
<extern,12>
<float,13>
<for,14>
<goto,15>
<if,16>
<int,17>
<long,18>
<register,19>
<return