手搓编译器(1)——词法分析器

本文介绍了如何手动实现一个词法分析器,详细讲解了字符预处理、注释和字符串处理,以及符号处理的步骤。在字符预处理中,处理了转义字符和非法字符的问题;在符号处理中,通过自动机模型进行字符分类和编码。此外,还分析了词法分析器的时间复杂度,并提出了拓展方案。
摘要由CSDN通过智能技术生成

词法分析器

词法分析器的实现参考了 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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值