精华内容
下载资源
问答
  • 编译原理课程设计中的一个,作为代码优化的一部分完成循环的查找:根据给定的一段C语言源代码,查找入口语句,确定基本块,构造程序流图,并画出流图,查找必经结点,查找回边和相应的循环!在编译课程设计中是最...
  • 易语言源码编译原理易语言循环首尾配对源码.rar 易语言源码编译原理易语言循环首尾配对源码.rar 易语言源码编译原理易语言循环首尾配对源码.rar 易语言源码编译原理易语言循环首尾配对源码.rar 易语言源码编译...
  • 易语言编译原理,易语言循环首尾配对源码,易语言取剩余堆栈成员数,易语言_词法分析_循环首尾配对,易语言_词法分析_表达式计算
  • 编译原理课设for循环

    2012-01-08 00:06:36
    编译原理课程设计,for循环(简单优先法、输出三地址),代码加课设,功能强,可读性好,程序很小。
  • 编译原理课程设计中的一个,作为代码优化的一部分完成循环的查找:根据给定的一段C语言源代码,查找入口语句,确定基本块,构造程序流图,并画出流图,查找必经结点,查找回边和相应的循环!在编译课程设计中是最...
  • 编译原理易语言循环首尾配对源码
  • 编译原理易语言循环首尾配对源码
  • 编译原理课程设计 WHILE循环语句递归下降法实现 含有完整文档和源代码
  • 编译原理编译原理简单介绍

    万次阅读 多人点赞 2017-05-07 13:27:20
    编译原理简单介绍编译原理简单介绍 什么叫编译程序 翻译程序 编译程序 翻译和编译的区别 编译的过程 词法分析 语法分析 语义分析和中间代码的产生 优化 目标代码生成 编译程序的结构 编译程序总框 表格与表格的管理 ...

    编译原理简单介绍



    什么叫编译程序

    翻译程序

    翻译程序(Translator)是一种程序,其输入是某种语言的一系列语句,而其输出则是另一种语言的一系列语句,二者在逻辑上是等价的。就类似生活中的翻译官一样,把英语翻译成汉语,二者在意思上也是等价的。
    这里写图片描述

    编译程序

    编译程序(Compiler)是一种程序。它把用高级语言写的源程序作为数据接收,经过翻译转换,产生面向机器的代码作为输出。
    这当中代码还可能要由汇编程序或装配程序作进一步加工,得出目标程序,交给计算机执行。
    这里写图片描述

    翻译和编译的区别

    这里写图片描述

    编译的过程

    编译程序的工作过程一般可以分为5个阶段:
    1. 词法分析
    2. 语法分析
    3. 语义分析和中间代码的产生
    4. 优化
    5. 目标代码生成

    词法分析

    词法分析的任务是:输入源程序,对构成源程序的字符串进行扫描和分解,识别出一个个单词(定义符、标识符、运算符、界符、常数)。

    在词法分析阶段的工作中所依循的是语言的语法规则(或称构词规则)。
    描述语法规则的有效工具是正规式有限自动机

    语法分析

    语法分析的任务是:在词法分析的基础上,根据语言的语法规则,把单词符号串分解成各类语法单元(语法范畴)(短语、子句、句子、程序段、程序),并确定整个输入串是否构成语法上正确的程序。

    语法分析所依循的是语言的语法规则。
    语法规则通常用上下文无关文法描述。
    词法分析是一种线性分析,而语法分析是一种层次结构分析。

    语义分析和中间代码的产生

    这一阶段的任务是:对语法分析所识别出的各类语法范畴,分析其含义,并进行初步翻译(产生中间代码)。这一阶段通常包含两个方面的工作。
    1. 对每种语法范畴进行静态语义的检查,例如,变量是否定义、类型是否正确等等。
    2. 如果语义正确则进行中间代码的翻译。

    这一阶段所依循的是语言的语义规则,通常使用属性文法描述语义规则。

    优化

    对于代码(主要是中间代码)进行加工变换,以期能够产生更为高效(省时间和空间)的目标代码 。
    优化的主要方面有:公共子表达式的提取、循环优化、删除无用代码等等。

    优化所依循的是程序的等价变换规则。

    目标代码生成

    这一阶段的任务是:把中间代码(经过优化处理之后的)变换成特定机器上的低级语言代码(绝对指令、可重定位指令、汇编指令
    )。

    编译程序的结构

    编译程序总框

    这里写图片描述

    表格与表格的管理

    编译程序在工作过程中需要保持一系列的表格,以登记源程序的各类信息和编译各阶段的进展状况
    最重要的是符号表,用来等级源程序中出现的每个名字以及名字的各种属性。例如,一个名字是常量名还是变量名,还是过程名;如果是变量名,类型是什么,占多大内存,地址是多少等等。

    编译各阶段均须维持表格并进行表格管理,建表的技术支持是数据结构,表格的分类、结构、处理方法决定于语言及机器,还有优化措施。

    出错处理

    如果源程序有错误,编译程序应设法发现错误,并把有关错误的信息报告给用户。一个好的编译程序:
    1. 全
    最大限度发现错误
    2. 准
    准确指出错误的性质和发生地点
    3. 局部化
    将错误的影响限制在尽可能小的范围内
    4. 自动校正
    若能自动校正错误则更好,但其代价非常高

    源程序中的错误一般分为语法错误和语义错误。
    1. 语法错误
    指源程序中不符合不符合语法(或词法)规则的错误,例如:单词拼写错误、括号不匹配等等。
    2. 语义错误
    指源程序中不符合语义规则的错误,例如:说明错误、作用域错误、类型不匹配等等。
    一般在语义分析时检出来,有的语义错误要在运行时才能检测出来。

    遍 是对源程序或源程序的中间结果从头到尾扫描一次,并作有关的加工处理,生成新的中间结果或目标程序。遍数多了,整个编译程序的逻辑结构就比较清晰,但是会增加输入和输出所消耗的时间。因此,在主存可能的前提下,一般还是遍数少的好。
    分遍的依据:
    1. 源程序的结构
    2. 选用机型的内存大小
    3. 设计目标的技术指标
    4. 参加编译程序人员的数量、素质

    好的编译程序的指标:
    1. 符合语法规则的程序都可执行。
    2. 任何非法的错误都有可能识别,并尽量少的产生连锁反应。
    3. 错误不至于导致系统崩溃。
    4. 可维护和可读性。
    5. 模块化和结构化。

    编译的前端与后端

    概念上我们有时候把编译程序分成编译前端和编译后端。

    编译前端

    前端主要由源语言有关但与目标机无关的那些部分组成,通常包括词法分析、语法分析、语义分析与中间代码的产生,有的代码优化工作也可以包括在前端。

    编译后端

    后端包括编译程序中与目标机有关的那些部分,如与目标机有关的代码优化和目标代码生成等。
    通常后端不依赖源语言而仅仅依赖于中间语言。

    编译程序的生成

    编译程序的构造工具

    以前人们构造编译程序大多数采用的是机器语言或汇编语言,现在只有为了充分发挥各种不同硬件系统的效率,为了满足各种不同的具体要求,才会采用这种工具来构造编译程序(或编译程序的“核心”部分)。现在越来越多采用高级语言来构造编译程序。

    T型图

    为了便于说明,我们常采用T型图来表示源语言S、目标语言T、和比编译程序实现语言I之间的关系。
    每个T型图相当于一个编译程序。
    这里写图片描述

    用高级语言L1构造编译程序

    如果A机器上有一个使用A机器代码实现的某高级语言L1的编译程序(黄色),则我们可以使用L1语言编写另外一种高级语言L2的编译程序(橙色)。把写好的L2编译程序经过L1编译程序编译后就可以得到A机器码实习的L2编译程序(绿色)。
    这里写图片描述

    编译程序的移植

    通过上面用高级语言L1构造编译程序的原理,我们可以实现编译程序的“移植”。首先我们有一个可以在A机器上编译的高级语言L,
    这里写图片描述
    接下来我们使用L去写一个能够在B机器上运行的编译程序,
    这里写图片描述
    然后通过L的编译程序就可以生成在A机器上可以运行的产生B机器代码的编译程序(3)。
    这里写图片描述
    使用这个编译程序(3)去编译一遍(2)就可以得到能在B机器上运行的B机器代码的编译程序(4).
    这里写图片描述

    自编译方式

    先对语言的核心部分构造一个小小的编译程序(可用低级语言实现),再以它为工具构造能编译更多语言成分的较大编译程序,如此不断扩展,最后形成整个编译程序(滚雪球),这种通过一系列自展途径而形成的编译程序的过程叫做自编译过程。

    构造工具

    现在人们已经建立了多种编制部分编译程序或者整个编译程序的有效工具。构造编译程序的工具称为编译程序-编译程序、编译程序产生器或翻译程序书写系统。
    例如:
    自动产生扫描器:LEX FLEX
    自动产生语法分析器:YACC BISON

    展开全文
  • 资源介绍:编译原理易语言循环首尾配对源码资源作者:
  • 本科课设,编译原理,while循环语句的识别,用的c++,有文本输入和输出
  • 其中,语句①的循环变量的步长为1, 语句②的循环变量的步长为-1。 选做内容(成绩评定范围扩大到:“优”和“良”) (1)增加运算:++ 和 --。 (2)增加类型:① 字符类型; ② 实数类型。 (3)扩充函数:① 有...
  • FOR循环 递归下降法 四元式 编译原理课程设计 VC++
  • (编译原理课程设计)WHILE循环语句的翻译程序设计
  • 注: 课程:《编译技术》上机 实验一:词法语法分析器的设计与实现,生成抽象语法树。此处完成补充 for循环 的操作
    注:
    课程:《编译技术》上机
    实验一:词法语法分析器的设计与实现,生成抽象语法树。
    建议使用词法语法分析程序生成工具如:LEX/FLEX , YACC/BISON等专业工具完成。
    
    此处完成补充 for循环 的操作
    

    另外:希望大噶支持下我滴个人博客网站:www.xyzsh.cn
    文章有更新的话,个人网站会优先发出来的(CSDN有审核)
    希望大概可以去踩一踩~!

    前期准备
    1. 已完成上一篇文章中的补充char操作
    2. 已经将整个文件夹都备好份,以供魔改后的回溯
      在这里插入图片描述
    开始实验
    第一步

    修改lex.l文件(lex描述文件给出了每一类词法单元的规则)

    1. 第37行插入对字符串for的识别
    2. 第38行插入对字符串break的识别
    3. 第39行插入对字符串continue的识别
      在这里插入图片描述
    第二步

    修改parser.y文件(parser.y是C语言文法)

    1. 第36行插入单词声名FOR BREAK CONTINUE
      在这里插入图片描述
    2. 修改Stmt第92行插入for的语句
       解释一下Stmt指代是语句,for循环整体算作一个语句在这里定义FOR LP Def Exp SEMI Exp RP Stmt
    (对照着for(int a=0;a<10;a++){printf(" %d ",a);}来看)
    LP是左括号,
    Def是变量定义语句,因为Def包含分号;,所以下一个直接接上了Exp
    Exp是复合表达式/语句(不带分号;)
    SEMI是分号
    又来一个EXP,a++可以看做是一个复合表达式的
    Rp是右括号
    Stmt,我想用的是Stmt:Compst这个文法,
    Compst是函数体,有大括号包起来的变量定义列表+语句列表
    

    在这里插入图片描述

    {$$=mknode(4,FOR,yylineno,$3,$4,$6,$8);}的含义
    mknode是创建子节点的函数,
    4表示创建4个节点
    FOR是该文法的标识(type)
    yylineno是当前行数
    $3,$4,$6,$8分别对应Def Exp Exp Stmt(几个非终结符)
    
    1. 第125-126行插入break,continue的文法
      终结符不用创建子节点,所以mknode的第一个参数为0
      在这里插入图片描述
    第三步

    修改ast.c文件(ast.c定义了树的生成与输出)

    1. 在第83行插入for的输出
       感觉不需要解释,每一个语句都是顺其自然的
      先输出For循环声名,这是父节点,然后子节点有四个,每个都indent+3
      而indent+6是子节点的内部定义,是子节点的子节点
      在这里插入图片描述
    2. 第93行插入break,contiue的输出
      在这里插入图片描述

    补:解释一下 printf("% * cCHAR:%c\n",indent,’ ',T->type_char);的含义:先打印indent个空格,再打印CHAR:%c
    默认低一级的话,往后移3个空格

    第四步

    修改test.c文件(测试代码)

    1. 第16-19行是测试for循环
    2. 第18行是测试break
    3. 第25是测试continue
      在这里插入图片描述
    结果检验

     依次运行

    flex lex.l
    bison -d parser.y
    gcc -o parser lex.yy.c parser.tab.c ast.c
    parser test.c
    

    在这里插入图片描述
     发现有乱码,使用chcp 65001切换到UTF-8编码界面
    在这里插入图片描述
     for循环输出完成!
     break输出完成!
    在这里插入图片描述
     continute输出完成!

    for循环补充完成啦!有没有感觉自己又会了hhhhhh

    写在结尾

    希望以上可以帮到你!
    如有错误,或不同想法,欢迎指出,互相学习共同进步!

    源码资源下载:点赞关注后即可免费下载,下载戳我!

    展开全文
  • 编译原理——for循环语句的翻译,LR,四元式,有报告
  • 循环语句语法语义分析,DO-while语句采用LR分析法进行设计的,其中输出中间代码以四元式的形式输出。详细的分析过程
  • 编译原理

    千次阅读 多人点赞 2019-06-05 23:00:32
    编译原理基本知识总结,基于MOOC陈鄞老师的课程。

    文章目录

    一、绪论

    什么是编译

    计算机程序设计语言及编译

    在这里插入图片描述

    编译器在语言处理系统中的位置

    在这里插入图片描述

    编译系统的结构

    在这里插入图片描述

    词法分析概述

    词法分析/扫描(Scanning)

    词法分析的主要任务:从左向右逐行扫描源程序的字符,识别出各个单词,确定单词的类型。 将识别出的单词转换成统一的机内表示——词法单元(token)形式

    在这里插入图片描述

    语法分析概述

    语法分析器(parser)从词法分析器输出的token序列中识别出各类短语,并构造语法分析树(parse tree)

    语义分析概述

    语义分析的主要任务

    • 收集标识符的属性信息
      • 种属 (Kind)
        • 简单变量、复合变量(数组、记录、…)、过程、…
      • 类型 (Type)
        • 整型、实型、字符型、布尔型、指针型、…
      • 存储位置、长度
      • 作用域
      • 参数和返回值信息
        • 参数个数、参数类型、参数传递方式、返回值类型、…
    • 语义检查
      • 变量或过程未经声明就使用
      • 变量或过程名重复声明
      • 运算分量类型不匹配
      • 操作符与操作数之间的类型不匹配
        • 数组下标不是整数
        • 对非数组变量使用数组访问操作符
        • 对非过程名使用过程调用操作符
        • 过程调用的参数类型或数目不匹配
        • 函数返回类型有误

    中间代码生成及编译器后端概述

    常用的中间表示形式

    • 三地址码 (Three-address Code)
      • 三地址码由类似于汇编语言的指令序列组成,每个指令最多有三个操作数(operand)
    • 语法结构树/语法树 (Syntax Trees)

    三地址指令的表示

    • 四元式 (Quadruples)
      • (op, y, z, x)
    • 三元式 (Triples)
    • 间接三元式 (Indirect triples)

    目标代码生成器

    • 目标代码生成器以源程序的中间 表示形式作为输入,并把它映射到目标语言
    • 目标代码生成的一个重要任务是为程序中使用的变量合理分配寄存器

    代码优化

    为改进代码所进行的等价程序变换,使其运行得更快一些、占用空间更少一些,或者二者兼顾

    二、语言及其文法

    基本概念

    字母表 (Alphabet)

    字母表∑是一个有穷符号集合

    字母表上的运算

    1. 字母表∑1和∑2的乘积( product) ∑1∑2 ={ab|a ∈ ∑1, b ∈ ∑2}

    2. 字母表∑的n次幂( power)

      • ∑0 ={ ε }

      • ∑n =∑n-1 ∑ , n ≥ 1

        字母表的n次幂:长度为n的符号串构成的集合

    3. 字母表∑的正闭包( positive closure)

      ∑+ = ∑∪∑2∪∑3∪…

      字母表的正闭包:长度正数的符号串构成的集合

    4. 字母表∑的克林闭包(Kleene closure)

      ∑ 克林闭包= ∑0∪∑+ = ∑0∪∑∪∑2∪∑3∪…

      字母表的克林闭包:任意符号串(长度可以为零)构成的集合

    串 (String)

    设∑是一个字母表,任意x∈∑克林闭包,x称为是∑上的一个串

    串是字母表中符号的一个有穷序列

    串s的长度,通常记作|s|,是指s中符号的个数

    空串是长度为0的串,用 ε(epsilon)表示

    串上的运算——连接

    如果 x和y是串,那么x和y的连接(concatenation)是把y附加到x后面而形成的串,记作xy

    空串是连接运算的单位元( identity),即,对于任何串s都有,εs = sε = s

    设x,y,z是三个字符串,如果 x=yz, 则称y是x的前缀,z是x的后缀

    串上的运算——幂

    串s的幂运算

    • s0= ε
    • sn = sn-1 s , n ≥1

    串s的n次幂:将n个s连接起来

    文法的定义

    文法的形式化定义

    G=(VT ,VN ,P,S)

    VT :终结符集合

    VN:非终结符集合

    P :产生式集合

    S :开始符号

    符号约定

    • 终结符
    1. 字母表中排在前面的小写字母,如a、b、c
    2. 运算符,如+、乘等
    3. 标点符号,如括号、逗号等
    4. 数字0、1、. . . 、9
    5. 粗体字符串,如id、if等
    • 非终结符
    1. 字母表中排在前面的大写字母,如A、B、C
    2. 字母S。通常表示开始符号
    3. 小写、斜体的名字,如expr、stmt等
    4. 代表程序构造的大写字母。如E(表达式)、T(项) 和F(因子)

    在这里插入图片描述

    语言的定义

    推导 (Derivations)和归约(Reductions)

    句子的推导(派生)-从生成语言的角度

    句子的归约 - 从识别语言的角度

    句型和句子

    如果 S ->star α,α∈(VT∪VN)star ,则称α是G的一个句型 (sentential form)

    • 一个句型中既可以包含终结符,又可以包含非终 结符,也可能是空串

    如果 S ->star w,w ∈VTstar,则称w是G的一个句子(sentence)

    • 句子是不包含非终结符的句型

    语言的形式化定义

    由文法G的开始符号S推导出的所有句子构成 的集合称为文法G生成的语言,记为L(G )。 即

    L(G )= {w | S ->star w, w∈ VT star }

    语言上的运算

    在这里插入图片描述

    文法的分类

    Chomsky 文法分类体系

    前提:α →β

    0型文法:α中至少包含1个非终结符

    1型文法(CSG) :|α|≤|β|

    • 上下文有关文法(Context-Sensitive Grammar , CSG )

    2型文法(CFG) :α ∈ VN

    • 上下文无关文法 (Context-Free Grammar, CFG )

    3型文法(RG):A→wB 或 A→w (A→Bw 或A→w)

    在这里插入图片描述

    CFG的分析树

    CFG 的分析树

    在这里插入图片描述

    • 根节点的标号为文法开始符号
    • 内部结点表示对一个产生式A→β的应用,该结点的标号是此产生式左部A 。该结点的子结点的标号从左到右构成了产生式的右部β
    • 叶结点的标号既可以是非终结符,也可以是终结符。从左到右排列叶节点得到的符号串称为是这棵树的产出( yield )或边缘(f rontier)

    分析树是推导的图形化表示

    给定一个推导 S -> α1-> α2->…-> αn ,对于推导 过程中得到的每一个句型αi,都可以构造出一个 边缘为αi的分析树

    在这里插入图片描述

    (句型的)短语

    给定一个句型,其分析树中的每一棵子树的边缘称为该句型的一个短语(phrase)

    • 如果子树只有父子两代结点,那么这棵子树的边缘称为该句型的一个直接短语(immediate phrase)

    在这里插入图片描述

    二义性文法 (Ambiguous Grammar)

    如果一个文法可以为某个句子生成多棵分析树, 则称这个文法是二义性的

    二义性文法的判定

    对于任意一个上下文无关文法,不存在一个算法, 判定它是无二义性的;但能给出一组充分条件, 满足这组充分条件的文法是无二义性的

    • 满足,肯定无二义性
    • 不满足,也未必就是有二义性的

    三、词法分析

    正则表达式

    正则表达式 (Regular Expression,RE ) 是一种用来描述正则语言的更紧凑的表示方法

    正则表达式可以由较小的正则表达式按照特定规则递归地 构建。每个正则表达式 r定义(表示)一个语言,记为L(r )。 这个语言也是根据r 的子表达式所表示的语言递归定义的。

    正则表达式的定义

    • ε是一个RE,L(ε) = {ε}
    • 如果 a∈∑,则a是一个RE,L(a) = {a}
    • 假设 r和 s都是 RE,表示的语言分别是 L( r)和L(s),则
      • r|s 是一个RE,L( r|s ) = L (r )∪L(s)
      • rs 是一个RE,L( rs ) = L(r ) L(s)
      • r star是一个RE,L( r star)= (L(r ))star
      • (r ) 是一个RE,L( (r ) ) = L(r )

    运算的优先级:*、连接、|

    正则语言

    可以用RE定义的语言叫做正则语言(regular language)或正则集合(regular set)

    正则文法与正则表达式等价

    对任何正则文法 G,存在定义同一语言的 正则表达式 r

    对任何正则表达式 r,存在生成同一语言的 正则文法 G

    正则定义(Regular Definition)

    在这里插入图片描述

    有穷自动机 ( Finite Automata,FA )

    • 一类处理系统 建立的数学模型
    • 具有一系列离散的输入输出信息和有穷数目的内部状态(状态:概括了对过去输入信息处理的状况)
    • 系统只需要根据当前所处的状态和当前面临的输入信息就可以决定系统的后继行为。每当系统处理了当前的输入后,系统的内部状态也将发生改变

    FA模型

    在这里插入图片描述

    FA的表示

    转换图 (Transition Graph)

    • 结点:FA的状态
      • 初始状态(开始状态):只有一个,由start箭头指向
      • 终止状态(接收状态):可以有多个,用双圈表示
    • 带标记的有向边:如果对于输入a,存在一个从状态p到状 态q的转换,就在p、q之间画一条有向边,并标记上a

    在这里插入图片描述

    FA定义(接收)的语言

    给定输入串x,如果存在一个对应于串x的从初始状态到某个终止状态的转换序列,则称串x被该FA接收

    由一个有穷自动机M接收的所有串构成的集合称为是该FA定义(或接收)的语言,记为L(M )

    最长子串匹配原则 (Longest String Matching Principle)

    当输入串的多个前缀与一个或多个模式匹配时, 总是选择最长的前缀进行匹配

    在到达某个终态之后,只要输入带上还有符号, DFA就继续前进,以便寻找尽可能长的匹配

    有穷自动机的分类

    确定的FA (Deterministic finite automata, DFA)

    在这里插入图片描述

    非确定的FA (Nondeterministic finite automata, NFA)

    在这里插入图片描述

    DFA和NFA的等价性

    • 对任何非确定的有穷自动机N ,存在定义同一语言的确定的有穷自动机D
    • 对任何确定的有穷自动机D ,存在定义同一语言的 非确定的有穷自动机N

    从正则表达式到有穷自动机

    在这里插入图片描述

    从NFA到DFA的转换

    子集构造法(subset construction)

    在这里插入图片描述

    计算 ε-closure(T)

    屏幕快照 2019-06-05 22.08.45

    确定性有限自动机的简化

    DFA化简

    概念:指寻找一个状态数比DFA M少的DFA M’,使得L(M)=L(M’)

    一个确定有限自动机M是通过①消除多余状态和②合并等价状态而转换成一个最小的与之等价的有限自动机来实现其化简的。

    多余状态(无用状态)

    概念:从该自动机的初始状态出发,任何输入串也不能到达的那个状态。

    对于给定的DFA M,若它含有多余状态,可以非常简单地将多余状态消除,而得到与之等价的有穷自动机。

    两个状态S和T等价的条件

    1. 如果从S出发能读出某个字w而停于终态,那么从T出发也能读出同样的字而停于终态;
    2. 如果从T出发能读出某个字w而停于终态,那么从S出发也能读出同样的字而停于终态。

    在DFA M里如果两个状态若不等价,则称这两个状态是可区别的。

    DFA的状态最少化算法

    子集分割法:把一个DFA M的状态分割成一些不相交的子集,使得任何不同的两个子集的状态都是可区别的,而同一子集中的两个状态都是等价的。最后,在每个子集中选出一个代表,同时消去其他等价的状态。如果在从M′中删除所有的无用状态(多余状态),则M′便是最简的(状态最少),从而得到把DFA M状态简化了的DFA M’。

    在这里插入图片描述

    在这里插入图片描述

    四、语法分析

    自顶向下的分析 (Top-Down Parsing)

    每一步推导中,都需要做两个选择

    • 替换当前句型中的哪个非终结符
    • 用该非终结符的哪个候选式进行替换

    最左推导 (Left-most Derivation)

    在最左推导中,总是选择每个句型的最左非终结符进行替换

    如果S->* α,则称α是当前文法的最左句型 (left-sentential form)

    自顶向下的语法分析采用最左推导方式

    最右推导 (Right-most Derivation)

    在最右推导中,总是选择每个句型的最右非终结符进行替换

    在自底向上的分析中,总是采用最左归约的方式,因此把最左归约称为规范归约,而最右推导相应地称为规范推导

    递归下降分析 (Recursive-Descent Parsing)

    • 由一组过程组成,每个过程对应一个非终结符
    • 从文法开始符号S对应的过程开始,其中递归调用文法中其它非终结符对应 的过程。如果S对应的过程体恰好扫描了整个输入串,则成功完成语法分析
    • 可能需要回溯 (backtracking), 导致效率较低

    预测分析 (Predictive Parsing)

    预测分析是递归下降分析技术的一个特例,通过 在输入中向前看固定个数(通常是一个)符号来选 择正确的A-产生式。

    可以对某些文法构造出向前看k个输入符号的预测分析 器,该类文法有时也称为LL(k) 文法类

    预测分析不需要回溯,是一种确定的自顶向下分析方法

    文法转换

    左递归

    含有A→Aα形式产生式的文法称为是直接左递归的 (immediate left recursive)

    如果一个文法中有一个非终结符A使得对某个串α存在一个推导A->+Aα ,那么这个文法就是左递归的

    经过两步或两步以上推导产生的左递归称为是间接左递归的

    消除直接左递归

    在这里插入图片描述
    事实上,这种消除过程就是把左递归转换成了右递归

    消除间接左递归

    在这里插入图片描述

    提取左公因子 (Left Factoring )

    通过改写产生式来推迟决定,等读入了足够多的输入,获得足够信息后再做出正确的选择

    在这里插入图片描述

    LL(1) 文法

    S _ 文 法-简单的确定性文法

    • 每个产生式的右部都以终结符开始
    • 同一非终结符的各个候选式的首终结符都不同

    S_文法不含ε产生式

    预测分析法的工作过程:从文法开始符号出发,在每一步推导过程中根据当前句型 的最左非终结符A和当前输入符号a,选择正确的 A- 产生式。为保证分析的确定性,选出的候选式必须是唯一的。

    非终结符的后继符号集

    可能在某个句型中紧跟在A后边的终结符a的集合,记为FOLLOW(A),FOLLOW(A)={a| S->αAaβ, a∈VT,α,β∈(VT ∪ VN)*}

    如果 A是某个句型的的最右符号, 则将结束符 “$” 添加到 FOLLOW(A) 中

    产生式的可选集

    产生式A→β的可选集是指可以选用该产生式进行推导时对应的输入符号的集合,记为SELECT( A→β )

    SELECT( A→aβ ) = { a }

    SELECT( A→ε )=FOLLOW( A )

    q_ 文法
    • 每个产生式的右部或为ε ,或以终结符开始
    • 具有相同左部的产生式有不相交的可选集

    q_文法不含右部以非终结符打头的产生式

    串首终结符集

    串首终结符

    串首第一个符号,并且是终结符,简称首终结符

    给定一个文法符号串α, α的串首终结符集FIRST(α)被定义为可以从α推导出的所有串首终结符构成的集合。

    • 如果α ->* ε, 那么ε也在FIRST(α)中
    • 对于任意α∈(VT ∪ V N)+, FIRST(α)={ a | α->* aβ,a∈ VT,β∈(VT ∪ VN)*};

    产生式A→α的可选集SELECT

    • 如果ε不属于FIRST(α), 那么SELECT(A→α)=FIRST(α)
    • 如果 ε∈FIRST(α), 那么SELECT(A→α)=( FIRST(α)-{ε} )∪FOLLOW(A)

    LL(1) 文法

    文法G是LL(1)的,当且仅当G的任意两个具有相同左部的产生式A → α | β 满足下面的条件

    • 不存在终结符a使得α 和β都能够推导出以a开头的串
    • α 和β至多有一个能推导出ε
    • 如果 β -> ε,则FIRST (α)∩FOLLOW(A) =Φ;如果 α -> ε,则FIRST (β)∩FOLLOW(A) =Φ

    sum=同一非终结符的各个产生式的可选集互不相交

    • 第一个“L” 表示从左向右扫描输入
    • 第二个“ L” 表示产生最左推导
    • “1” 表示在每一步中只需要向前看一个输入符号来决定语法分析动作

    FIRST集 和 FOLLOW集 的计算

    在这里插入图片描述

    计算文法符号X的FIRST(X )

    算法:不断应用下列规则,直到没有新的终结符或ε可以被 加入到任何FIRST集合中为止

    • 如果X是一个终结符,那么FIRST ( X ) = { X }
    • 如果X是一个非终结符,且 X→Y1…Yk∈P (k≥1),那么如果对于某个i,a在FIRST (Yi ) 中且ε 在所有的FIRST(Y1) , … , FIRST(Yi-1)中(即Y1…Yi-1->* ε ),就把a加入 到FIRST( X )中。如果对于所有的 j = 1,2, . . . , k,ε在 FIRST(Yj)中,那么将ε加入到FIRST( X )
    • 如果 X→ε∈P,那么将ε加入到FIRST( X ) 中

    计算串X1X2 …Xn的FIRST 集合

    在这里插入图片描述

    计算非终结符A的FOLLOW(A)

    算法:不断应用下列规则,直到没有新的终结符可以被加 入到任何FOLLOW集合中为止

    • 将$放入FOLLOW( S )中,其中S是开始符号,$ 是输入右 端的结束标记
    • 如果存在一个产生式A→αBβ,那么FIRST ( β )中除ε 之外 的所有符号都在FOLLOW( B )中
    • 如果存在一个产生式A→αB,或存在产生式A→αBβ且 FIRST ( β ) 包含ε,那么 FOLLOW( A ) 中的所有符号都在 FOLLOW( B ) 中

    递归的预测分析法

    递归的预测分析法是指:在递归下降分析中,根据预测分析表进行产生式的选择

    根据每个非终结符的产生式和LL(1)文法的预测分析表,为每个非终结符编写对应的过程

    在这里插入图片描述

    非递归的预测分析法

    非递归的预测分析不需要为每个非终结符编写递归下降过程,而是根据预测分析表构造一个自动机,也叫表驱动的预测分析

    在这里插入图片描述

    例子

    表驱动的预测分析法

    在这里插入图片描述

    预测分析法实现步骤

    1. 构造文法
    2. 改造文法:消除二义性、消除左递归、消除回溯
    3. 求每个变量的FIRST集和FOLLOW集,从而求得每个候选式的SELECT集
    4. 检查是不是 LL(1) 文法,若是,构造预测分析表
    5. 对于递归的预测分析,根据预测分析表为每一个非终结符编写一个过程;对于非递归的预测分析,实现表驱动 的预测分析算法

    自底向上分析

    自底向上分析概述

    自底向上的语法分析

    • 自顶向下的语法分析采用最左推导方式
    • 自底向上的语法分析采用最左归约方式(反向构造最右推导)

    自底向上语法分析的通用框架:移入 - 归约分析(Shift-Reduce Parsing)

    在这里插入图片描述

    移入-归约分析器可采取的4种动作

    • 移入:将下一个输入符号移到栈的顶端
    • 归约:被归约的符号串的右端必然处于栈顶。语法 分析器在栈中确定这个串的左端,并决定用哪个非 终结符来替换这个串
    • 接收:宣布语法分析过程成功完成
    • 报错:发现一个语法错误,并调用错误恢复子例程

    LR分析法概述

    LR 分析法

    LR文法是最大的、可以构造出相应移入 - 归约语法分析器的文法类

    • L: 对输入进行从左到右的扫描
    • R: 反向构造出一个最右推导序列

    LR(k)分析:需要向前查看k个输入符号的LR分析

    k = 0和k = 1这两种情况具有实践意义,当省略 (k) 时,表示 k =1

    LR 分析法的基本原理

    自底向上分析的关键问题是什么? ans:如何正确地识别句柄

    句柄是逐步形成的,用“状态”表示句柄识别的进展程度

    在这里插入图片描述

    LR 分析器(自动机)的总体结构

    在这里插入图片描述

    LR(0)分析

    LR(0) 项目

    右部某位置标有圆点的产生式称为相应文法的一个LR(0) 项目(简称为项目)

    • S → ·bBB 移进项目
    • S → b·BB 待约项目
    • S → bB·B 待约项目
    • S → bBB· 归约项目

    项目描述了句柄识别的状态

    产生式A→ε 只生成一个项目A→ ·

    增广文法 (Augmented Grammar)

    如果G 是一个以S为开始符号的文法,则G的增广文法 G’ 就是在G中加上新开始符号S’ 和产生式S’ → S而得到的文法

    引入这个新的开始产生式的目的是使得文法开始符号仅出现在一个产生式的左边,从而使得分析器只有一个接受状态

    文法中的项目

    后继项目(Successive Item)

    同属于一个产生式的项目,但圆点的位置只相差一个符号,则称后者是前者的后继项目

    eg A→α· Xβ的后继项目是A→αX·β

    可以把等价的项目组成一个项目集( I ) ,称为项目集闭包 (Closure of Item Sets),每个项目集闭包对应着自动机的一个状态

    在这里插入图片描述

    LR(0)分析表构造算法

    CLOSURE( ) 函数

    计算给定项目集I的闭包 CLOSURE(I) =I ∪{B→·γ|A→α·Bβ∈CLOSURE(I),B→γ∈P}

    GOTO ( ) 函数

    返回项目集I对应于文法符号X的后继项目集闭包GOTO( I, X )=CLOSURE({A→αX·β | A→α·Xβ∈I })

    构造 LR(0) 自动机的状态集

    规范LR(0) 项集族(Canonical LR(0) Collection)C={I0}∪{I | 存在J∈C, X∈VN∪ VT , I=GOTO(J , X) }

    LR(0) 分析表构造算法

    在这里插入图片描述

    LR(0) 自动机的形式化定义

    在这里插入图片描述

    SLR分析

    SLR 分析

    在这里插入图片描述

    SLR 分析表构造算法

    在这里插入图片描述

    LR(1)分析

    LR(1)分析法的提出

    SLR分析存在的问题:SLR只是简单地考察下一个输入符号b是否属于与归约项目A→α相关联的FOLLOW(A),但b∈FOLLOW(A) 只是归约α的一个必要条件,而非充分条件

    对于产生式 A→α的归约,在不同的使用位置,A会要求不同的后继符号

    在特定位置,A的后继符集合是 FOLLOW(A) 的子集

    规范LR(1)项目

    将一般形式为 [A→α· β, a]的项称为 LR(1) 项,其中A→αβ 是 一个产生式,a 是一个终结符(这里将$视为一个特殊的终结符) 它表示在当前状态下,A后面必须紧跟的终结符,称为该项的展望符

    LR(1) 中的1指的是项的第二个分量的长度

    在形如[A→α·β, a]且β ≠ ε的项中,展望符a没有任何作用,但是一个形如 [A→α·, a]的项在只有在下一个输入符号等于a时才可 以按照A→α 进行归约

    这样的a的集合总是FOLLOW(A)的子集,而且它通常是一个真子集

    等价LR(1)项目

    在这里插入图片描述

    b∈FIRST(βa)

    LR(1)项目集闭包

    CLOSURE( I ) = I ∪ { [B→·γ, b] | [A→α·Bβ,a] ∈ CLOSURE( I ), B→γ∈P, b∈FIRST(βa) }

    GOTO 函数

    GOTO( I, X ) = CLOSURE({[A→αX·β,a]|[A→α·Xβ, a]∈I })

    LR(1)自动机的形式化定义

    在这里插入图片描述

    LR分析表构造算法

    在这里插入图片描述

    LALR分析法

    LALR ( lookahead-LR )分析的基本思想

    1. 寻找具有相同核心的LR (1) 项集,并将这些项集合并为一个项集。 所谓项集的核心就是其第一分量的集合。
    2. 根据合并后得到的项集族构造语法分析表
    3. 如果分析表中没有语法分析动作冲突,给定的文 法就称为LALR (1) 文法,就可以根据该分析表 进行语法分析

    合并同心项集后,虽然不产生冲突,但可能会推迟错误的发现

    合并同心项集时会产生归约-归约冲突

    在这里插入图片描述

    合并同心项集不会产生移进 - 归约冲突

    存在的小问题

    合并同心项集后,虽然不产生冲突,但可能会推迟错误的发现

    在这里插入图片描述

    LALR分析法可能会作多余的归约动作, 但绝不会作错误的移进操作,因为合并同心项集不会影响移进

    LALR(1)的特点

    • 形式上与 LR(1) 相同
    • 大小上与 LR(0)/SLR 相当
    • 分析能力介于SLR 和 LR(1) 二者之间:SLR<LALR(1)<LR(1)
    • 合并后的展望符集合仍为FOLLOW 集的子集

    五、语法制导翻译

    语法制导翻译概述

    什么是语法制导翻译

    在这里插入图片描述

    语法制导翻译的基本思想

    如何表示语义信息?

    • 为CFG中的文法符号设置语义属性,用来表示语法成分对应的语义信息

    如何计算语义属性?

    • 文法符号的语义属性值是用与文法符号所在产生式 (语法规则)相关联的语义规则来计算的
    • 对于给定的输入串x ,构建x的语法分析树,并利用与 产生式(语法规则)相关联的语义规则来计算分析树 中各结点对应的语义属性值

    两个概念

    将语义规则同语法规则(产生式)联系起来要 涉及两个概念

    • 语法制导定义(Syntax-Directed Definitions, SDD)
    • 语法制导翻译方案 (Syntax-Directed Translation Scheme , SDT )

    语法制导定义 (SDD)

    SDD 是对CFG的推广

    • 将每个文法符号和一个语义属性集合相关联
    • 将每个产生式和一组语义规则相关联,这些规则用于计算该产生式中各文法符号的属性值

    在这里插入图片描述

    语法制导翻译方案( SDT )

    SDT是在产生式右部嵌入了程序片段的CFG,这些程序片段称为语义动作。

    在这里插入图片描述

    一个语义动作在产生式中的位置决定了这个动作的执行时间

    SDD与SDT

    SDD

    • 是关于语言翻译的高层次规格说明
    • 隐蔽了许多具体实现细节,使用户不必显式地说明翻译发生的顺序

    SDT

    • 可以看作是对SDD的一种补充,是SDD的具体实施方案
    • 显式地指明了语义规则的计算顺序,以便说明某些实现细节

    语法制导定义SDD

    综合属性(synthesized attribute)

    在分析树结点 N上的非终结符A的综合属性只能通过N的子结点或 N本身的属性值来定义

    终结符可以具有综合属性:终结符的综合属性值是由词法分析器提供的词法值,因此在SDD中没有计 算终结符属性值的语义规则

    继承属性(inherited attribute)

    在分析树结点 N上的非终结符A的继承属性只能通过N的父结点、N的兄弟结点或 N本身的属性值来定义

    终结符没有继承属性:终结符从词法分析器处获得 的属性值被归为综合属性值

    属性文法 (Attribute Grammar)

    一个没有副作用的SDD有时也称为属性文法

    属性文法的规则仅仅通过其它属性值和常量来定义一个属性值

    SDD的求值顺序

    SDD的求值顺序

    按照什么顺序计算属性值?

    • 语义规则建立了属性之间的依赖关系,在对语法分析树节点的一个属性求值之前,必须首先求出 这个属性值所依赖的所有属性值

    依赖图(Dependency Graph)

    • 依赖图是一个描述了分析树中结点属性间依赖关系的有向图
    • 分析树中每个标号为X的结点的每个属性a都对应 着依赖图中的一个结点
    • 如果属性X.a的值依赖于属性Y.b的值,则依赖图 中有一条从Y.b的结点指向X.a的结点的有向边

    属性值的计算顺序

    可行的求值顺序是满足下列条件的结点序列N1, N2, … , Nk :如果依赖图中有一条从结点Ni到 Nj 的边(Ni→Nj), 那么i < j(即:在节点序列中,Ni 排在Nj 前面)

    这样的排序将一个有向图变成了一个线性排序, 这个排序称为这个图的拓扑排序(topological sort)

    拓扑排序存在性
    • 对于只具有综合属性的SDD ,可以按照任何自 底向上的顺序计算它们的值
    • 对于同时具有继承属性和综合属性的SDD,不能保证存在一个顺序来对各个节点上的属性进行求值
      • 如果图中没有环,那么至少存在一个拓扑排序
      • 从计算的角度看,给定一个SDD,很难确定是否存在某棵语法分析树,使得SDD的属性之间存在循环依赖关系;幸运的是,存在一个SDD的有用子类,它们能够保证对每棵语法分析树都存在一个求值顺序,因为它们不允许产生带有环的依赖图

    S-属性定义与L-属性定义

    S-属性定义

    仅仅使用综合属性的SDD称为S属性的SDD,或S-属性定义、S-SDD

    • 如果一个SDD是S属性的,可以按照语法分析树节点的任何 自底向上顺序来计算它的各个属性值
    • S-属性定义可以在自底向上的语法分析过程中实现

    L-属性定义

    一个SDD是L-属性定义,当且仅当它的每个属性要 么是一个综合属性,要么是满足如下条件的继承属 性:假设存在一个产生式A→X1X2…Xn,其右部符 号Xi (1<= i <= n)的继承属性仅依赖于下列属性:

    • A的继承属性(如果是综合属性,可能会形成环)
    • 产生式中Xi左边的符号 X1, X2, … , Xi-1 的属性
    • Xi本身的属性,但Xi 的全部属性不能在依赖图中形成环路

    注:每个S-属性定义都是L-属性定义

    语法制导翻译方案SDT

    语法制导翻译方案SDT

    语法制导翻译方案(SDT )是在产生式右部中嵌入了程序片段(称为语义动作)的CFG

    SDT可以看作是SDD的具体实施方案

    • 基本文法可以使用LR分析技术,且SDD是S属性的
    • 基本文法可以使用LL分析技术,且SDD是L属性的

    将S-SDD转换为SDT

    将一个S-SDD转换为SDT的方法:将每个语义动作都放在产生式的最后

    S-属性定义的SDT 实现

    如果一个S-SDD的基本文法可以使用LR分析技术,那么它的SDT可以在LR语法分析过程中实现

    将L-SDD转换为SDT

    将L-SDD转换为SDT的规则

    • 将计算某个非终结符号A的继承属性的动作插入 到产生式右部中紧靠在A的本次出现之前的位置上
    • 将计算一个产生式左部符号的综合属性的动作放 置在这个产生式右部的最右端

    L-属性定义的SDT 实现

    如果一个L-SDD的基本文法可以使用LL分析技术,那么它的SDT可以在LL或LR语法分析过程中实现

    • 在非递归的预测分析过程中进行语义翻译
    • 在递归的预测分析过程中进行语义翻译
    • 在LR分析过程中进行语义翻译

    在非递归的预测分析过程中进行翻译

    扩展语法分析栈

    在这里插入图片描述

    分析栈中的每一个记录都对应着一段执行代码

    • 综合记录出栈时,要将综合属性值复制给后面特定的语义动作
    • 变量展开时(即变量本身的记录出栈时),如果其含有继承属性,则要将继承属性值复制给后面特定的语义动作

    在递归的预测分析过程中进行翻译

    算法

    • 为每个非终结符A构造一个函数,A的每个继承属性对应该函数的一个形参,函数的返回值是A的综 合属性值。对出现在A产生式中的每个文法符号的 每个属性都设置一个局部变量
    • 非终结符A的代码根据当前的输入决定使用哪个产生式
    • 与每个产生式有关的代码执行如下动作:从左到右考虑 产生式右部的词法单元、非终结符及语义动作
      • 对于带有综合属性x的词法单元 X,把x的值保存在局部变量 X.x中;然后产生一个匹配 X的调用,并继续输入
      • 对于非终结符B,产生一个右部带有函数调用的赋值语句c := B(b1 , b2 , …, bk),其中, b1 , b2 , …, bk是代表B的继承属性的变 量,c是代表B的综合属性的变量
      • 对于每个动作,将其代码复制到语法分析器,并把对属性的引 用改为对相应变量的引用

    L-属性定义的自底向上翻译

    给定一个以LL文法为基础的L-SDD,可以修改这个文法,并在LR语法分析过程中计算这个新文法之上的SDD

    • 首先构造SDT,在各个非终结符之前放置语义动作来计算它的继承属性, 并在产生式后端放置语义动作计算综合属性
    • 对每个内嵌的语义动作,向文法中引入一个标记非终结符来替换它。每个这样的位置都有一个不同的标记,并且对于任意一个标记M都有一个产生 式M→ε
    • 如果标记非终结符M在某个产生式A→α{a}β中替换了语义动作a,对a进行 修改得到a’ ,并且将a’关联到M→ε 上。动作a’
      1. 将动作a需要的A或α中符号的任何属性作为M的继承属性进行复制
      2. 按照a中的方法计算各个属性,但是将计算得到的这些属性作为M的综合属性

    六、中间代码生成

    类型表达式

    类型表达式 (Type Expressions)

    • 基本类型是类型表达式
    • 可以为类型表达式命名,类型名也是类型表达式
    • 将类型构造符(type constructor)作用于类型表达式可以构成新的类型表达式
      • 数组构造符array
        • 若T是类型表达式,则array ( I, T )是类型表达式( I是一个整数)
      • 指针构造符pointer
        • 若T 是类型表达式,则 pointer ( T ) 是类型表达式,它表示一个指针类型
      • 笛卡尔乘积构造符
        • 若T1 和T2是类型表达式,则笛卡尔乘积T1 x T2 是类型表达式
      • 函数构造符→
        • 若T1、T2、…、Tn 和R是类型表达式,则T1xT2 x…xTn→ R是类型表达式
      • 记录构造符record
        • 若有标识符N1 、N2 、…、Nn 与类型表达式T1 、T2 、…、Tn , 则 record((N1 xT1)x(N2xT2)x…x(Nn xTn))是一个类型表达式

    声明语句的翻译

    局部变量的存储分配

    • 对于声明语句,语义分析的主要任务就是收集标识符的类型等属性信息,并为每一个名字分配一个相对地址
      • 从类型表达式可以知道该类型在运行时刻所需的存储单元数量称为类型的宽度(width)
      • 在编译时刻,可以使用类型的宽度为每一个名字分配一个相对地址
    • 名字的类型和相对地址信息保存在相应的符号表记录中

    在这里插入图片描述

    在这里插入图片描述

    简单赋值语句的翻译

    赋值语句翻译的任务

    在这里插入图片描述

    赋值语句翻译的主要任务:生成对表达式求值的三地址码

    在这里插入图片描述

    数组引用的翻译

    数组引用的翻译

    在这里插入图片描述

    将数组引用翻译成三地址码时要解决的主要问题是确定数组元素的存放地址,也就是数组元素的寻址

    数组元素寻址 (Addressing Array Elements )

    在这里插入图片描述

    在这里插入图片描述

    带有数组引用的赋值语句的翻译

    在这里插入图片描述

    数组引用的SDT

    在这里插入图片描述

    在这里插入图片描述

    控制流语句及其SDT

    控制流语句的基本文法

    在这里插入图片描述

    布尔表达式及其SDT

    布尔表达式的基本文法

    在这里插入图片描述

    在跳转代码中,逻辑运算符&&、|| 和 ! 被翻译成跳转指令。运算符本身 不出现在代码中,布尔表达式的值是通过代码序列中的位置来表示的

    在这里插入图片描述

    SDT的通用实现方法

    任何SDT都可以通过下面的方法实现:首先建立一棵语法分析树,然后按照从左到右的深度优先顺序来执行这些动作

    回填 (Backpatching)

    基本思想

    生成一个跳转指令时,暂时不指定该跳转指令的目标标 号。这样的指令都被放入由跳转指令组成的列表中。同一个列表中的所有跳转指令具有相同的目标标号。等到能够确定正确的目标标号时,才去填充这些指令的目标标号。

    七、运行存储分配

    运行存储分配概述

    运行存储分配策略

    编译器在工作过程中,必须为源程序中出现的一些数据对象分配运行时的存储空间

    • 对于那些在编译时刻就可以确定大小的数据对象,可以在 编译时刻就为它们分配存储空间,这样的分配策略称为静态存储分配
    • 如果不能在编译时完全确定数据对象的大小,就要 采用动态存储分配的策略。即在编译时仅产生各种必要的信息,而在运行时刻,再动态地分配数据对象的存储空间
      • 栈式存储分配
      • 堆式存储分配

    注:静态和动态分别对应编译时刻和运行时刻

    运行时内存的划分

    在这里插入图片描述

    活动记录

    • 使用过程(或函数、方法)作为用户自定义动作的单元的 语言,其编译器通常以过程为单位分配存储空间
    • 过程体的每次执行称为该过程的一个活动(activation)
    • 过程每执行一次,就为它分配一块连续存储区,用来管 理过程一次执行所需的信息,这块连续存储区称为活动 记录( activation record )
    活动记录的一般形式

    在这里插入图片描述

    静态存储分配

    静态存储分配

    在静态存储分配中,编译器为每个过程确定其活动记 录在目标程序中的位置,这样,过程中每个名字的存储位置就确定了。因此,这些名字的存储地址可以被编译到目标代码,过程每次执行时,它的名字都绑定到同样的存储单元。

    静态存储分配的限制条件

    适合静态存储分配的语言必须满足以下条件

    • 数组上下界必须是常数
    • 不允许过程的递归调用
    • 不允许动态建立数据实体

    满足这些条件的语言有BASIC和FORTRAN等

    常用的静态存储分配方法

    • 顺序分配法

      • 按照过程出现的先后顺序逐段分配存储空间
      • 各过程的活动记录占用互不相交的存储空间

      在这里插入图片描述

    • 层次分配法

      • 通过对过程间的调用关系进行分析,凡属无相互调用 关系的并列过程,尽量使其局部数据共享存储空间

      在这里插入图片描述

      • 层次分配法方向:自底向上

    栈式存储分配

    栈式存储分配

    有些语言使用过程、函数或方法作为用户自定义动作的单 元,几乎所有针对这些语言的编译器都把它们的(至少一 部分的)运行时刻存储以栈的形式进行管理,称为栈式存 储分配

    当一个过程被调用时,该过程的活动记录被压入栈;当过程结束时,该活动记录被弹出栈

    这种安排不仅允许活跃时段不交叠的多个过程调用之间共 享空间,而且允许以如下方式为一个过程编译代码:它的非局部变量的相对地址总是固定的,和过程调用序列无关

    活动树

    用来描述程序运行期间控制进入和离开各个活动的情况的树称为活动树

    • 树中的每个结点对应于一个活动。根结点是启动程序执行的main过程的活动
    • 在表示过程p的某个活动的结点上,其子结点对应于被p 的这次活动调用的各个过程的活动。按照这些活动被调用的顺序,自左向右地显示它们,一个子结点必须在其 右兄弟结点的活动开始之前结束
    • 每个活跃的活动都有一个位于控制栈中的活动记录
    • 活动树的根的活动记录位于栈底
    • 程序控制所在的活动的记录位于栈顶
    • 栈中全部活动记录的序列对应于在活动树中到达当前控制所在的活动结点的路径

    设计活动记录的一些原则

    在这里插入图片描述

    调用序列和返回序列

    过程调用和过程返回都需要执行一些代码来管理活动记录 栈,保存或恢复机器状态等

    • 调用序列

      • 实现过程调用的代码段。为一个活动记录在栈中分配空间,并在此记录的字段中填写信息

        在这里插入图片描述

    • 返回序列

      • 恢复机器状态,使得调用过程能够在调用结束之后继续执行

      在这里插入图片描述

    • 一个调用代码序列中的代码通常被分割到调用过程(调用者) 和被调用过程(被调用者)中。返回序列也是如此。

      在这里插入图片描述

    变长数据的存储分配

    • 在现代程序设计语言中,在编译时刻不能确定大小的对象 将被分配在堆区。但是,如果它们是过程的局部对象,也 可以将它们分配在运行时刻栈中。尽量将对象放置在栈区 的原因:可以避免对它们的空间进行垃圾回收,也就减少了相应的开销。
    • 只有一个数据对象局部于某个过程,且当此过程结束时它 变得不可访问,才可以使用栈为这个对象分配空间。

    非局部数据的访问

    非局部数据的访问

    一个过程除了可以使用过程自身定义的局部数据以 外,还可以使用过程外定义的非局部数据

    语言可以分为两种类型

    • 支持过程嵌套声明的语言
      • 可以在一个过程中声明另一个过程
      • 例: Pascal:一个过程除自身定义的局部数据和全局定义的数据以外,还可以使用外围过程中声明的对象
    • 不支持过程嵌套声明的语言
      • 不可以在一个过程中声明另一个过程
      • 例:C:过程中使用的数据要么是自身定义的局部数据,要么是在所有过程之外定义的全局数据

    无过程嵌套声明时的数据访问

    变量的存储分配和访问

    • 全局变量被分配在静态区,使用静态确定的地址访问它们
    • 其它变量一定是栈顶活动的局部变量。可以通过 运行时刻栈的top_sp指针访问它们

    有过程嵌套声明时的数据访问

    嵌套深度

    • 过程的嵌套深度
      • 不内嵌在任何其它过程中的过程,设其嵌套深度为1
      • 如果一个过程p在一个嵌套深度为i的过程中定义,则设定p的嵌套深度为i +1
    • 变量的嵌套深度
      • 将变量声明所在过程的嵌套深度作为该变量的嵌套深度

    访问链 (Access Links)

    静态作用域规则:只要过程b的声明嵌套在过程a的声明中,过程b就可以访问过程a中声明的对象

    • 可以在相互嵌套的过程的活动记录之间建立一种称 为访问链(Access link)的指针,使得内嵌的过程可以访 问外层过程中声明的对象
      • 如果过程b在源代码中直接嵌套在过程a中(b的嵌套深度 比a的嵌套深度多1),那么b的任何活动中的访问链都指向最近的a的活动

    访问链的建立

    建立访问链的代码属于调用序列的一部分

    假设嵌套深度为nx的过程x调用嵌套深度为ny的过程y (x→y)

    • nx < ny 的情况(外层调用内层)
      • y一定是直接在x中定义的 (例如:s→q, q→p) ,因此,ny=nx +1
      • 在调用代码序列中增加一个步骤:在y的访问链 中放置一个指向x的活动记录的指针
    • n =n的情况(本层调用本层)
      • 递归调用(例如: q→q )
      • 被调用者的活动记录的访问链与调用者的活 动记录的访问链是相同的,可以直接复制
    • nx > ny的情况(内层调用外层,如: p→e )
      • 过程x必定嵌套在某个过程z中,而z中直接定义了过程y
      • 从x的活动记录开始,沿着访问链经过nx - ny + 1 步就可以找到离栈顶最近的z的活动记录。y的访问链必须指向z的这个活动记录

    堆式存储分配

    堆式存储分配

    • 堆式存储分配是把连续存储区域分成块,当活动记录或其它对象需要时就分配
    • 块的释放可以按任意次序进行,所以经过一段时间后, 对可能包含交错的正在使用和已经释放的区域

    符号表

    符号表的组织:为每个作用域(程序块)建立一个独立的符号表

    根据符号表进行数据访问

    在这里插入图片描述

    实际上,这种为每个过程或作用域建立的符号表与编译时的活动记录是对应的。一个过程的非局部名字的信息可以通过扫描外围过程的符号表而得到

    标识符的基本处理方法

    • 当在某一层的声明语句中识别出一个标识符(id的定义性出 现)时,以此标识符查相应于本层的符号表
      • 如果查到,则报错并发出诊断信息“id重复声明”
      • 否则,在符号表中加入新登记项,将标识符及有关信息填入
    • 当在可执行语句部分扫视到标识符时( id的应用性出现)
      • 首先在该层符号表中查找该id,如果找不到,则到直接外层 符号表中去查,如此等等,一旦找到,则在表中取出有关信 息并作相应处理
      • 如果查遍所有外层符号表均未找到该id,则报错并发出诊断 信息“id未声明”

    符号表的建立

    在这里插入图片描述

    红色箭头在建表的时候完成,蓝色箭头在表建好之后完成。

    展开全文
  • 编译原理课程设计 for循环 用LR法实现 中间代码用三元式表示
  • 编译原理课设

    2013-01-16 16:02:10
    编译原理课程设计WHILE循环语句的翻译程序设计(递归下降法、输出四元式
  • 编译原理课程设计 do-while 循环语句的实现 LR分析法实现 输出四元式 do-while 循环语句
  • while语句循环(LR法)源代码 三地址输出 编译原理课程设计
  • 系统给定一个文法来实现do A until B 语句中的A和B,在用户输入A和B语句后,根据文法对until语句进行词法分析和语法分析,如果分析无误,系统就会输出符合语法要求的四元式。
  • 编译原理与操作系统——循环首次适应算法 (C语言)
  • 编译原理课程设计,for循环语句的实现简单优先法,三地址码输出
  • 编译原理书籍推荐

    千次阅读 多人点赞 2018-09-28 13:44:20
    大学课程为什么要开设编译原理呢?这门课程关注的是编译器方面的产生原理和技术问题,似乎和计算机的基础领域不沾边,可是编译原理却一直作为大学本科的必修课程,同时也成为了研究生入学考试的必考内容。编译原理及...

    大学课程为什么要开设编译原理呢?这门课程关注的是编译器方面的产生原理和技术问题,似乎和计算机的基础领域不沾边,可是编译原理却一直作为大学本科的必修课程,同时也成为了研究生入学考试的必考内容。编译原理及技术从本质上来讲就是一个算法问题而已,当然由于这个问题十分复杂,其解决算法也相对复杂。我们学的数据结构与算法分析也是讲算法的,不过讲的基础算法,换句话说讲的是算法导论,而编译原理这门课程讲的就是比较专注解决一种的算法了。在20世纪50年代,编译器的编写一直被认为是十分困难的事情,第一Fortran的编译器据说花了18年的时间才完成。在人们尝试编写编译器的同时,诞生了许多跟编译相关的理论和技术,而这些理论和技术比一个实际的编译器本身价值更大。就犹如数学家们在解决著名的哥德巴赫猜想一样,虽然没有最终解决问题,但是其间诞生不少名著的相关数论。

    推荐参考书

    虽然编译理论发展到今天,已经有了比较成熟的部分,但是作为一个大学生来说,要自己写出一个像Turboc C,Java那样的编译器来说还是太难了。不仅写编译器困难,学习编译原理这门课程也比较困难。

    正是因为编译原理学习相对困难,那么就要求有好的教师和好的教材。教师方面不是我们能自己更改的,而在教材方面我们却可以按自己的意愿来阅读。我下面推荐几本好的编译原理的教材。我推荐的书籍都是国外的经典教材,因为在国内的教材中,确实还没发现什么让人满意的。

    第一本书的原名叫《Compilers Principles,Techniques,and Tools》,另外一个响亮的名字就是龙书。原因是这本书的封面上有条红色的龙,也因为獗臼樵诒嘁朐?砘?×煊蛉肥堤?忻???所以很多国外的学者都直接取名为龙书。最近机械工业出版社已经出版了此书的中文版,名字就叫《编译原理》。该书出的比较早,大概是在85或86年编写完成的,作者之一还是著名的贝尔实验室的科学家。里面讲解的核心编译原理至今都没有变过,所以一直到今天,它的价值都非凡。这本书最大的特点就是一开始就通过一个实际的小例子,把编译原理的大致内容罗列出来,让很多编译原理的初学者很快心里有了个底,也知道为什么会有这些理论,怎么运用这些理论。而这一点是我感觉国内的教材缺乏的东西,所以国内的教材都不是写给愿意自学的读者,总之让人看了半天,却不知道里面的东西有什么用。

    第二本书的原名叫《Modern Compiler Design》,中文名字叫做《现代编译程序设计》。该书由人民邮电出版社所出。此书比较关注的是编译原理的实践,书中给出了不少的实际程序代码,还有很多实际的编译技术问题等等。此书另外一个特点就是其“现代”而字。在传统的编译原理教材中,你是不可能看到如同Java中的“垃圾回收”等算法的。因为Java这样的解释执行语言是在近几年才流行起来的东西。如果你想深入学习编译原理的理论知识,那么你肯定得看前面那本龙书,如果你想自己动手做一个先进的编译器,那么你得看这本《现代编译程序设计》。

    第三本书就是很多国内的编译原理学者都推荐的那本《编译原理及实践》。或许是这本书引入国内比较早吧,我记得我是在高中就买了这本书,不过也是在前段时间才把整本书看完。此书作为入门教程也的确是个不错的选择。书中给出的编译原理讲解也相当细致,虽然不如前面的龙书那么深入,但是很多地方都是点到为止,作为大学本科教学已经是十分深入了。该书的特点就是注重实践,不过感觉还不如前面那本《现代编译程序设计》的实践味道更重。此书的重点还是在原理上的实践,而非前面那本那样的技术实践。《编译原理及实践》在讲解编译原理的各个部分的同时,也在逐步实践一个现代的编译器Tiny C.等你把整本书看完,差不多自己也可以写一个Tiny C了。作者还对Lex和Yacc这两个常用的编译相关的工具进行了很详细的说明,这一点也是很难在国内的教材中看到的。

    推荐了这三本教材,都有英文版和中文版的。很多英文好的同学只喜欢看原版的书,不我的感觉是这三本书的翻译都很不错,没有必要特别去买英文版的。理解理论的实质比理解表面的文字更为重要。

    编译原理的实质

    前面已经说过,学习编译原理其实也就是学习算法而已,没什么特别的。只不过这些算法的产生已经形成了一套理论。下面我来看看编译原理里面到底有什么高深的理论吧。

    几乎每本编译原理的教材都是分成词法分析,语法分析(LL算法,递归下降算法,LR算法),语义分析,运行时环境,中间代码,代码生成,代码优化这些部分。其实现在很多编译原理的教材都是按照85,86出版的那本龙书来安排教学内容的,所以那本龙书的内容格式几乎成了现在编译原理教材的定式,包括国内的教材也是如此。一般来说,大学里面的本科教学是不可能把上面的所有部分都认真讲完的,而是比较偏重于前面几个部分。像代码优化那部分东西,就像个无底洞一样,如果要认真讲,就是单独开一个学期的课也不可能讲得清楚。所以,一般对于本科生,对词法分析和语法分析掌握要求就相对要高一点了。

    词法分析相对来说比较简单。可能是词法分析程序本身实现起来很简单吧,很多没有学过编译原理的人也同样可以写出各种各样的词法分析程序。不过编译原理在讲解词法分析的时候,重点把正则表达式和自动机原理加了进来,然后以一种十分标准的方式来讲解词法分析程序的产生。这样的做法道理很明显,就是要让词法分析从程序上升到理论的地步。

    语法分析部分就比较麻烦一点了。现在一般有两种语法分析算法,LL自顶向下算法和LR自底向上算法。LL算法还好说,到了LR算法的时候,困难就来了。很多自学编译原理的都是遇到LR算法的理解成问题后就放弃了自学。其实这些东西都是只要大家理解就可以了,又不是像词法分析那样非得自己写出来才算真正的会。像LR算法的语法分析器,一般都是用工具Yacc来生成,实践中完全没有比较自己来实现。对于LL算法中特殊的递归下降算法,因为其实践十分简单,那么就应该要求每个学生都能自己写。当然,现在也有不少好的LL算法的语法分析器,不过要是换在非C平台,比如Java,Delphi,你不能运用YACC工具了,那么你就只有自己来写语法分析器。

    等学到词法分析和语法分析时候,你可能会出现这样的疑问:“词法分析和语法分析到底有什么?”就从编译器的角度来讲,编译器需要把程序员写的源程序转换成一种方便处理的数据结构(抽象语法树或语法树),那么这个转换的过程就是通过词法分析和语法分析的。其实词法分析并非一开始就被列入编译器的必备部分,只是我们为了简化语法分析的过程,就把词法分析这种繁琐的工作单独提取出来,就成了现在的词法分析部分。除了编译器部分,在其它地方,词法分析和语法分析也是有用的。比如我们在DOS,Unix,Linux下输入命令的时候,程序如何分析你输入的命令形式,这也是简单的应用。总之,这两部分的工作就是把不“规则”的文本信息转换成一种比较好分析好处理的数据结构。那么为什么编译原理的教程都最终把要分析的源分析转换成“树”这种数据结构呢?数据结构中有Stack, Line,List…这么多数据结构,各自都有各自的特点。但是Tree这种结构有很强的递归性,也就是说我们可以把Tree的任何结点Node提取出来后,它依旧是一颗完整的Tree。这一点符合我们现在编译原理分析的形式语言,比如我们在函数里面使用函树,循环中使用循环,条件中使用条件等等,那么就可以很直观地表示在Tree这种数据结构上。同样,我们在执行形式语言的程序的时候也是如此的递归性。在编译原理后面的代码生成的部分,就会介绍一种堆栈式的中间代码,我们可以根据分析出来的抽象语法树,很容易,很机械地运用递归遍历抽象语法树就可以生成这种指令代码。而这种代码其实也被广泛运用在其它的解释型语言中。像现在流行的Java,.NET,其底层的字节码bytecode,可以说就是这中基于堆栈的指令代码的。

    关于语义分析,语法制导翻译,类型检查等等部分,其实都是一种完善前面得到的抽象语法树的过程。比如说,我们写C语言程序的时候,都知道,如果把一个浮点数直接赋值给一个整数,就会出现类型不匹配,那么C语言的编译器是怎么知道的呢?就是通过这一步的类型检查。像C++语言这中支持多态函数的语言,这部分要处理的问题就更多更复杂了。大部编译原理的教材在这部分都是讲解一些比较好的处理策略而已。因为新的问题总是在发生,旧的办法不见得足够解决。

    本来说,作为一个编译器,起作用的部分就是用户输入的源程序到最终的代码生成。但是在讲解最终代码生成的时候,又不得不讲解机器运行环境等内容。因为如果你不知道机器是怎么执行最终代码的,那么你当然无法知道如何生成合适的最终代码。这部分内容我自我感觉其意义甚至超过了编译原理本身。因为它会把一个计算机的程序的运行过程都通通排在你面前,你将来可能不会从事编译器的开发工作,但是只要是和计算机软件开发相关的领域,都会涉及到程序的执行过程。运行时环境的讲解会让你更清楚一个计算机程序是怎么存储,怎么装载,怎么执行的。关于部分的内容,我强烈建议大家看看龙书上的讲解,作者从最基本的存储组织,存储分配策略,非局部名字的访问,参数传递,符号表到动态存储分配(malloc,new)都作了十分详细的说明。这些东西都是我们编写平常程序的时候经常要做的事情,但是我们却少去探求其内部是如何完成。

    关于中间代码生成,代码生成,代码优化部分的内容就实在不好说了。国内很多教材到了这部分都会很简单地走马观花讲过去,学生听了也只是作为了解,不知道如何运用。不过这部分内容的东西如果要认真讲,单独开一学期的课程都讲不完。在《编译原理及实践》的书上,对于这部分的讲解就恰到好处。作者主要讲解的还是一种以堆栈为基础的指令代码,十分通俗易懂,让人看了后,很容易模仿,自己下来后就可以写自己的代码生成。当然,对于其它代码生成技术,代码优化技术的讲解就十分简单了。如果要仔细研究代码生成技术,其实另外还有本叫做《Advance Compiler Desgin and Implement》,那本书现在由机械工业出版社引进的,十分厚重,而且是英文原版。不过这本书我没有把它列为推荐书给大家,毕竟能把龙书的内容搞清楚,在中国已经就算很不错的高手了,到那个时候再看这本《Advance Compiler Desgin and Implement》也不迟。代码优化部分在大学本科教学中还是一个不太重要的部分,就是算是实践过程中,相信大家也不太运用得到。毕竟,自己做的编译器能正确生成执行代码已经很不错了,还谈什么优化呢?

    关于实践

    编译原理的课程毕竟还只是讲解原理的课程,不是专门的编译技术课程。这两门课程是有很大的区别的。编译技术更关注实际的编写编译器过程中运用到的技术,而原理的课关注讲解其基本理论。但是计算机科学本身就是一门实践性很强的课程,如果能够学以致用,那才叫真正的学会。李阳在讲解疯狂英语的时候就说到,只要当你会实际中运用一个单词一个词组的时候你才能叫学会了这个单词或者词组,而不是只是知道了它的拼写和意思。其实任何学习都是一样的,如果缺少了实践的结合,你不能算学会。

    编译原理的课程主要就是讲解编译器产生的理论和原理,那么很简单,自己写个编译器就是最好的实践过程了。不过你得小心,编译系统可能是所有软件系统中最复杂的系统之一,不然为什么大学里面还会把编译器的编写开成一门叫做编译原理的课程来讲?我很佩服那些学了操作系统原理就开始自己写操作系统,学了编译原理就开始自己写编译器的人们,确实,在中国,敢这么做的学生太少了。且不管你这样做能不能做成功,至少有了这个尝试,会让你的程序设计,系统规划安排的功底增进不少。我下面给出一些关于实践过程中可能会遇到的困难,希望能够在你陷入困境的前帮你一把。

    1. Lex和Yacc. 这两工具是作为词法分析很语法分析的工具。如果你自己写一个编译器,我十分不建议你连词法分析这种事情都亲手来写。Lex和Yacc应该是作为每本编译原理的教材的必备内容,可是在国内的教材中缺很少看到。这两个工具是Unix系统下的小东西,如果你要在Windows中运用,那么你最好去下在cygwin这个软件。它是个在Windows下模拟Unix的东东,里面就包含了flex.exe和bison.exe(yacc)这两个工具.这两个工具使用起来还挺麻烦的(其实unix 下的很多十分有用的工具都是这样), 不过在《编译原理与实践》这本书上对于这两个工具的讲解十分详细,还列举了不少实际的例子。

    2. 做解释型语言比做生成机器代码的编译器简单。虽然说,做解释型的编译器,像Java那样的,你还得自己去写解释器,不过这样你就不必去查找机器代码的资料了。如果你做生成的最终机器代码编译器可能会遇到问题还有就是寄存器为基础的代码生成方法。前面说过,如果你生成的是以堆栈为基础的代码,那么其代码生成过程十分简单,需要考虑的东西也不多,如果你考虑最终的机器代码生成的话,你必须考虑机器的寄存器如何分配等麻烦的问题。

    3. 考虑用别人已经生成的语法文件,尽量不要自己动手写词法文件和语法文件.以前一个朋友曾经说过,写出一个好的程序语言的语法定义,就几乎完成了一个编译器的一半.确实是这样,语法文件的编写是个很难的事情.现在网上到处都可以找到比如C语言,C++,Java, Tiny C,Minus C等语言的词法文件和语法文件,你完全可以自己下下来来用.

    在《编译原理及实践》的书中,作者给出了一个Tiny C的全部代码.我自我感觉作者的这个编译器做得很不错,相对于其它php,perl等语言的源代码来说,简单得多,容易看懂,而且很清晰地展现了一个完成的编译系统的实现过程.其源代码可以在作者的网站上下载

    转载自:http://blog.csdn.net/aben_2005/article/details/6445134

    --------------------- 本文来自 liuzhushiqiang 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/liuzhushiqiang/article/details/14002665?utm_source=copy

    展开全文
  • WHILE循环语句的递归下降翻译程序设计编译原理课设加论文
  • 编译原理课程设计--WHILE循环语句的翻译程序设计程序代码

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 137,483
精华内容 54,993
关键字:

找循环编译原理