编译器设计(二)——上下文无关文法及推导

一、语法和语义

什么是语法?

程序本质上是一定字符集上的字符串,这些字符串描述了一定数据的处理过程,语法规则和词法规则定义了程序的形式结构。所以呢,语法就是一组规则,用它可以形成和产生一个合式(well-formed)的程序。

词法规则:单词符号的形成规则

  • 单词符号是语言中具有独立意义的最基本结构,一般包括:常数、标识符、基本字、算符、界符等。
  • 描述工具:有限自动机

语法规则:语法单位的形成规则

  • 语法单位通常包括:表达式、语句、分程序、过程、函数、程序等。
  • 描述工具:上下文无关文法

什么是语义?

语义同样也是是一组规则,用它可以定义一个程序的意义。描述语义的方法有自然语言描述和形式描述,但自然语言描述具有二义性、隐藏错误和不完整性。

二、文法

文法用来描述语言的语法结构的形式规则。

举一个文法处理自然语言的例子,He gave me a book.这是一个句子,而一个句子的文法的规则如下:
在这里插入图片描述
所以经过下面的推导过程,就可以得到句子He gave me a book。
在这里插入图片描述

三、语法描述的几个基本概念

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

四、上下文无关文法

上下文无关文法G是一个四元组 G = (VT,VN,S,P),其中
在这里插入图片描述
在这里插入图片描述
上面是上下文无关文法的产生式形式,上下文无关文法取名为“上下文无关”的原因就是因为字符P 总可以被字串 a(阿尔法) 自由替换,而无需考虑字符 P 出现的上下文。比如根据下面上下文文法规则,<句子>只要出现,就能被<主语><谓语><间接宾语><直接宾语>替换;<主语>只要出现,就可以被<代词>替换;而这些替换不需要考虑句子出现在什么地方,主语出现在什么地方,也就是不考虑文法的上下文。
在这里插入图片描述

举一个例子,定义只含+*的算术表达式的文法 G=< {i,+,*,(,)},{E},E, P >, 其中,P由下列产生式组成:

在这里插入图片描述

五、文法推导

推导过程:对每一个句型(句型的接受在后面),该句型一定有一个推导过程(可能不唯一),推导过程一定对应一颗语法树(语法树也可能不唯一,语法树不唯一就说明文法具有二义性)。下面是推导的定义:
在这里插入图片描述

下面有+的读作按非平方方式派生,表示至少经过一步推导;有*读作派生,表示至少经过0步推导。
在这里插入图片描述

句型和句子:只要是一个推导的右部,都可以称为句型;句子是一个特殊的句型,只含有终结符。
在这里插入图片描述

最左推导和最右推导

  • 最左推导:每步推导中只改写最左边的那个非终结符
  • 最右推导:每步推导中只改写最右边的那个非终结符,又称规范推导
    在这里插入图片描述

从一个句型到另一个句型的推导往往不唯一,如下面第一个是最左推导,第二个是最右推导:
在这里插入图片描述

下面是一个文法的推导过程:
在这里插入图片描述

六、语法树与二义性(ambiguity)

文法二义性也就是,根据一个文法推导推导文法的某个句子时,会产生两个或以上个不同的语法树,就说这个文法具有二义性。
在这里插入图片描述
下面是根据文法G(E)推到出来的两个不同的语法树:
在这里插入图片描述

七、乔姆斯基形式语言体系

乔姆斯基于1956年建立形式语言体系,他把文法分成四种类型:0,1,2,3型
与上下文无关文法一样,它们都由四部分组成,但对产生式的限制有所不同。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

四种文法能力强弱的比较:
在这里插入图片描述

  • 6
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
第1章 概论 1 1.1 为什么要用编译器 2 1.2 与编译器相关的程序 3 1.3 翻译步骤 5 1.4 编译器中的主要数据结构 8 1.5 编译器结构中的其他问题 10 1.6 自举与移植 12 1.7 TINY样本语言与编译器 14 1.7.1 TINY语言 15 1.7.2 TINY编译器 15 1.7.3 TM机 17 1.8 C-Minus:编译器项目的一种语言 18 练习 19 注意与参考 20 第2章 词法分析 21 2.1 扫描处理 21 2.2 正则表达式 23 2.2.1 正则表达式的定义 23 2.2.2 正则表达式的扩展 27 2.2.3 程序设计语言记号的正则表达式 29 2.3 有穷自动机 32 2.3.1 确定性有穷自动机的定义 32 2.3.2 先行、回溯和非确定性自动机 36 2.3.3 用代码实现有穷自动机 41 2.4 从正则表达式到DFA 45 2.4.1 从正则表达式到NFA 45 2.4.2 从NFA到DFA 48 2.4.3 利用子集构造模拟NFA 50 2.4.4 将DFA中的状态数最小化 51 2.5 TINY扫描程序的实现 52 2.5.1 为样本语言TINY实现一个扫描 程序 53 2.5.2 保留字与标识符 56 2.5.3 为标识符分配空间 57 2.6 利用Lex 自动生成扫描程序 57 2.6.1 正则表达式的Lex 约定 58 2.6.2 Lex输入文件的格式 59 2.6.3 使用Lex的TINY扫描程序 64 练习 65 编程练习 67 注意与参考 67 第3章 上下文无关文法及分析 69 3.1 分析过程 69 3.2 上下文无关文法 70 3.2.1 与正则表达式比较 70 3.2.2 上下文无关文法规则的说明 71 3.2.3 推导及由文法定义的语言 72 3.3 分析树与抽象语法树 77 3.3.1 分析树 77 3.3.2 抽象语法树 79 3.4 义性 83 3.4.1 义性文法 83 3.4.2 优先权和结合性 85 3.4.3 悬挂else问题 87 3.4.4 无关紧要的义性 89 3.5 扩展的表示法:EBNF和语法图 89 3.5.1 EBNF表示法 89 3.5.2 语法图 91 3.6 上下文无关语言的形式特性 93 3.6.1 上下文无关语言的形式定义 93 3.6.2 文法规则和等式 94 3.6.3 乔姆斯基层次和作为上下文无关 规则的语法局限 95 3.7 TINY语言的语法 97 3.7.1 TINY的上下文无关文法 97 3.7.2 TINY编译器的语法树结构 98 练习 101 注意与参考 104 第4章 自顶向下的分析 105 4.1 使用递归下降分析算法进行自顶向下 的分析 105 4.1.1 递归下降分析的基本方法 105 4.1.2 重复和选择:使用EBNF 107 4.1.3 其他决定问题 112 4.2 LL(1)分析 113 4.2.1 LL(1)分析的基本方法 113 4.2.2 LL(1)分析与算法 114 4.2.3 消除左递归和提取左因子 117 4.2.4 在LL(1)分析中构造语法树 124 4.3 First集合和Follow集合 125 4.3.1 First 集合 125 4.3.2 Follow 集合 130 4.3.3 构造LL(1)分析表 134 4.3.4 再向前:LL(k)分析程序 135 4.4 TINY语言的递归下降分析程序 136 4.5 自顶向下分析程序中的错误校正 137 4.5.1 在递归下降分析程序中的错误 校正 138 4.5.2 在LL(1)分析程序中的错误校正 140 4.5.3 在TINY分析程序中的错误校正 141 练习 143 编程练习 146 注意与参考 148 第5章 自底向上的分析 150 5.1 自底向上分析概览 151 5.2 LR(0)项的有穷自动机与LR(0)分析 153 5.2.1 LR(0)项 153 5.2.2 项目的有穷自动机 154 5.2.3 LR(0)分析算法 157 5.3 SLR(1)分析 160 5.3.1 SLR(1)分析算法 160 5.3.2 用于分析冲突的消除义性 规则 163 5.3.3 SLR(1)分析能力的局限性 164 5.3.4 SLR(k)文法 165 5.4 一般的LR(1)和LALR(1)分析 166 5.4.1 LR(1)项的有穷自动机 166 5.4.2 LR(1)分析算法 169 5.4.3 LALR(1)分析 171 5.5 Yacc:一个LALR(1)分析程序的 生成器 173 5.5.1 Yacc基础 173 5.5.2 Yacc选项 176 5.5.3 分析冲突与消除义性的规则 180 5.5.4 描述Yacc分析程序的执行 183 5.5.5 Yacc中的任意值类型 184 5.5.6 Yacc中嵌入的动作 185 5.6 使用Yacc生成TINY分析程序 186 5.7 自底向上分析程序中的错误校正 188 5.7.1 自底向上分析中的错误检测 188 5.7.2 应急方式错误校正 188 5.7.3 Yacc中的错误校正 189 5.7.4 TINY中的错误校正 192 练习 192 编程练习 195 注意与参考 197 第6章 语义分析 198 6.1 属性和属性文法 199 6.1.1 属性文法 200 6.1.2 属性文法的简化和扩充 206 6.2 属性计算算法 207 6.2.1 相关图和赋值顺序 208 6.2.2 合成和继承属性 212 6.2.3 作为参数和返回值的属性 219 6.2.4 使用扩展数据结构存储属性值 221 6.2.5 语法分析时属性的计算 223 6.2.6 语法中属性计算的相关性 226 6.3 符号表 227 6.3.1 符号表的结构 228 6.3.2 说明 230 6.3.3 作用域规则和块结构 232 6.3.4 同层说明的相互作用 236 6.3.5 使用符号表的属性文法的一个 扩充例子 237 6.4 数据类型和类型检查 241 6.4.1 类型表达式和类型构造器 242 6.4.2 类型名、类型说明和递归类型 246 6.4.3 类型等价 248 6.4.4 类型推论和类型检查 253 6.4.5 类型检查的其他主题 255 6.5 TINY语言的语义分析 257 6.5.1 TINY的符号表 258 6.5.2 TINY语义分析程序 259 练习 260 编程练习 264 注意与参考 264 第7章 运行时环境 266 7.1 程序执行时的存储器组织 266 7.2 完全静态运行时环境 269 7.3 基于栈的运行时环境 271 7.3.1 没有局部过程的基于栈的环境 271 7.3.2 带有局部过程的基于栈的环境 281 7.3.3 带有过程参数的基于栈的环境 284 7.4 动态存储器 286 7.4.1 完全动态运行时环境 286 7.4.2 面向对象的语言中的动态存储器 287 7.4.3 堆管理 289 7.4.4 堆的自动管理 292 7.5 参数传递机制 292 7.5.1 值传递 293 7.5.2 引用传递 294 7.5.3 值结果传递 295 7.5.4 名字传递 295 7.6 TINY语言的运行时环境 296 练习 297 编程练习 303 注意与参考 304 第8章 代码生成 305 8.1 中间代码和用于代码生成的数据 结构 305 8.1.1 三地址码 306 8.1.2 用于实现三地址码的数据结构 308 8.1.3 P-代码 310 8.2 基本的代码生成技术 312 8.2.1 作为合成属性的中间代码或目标 代码 312 8.2.2 实际的代码生成 314 8.2.3 从中间代码生成目标代码 317 8.3 数据结构引用的代码生成 319 8.3.1 地址计算 319 8.3.2 数组引用 320 8.3.3 栈记录结构和指针引用 325 8.4 控制语句和逻辑表达式的代码生成 328 8.4.1 if 和while 语句的代码生成 328 8.4.2 标号的生成和回填 330 8.4.3 逻辑表达式的代码生成 330 8.4.4 if 和while 语句的代码生成过程 样例 331 8.5 过程和函数调用的代码生成 334 8.5.1 过程和函数的中间代码 334 8.5.2 函数定义和调用的代码生成过程 336 8.6 商用编译器中的代码生成:两个案 例研究 339 8.6.1 对于80×86的Borland 3.0版C编 译器 339 8.6.2 Sun SparcStation的Sun 2.0 C编 译器 343 8.7 TM:简单的目标机器 346 8.7.1 Tiny Machine的基本结构 347 8.7.2 TM模拟器 349 8.8 TINY语言的代码生成器 351 8.8.1 TINY代码生成器的TM接口 351 8.8.2 TINY代码生成器 352 8.8.3 用TINY编译器产生和使用TM 代码文件 354 8.8.4 TINY编译器生成的TM代码文 件示例 355 8.9 代码优化技术考察 357 8.9.1 代码优化的主要来源 358 8.9.2 优化分类 360 8.9.3 优化的数据结构和实现技术 362 8.10 TINY代码生成器的简单优化 366 8.10.1 将临时变量放入寄存器 366 8.10.2 在寄存器中保存变量 367 8.10.3 优化测试表达式 367 练习 368 编程练习 371 注意与参考 372 附录A 编译器设计方案 373 附录B 小型编译器列表 381 附录C Tiny Machine模拟器列表 417
以下是一个简单的 3 型文法推导的 C 代码示例: ```c #include <stdio.h> #include <stdlib.h> #include <ctype.h> #include <string.h> // 定义终结符和非终结符 #define TERMINAL_A 'a' #define TERMINAL_B 'b' #define NON_TERMINAL_S 'S' #define NON_TERMINAL_T 'T' // 定义产生式规则 #define RULE_1 NON_TERMINAL_S "-> " NON_TERMINAL_T TERMINAL_A #define RULE_2 NON_TERMINAL_T "-> " TERMINAL_B NON_TERMINAL_T #define RULE_3 NON_TERMINAL_T "-> " "" // 定义结构体表示产生式 typedef struct Production { char lhs; // 产生式左部 char rhs[10]; // 产生式右部,最多 10 个字符 } Production; // 定义产生式数组 Production rules[] = { { NON_TERMINAL_S, NON_TERMINAL_T TERMINAL_A }, { NON_TERMINAL_T, TERMINAL_B NON_TERMINAL_T }, { NON_TERMINAL_T, "" } }; // 定义函数原型 int apply_rule(char lhs, char *input, char *output); int parse_input(char *input, char *output); int main() { char input[100]; char output[100] = { 0 }; printf("请输入一个只包含 a、b 的字符串:\n"); scanf("%s", input); if (parse_input(input, output)) { printf("推导成功,结果为:%s\n", output); } else { printf("推导失败!\n"); } return 0; } int apply_rule(char lhs, char *input, char *output) { int i, j, k; for (i = 0; i < sizeof(rules) / sizeof(Production); i++) { if (rules[i].lhs == lhs) { j = 0; k = 0; while (rules[i].rhs[j] != '\0') { if (isupper(rules[i].rhs[j])) { k = apply_rule(rules[i].rhs[j], input, output); if (k == -1) { return -1; } } else if (rules[i].rhs[j] == input[k]) { output[strlen(output)] = input[k]; k++; } else { return -1; } j++; } return k; } } return -1; } int parse_input(char *input, char *output) { int i = 0; int j = 0; while (input[i] != '\0') { if (input[i] == TERMINAL_A) { output[j++] = TERMINAL_A; i++; } else if (input[i] == TERMINAL_B) { i++; int k = apply_rule(NON_TERMINAL_T, input + i, output + j); if (k == -1) { return 0; } i += k; j += strlen(output + j); } else { return 0; } } if (apply_rule(NON_TERMINAL_S, output, output) == strlen(output)) { return 1; } return 0; } ``` 这个代码使用了递归下降分析法,它通过遍历产生式数组来找到符合条件的产生式,然后按照产生式的右部进行推导。为了处理左递归,这个代码使用了一个辅助函数 apply_rule,它可以递归调用自己来推导产生式。在函数 parse_input 中,我们遍历输入字符串,并逐个处理每个终结符。如果遇到终结符 a,我们就直接将其添加到输出字符串中。如果遇到终结符 b,我们就使用产生式 T -> bT 来进行推导。最后,我们使用产生式 S -> TA 来推导整个字符串。如果推导成功,输出字符串就是最终结果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yelvens

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值