编译原理 - 词法分析

简介: 编译原理 - 词法分析

词法分析


编译器的前端可以分成如下过程:


=》前端=》词法分析器=》记号=》语法分析器=》抽象语法树=》语义分析器=》中间表示=》

词法分析的任务是将程序员所写的程序切分成记号流。

if (x > 5)
  y = "hello";
else
  z = 1;


经过词法分析器分析之后,会被字符切分为如下内容

IF LPAREN IDENT(x) GT INT(5) RPAREN
  IDENT(y) ASSIGN STRING("hello") SEMICOLON
ELSE
  IDENT(z) ASSIGN INT(1) SEMICOLON EOF


记号


记号的数据结构定义,类似如下代码

enum kind {IF, LPAREM, ID, INTLIT, ...}
struct token
{
    enum kind k;
    char *lexeme;
}


词法分析器的任务:字符流到记号流的转换

  • 字符流:和被编译的语言密切相关(Unicode, ASCII)
  • 记号流:编译器内部定义的数据结构,编码所识别出的词法单元


词法分析器的实现方法


至少两种的实现方案:

  • 手工编码实现法
  • 相对复杂,且容易出错
  • 但是目前非常流行的实现方法
  • GCC,LLVM,…
  • 词法分析器的生成器
  • 可快速原型,代码量少
  • 但是较难控制细节


手工编码实现法


如果我们有<=、<>、<、=、>、>=、>符号,如下图,我们的提取步骤,根据第一个字符,我们可以按照下图步骤读取对应的符号。


其他的标识符转移图和上述的类似。


标识符和关键字


  • 很多语言中标识符和关键字是有交集的
  • 从词法分析的角度看,关键字是标识符的一部分
  • 以C语言为例:
  • 标识符:以字母或者下划线开头,后面跟了零个或者多个字母,下划线或者数字
  • 关键字:if while else


如何识别关键字


  • 在标识符识别的时候,单独加上标识符的识别


  • 关键字表算法


对给定语言中的所有的关键字,构造关键字构成的hash表

对所有的标识符和关键字,先统一按照标识符的转移图进行识别

识别完成之后进一步查询hash表看是否有关键字

通过合理的构造hash表,可以在O(1)时间内完成查询


词法分析器的生成器


正则表达式


  • 对给定的字符集 A = {c1, c2…cn}


  • 归纳定义


空串a是正则表达式

对于任意的c 属于A, c是正则表达式

如果M和N是正则表达式,则一下也是正则表达式

选择 M | N = {M, N}

连接 MN = {mn | m属于M, n属于N}

闭包 M* = {c, M,MM, MMM, … }


如何使用正则表达式?


  • 关键字
  • C语言中的关键字 if while 如何使用正则表达式表示呢 if
  • 表示符
  • C语言中的标识符需要以字母或下划线开头,后面跟零个或者多个字母i,数字或者下划线。 如何使用正则表达式表示呢?


正则表达式语法糖


参见正则表达式文章连接:正则表达式-CSDN博客


有限状态自动机(FA)


输入的字符串=》FA=》{YES, NO}


  • 确定的有限状态自动机:DFA
  • 对任意字符,对多只有一个状态可以转移
  • 非确定的有限状态自动机:NFA
  • 对任意的字符,有多余一个状态可以转移


DFA的实现


可以看成一个有边和节点的有向图;

  • 边上有信息
  • 节点上也有信息


正则表达式到非确定有限状态自动机


  • Thompson算法:正则表达式=》NFA
  • 子集构造算法:NFA=>DFA
  • Hopcroft最小化算法:DFA=>词法分析器代码


Thompson算法


  • 基于对RE的构造做归纳
  • 对基于的RE直接构造
  • 对复合的RE递归构造
  • 递归算法,容易实现


子集构造算法


  • 不动点算法
  • 算法为什么能工运行终止
  • 时间复杂度
  • 最坏情况O(2^n)
  • 但是在实际中不常发生
  • 因为并不是每个子集都会出现
  • ε-闭包的计算:深度优先或者是广度优先


Hopcroft最小化算法


DFA的代码表示


  • 概念上讲,DFA是一个有向图
  • 实际上,有不同的DFA的代码表示
  • 转移表
  • 哈希表
  • 跳表等
  • 取决于在实际实现中,对时间空间的权衡


转移表
nextToken()
state = 0
stack = []
while (state != ERROR)
  c = getChar()
  if (state is ACCEPT)
    clear(stack)
    push(state)
  state = table[state][c]
  
while (state is not ACCEPT)
  state = pop()
  rollback()
z


上述伪代码中的stack 是为了 最长匹配

跳转表
netxToken()
    state = 0
    stack = []
    goto q0
q0:
    c = getChar()
    if (state is ACCEPT)
        clear(stack)
    push (state)
    if (c == 'a')
        goto q1

q1:
  c = getChar()
    if (state is ACCEPT)
        clear(stack)
    push(state)
    if (c == 'b' || c == 'c')
        goto q1
目录
相关文章
clion中cpp文件显示This file does not belong to any project ,code insight features might not work【解决方案】
clion中cpp文件显示This file does not belong to any project ,code insight features might not work【解决方案】
clion中cpp文件显示This file does not belong to any project ,code insight features might not work【解决方案】
|
9月前
|
存储 监控 算法
园区导航系统技术架构实现与原理解构
本文聚焦园区导航场景中室内外定位精度不足、车辆调度路径规划低效、数据孤岛难以支撑决策等技术痛点,从架构设计到技术原理,对该系统从定位到数据中台进行技术拆解。
470 0
园区导航系统技术架构实现与原理解构
|
Kubernetes 应用服务中间件 nginx
使用kind搭建kubernetes
使用kind搭建kubernetes
895 6
|
数据安全/隐私保护
思科模拟器Cisco Packet Tracer 8.2.1注册、下载和安装教程(正确+详细)
本文详细总结了思科模拟器Cisco Packet Tracer 8.2.1注册、下载和安装教程(正确+详细),看这一篇就够啦~
52191 6
思科模拟器Cisco Packet Tracer 8.2.1注册、下载和安装教程(正确+详细)
|
运维 Kubernetes Cloud Native
深入理解云原生架构:从理论到实践
【10月更文挑战第38天】本文将引导读者深入探索云原生技术的核心概念,以及如何将这些概念应用于实际的软件开发和运维中。我们将从云原生的基本定义出发,逐步展开其背后的设计哲学、关键技术组件,并以一个具体的代码示例来演示云原生应用的构建过程。无论你是云原生技术的初学者,还是希望深化理解的开发者,这篇文章都将为你提供有价值的见解和实操指南。
|
机器学习/深度学习 自然语言处理 算法
编译器:原理与技术的奥秘
编译器:原理与技术的奥秘
|
SQL 关系型数据库 数据库
Flask模型关系与复杂查询技巧
【4月更文挑战第16天】本文探讨了Flask中使用SQLAlchemy进行模型关系管理与复杂查询的方法。SQLAlchemy作为ORM工具,简化了数据库操作。模型关系包括一对一、一对多和多对多,通过定义类间关系实现。文章还介绍了join、子查询、聚合函数、分组与排序等查询技巧,并提出了优化查询性能的建议,如创建索引、避免N+1查询、使用分页及预加载关联数据。理解并运用这些技巧能提升Flask应用的开发效率和性能。
|
NoSQL 关系型数据库 MySQL
|
XML 开发框架 Java
《Spring6核心源码解析》已完结,涵盖IOC容器、AOP切面、AOT预编译、SpringMVC,面试杠杠的!
全网首个全面解析Spring6核心源码的专栏,涵盖:IOC容器、AOP切面、声明式事务、AOT预编译和SpringMVC,让你从根本上彻底掌握Spring6核心技术。
850 1
《Spring6核心源码解析》已完结,涵盖IOC容器、AOP切面、AOT预编译、SpringMVC,面试杠杠的!
|
编译器
【cmake】 --- 一个完整的cmake工程示例
【cmake】 --- 一个完整的cmake工程示例
707 0