Programming Language Pragmatics 习题解答(二)

可以在这里下载书籍笔记和答案。
欢迎star/fork参加我的编译器项目
这章的Lax和yacc也是很好用的词法/语法工具。

复习部分

2.1.1

语法和语义区别在哪里?
语法分为两部分:描述语言的结构规则,根据已有的规则构造程序。
用到的技术分别是:正则表达式、上下文无关语法 和 扫描器和语法分析器
语义是程序的含义,对于编译型语言也可以认为是对应的汇编语言。

2.1.2

扫描器和语法分析器作用是什么?
扫描器用于扫描出单词,并且删除注释;语法分析器是识别该语言上下文无关的语法的下推自动机。

2.2

kleene星号是什么?
*号就是正则里面的可重复0—任意次部分

2.3

用于从简单正则表达式构造复杂正则表达式的基本操作是哪三个?
拼接,选择和kleene星号

2.4

上下文无关文法提供的额外操作是什么?
使用递归定义保证了左右括号配对。

2.5

什么是Backus-Naur形式?
上下文无关文法的记法形式,为了定义Algol 60语言发明了这种形式。

2.6

当然是Python,写这本书的时候估计还没发明出来。

2.7

什么叫推导?什么叫句型?
由开始符号导出一个非终结符的串叫做推导;
这一过程中得到的各个符号串叫做句型。

2.8

最左推导和最右推导的区别在哪里?哪一个被称为规范推导?
用某个产生式的右部替换掉最右边的非终结符叫做最右推导,也被称为规范推导;
最左推导反过来。

2.9

上下文无关文法中什么叫歧义?
一个串有多个生成树叫歧义。

2.10

为什么扫描器必须保留单词正文?
因为需要向前探查、回退和检查。

2.11

什么是编译指示?
预处理指令。它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作。

2.12

为什么需要前窥下一个字符?
最大可能单词规则要求向前探查一定深度。

2.13

什么是结合性和优先级?
从程序设计语言的经验来看,
优先级就是有限计算的顺序。
同一优先级的运算符,运算次序由结合方向所决定。

2.14

LL和LR的区别是什么?哪一个是预测性的/移入预约的?LL和LR分别代表了什么?
LL解析是左到右,最左推导。也就是说,我们考虑输入符号从左侧到右侧,并试图建立一个最左推导。这是由开始的开始符号,并多次扩大了最左边的非终结,直到我们的目标字符串完成。LR语法分析是左到右,最右推导,这意味着我们从左至右扫描并尝试构建最右推导。解析器连续采输入的字符串,并试图扭转回一个非终端。
LL的两个关键操作是预测(Predict)和匹配(Match)
预测:基于最左边的非终结和一些前向令牌,选择应该应用哪个语法来更接近输入字符串。
匹配:将最左侧猜测的终结符号与最左侧未消耗的输入符号匹配。
LL(2) 解析器(使用两个前向标记):
在这里插入图片描述
在 LR 解析器中,有两个操作:
移位:将输入的下一个令牌添加到缓冲区进行考虑。
减少:通过反转production,将此缓冲区中的终结符和非终结符的集合变成某些非终结符。

LL是从左到右,最左推导;LR是从左到右,最右推导。

2.15

哪一类语法分析器在产品编译器中更普遍?
LL

2.16

yacc/bison生成的是哪一类语法分析器?
生成的是LALR分析器

2.17

自动生成的扫描器有哪些优势?为什么大部分产品是手工生成的扫描器?
为什么不用手工写出的语法分析器?
快捷方便,易于实现。
因为手工生成的扫描器更加高效。
逻辑复杂容易出错?(我一点也不确定)

2.18

什么是FIRST和FALLOW集合?什么用途?
FIRST(foo)集合就是以foo开始的所有单词的集合,FOLLOW(foo)则是在某些合法程序中可以出现在foo后面所有单词的集合。

2.19

请描述两种情况,其中上下文特定的follow集合可能有用。
首先,在构造预测分析表的时候有用。
预测分析表如果FIRST集合包含了空集#,那么就去找FOLLOW集合,把包含终结符的产生式放入格子(就是跳过了这条产生式的情况)
其次,在递归下降分析的时候,读入符号要做word in follow(A)的检查,如果符合,就跳过,否则报错。

2.20

请列举两种不能通过自上而下的方式分析的上下文无关语法形式。
非LL(1)文法
1.文法含有左递归
2.文法中每一个非终结符产生式中每一个候选式的FIRST集存在两两相交
3.如果文法中的每一个非终结符的FIRST集若含有ε,则存在一个或者以上候选式的FIRST集和该非终结符的FOLLOW集相交

2.21

什么是悬空的else问题?如何解决?
dangling else,else悬空会导致匹配问题
使用括号圈起来

2.22

解释在LR语法中特征有穷自动机的意义
LR语法本身就是一个有穷自动机(DFA),通过读入一个字符进行状态转移。

2.23

为了方便标记语法分析器读入了多少输入,引入一个点记号
左边是已经读入的,右边是未读入的。

2.24

根据什么区分基础和闭包?
最初的项称为这个表的基础,后来加入的项是它的闭包。

2.25

一个右句型的句柄是什么?
将下一步的反向推导所需要的符号称为其句柄。

练习部分

2.1

我认为区分大小写的方法比较好。因为区分大小写可以将更少的时间放在给实例命名上面。可以使用小写来创建实体类,类的命名则用大写。

2.2

我觉得缩进表示逻辑有害于程序的构造和维护。
很明显,它难以表示复杂的逻辑,比如多重判断,循环等等。

2.3

正则表达式是个好东西,用处广泛。但是在语法分析里面,我们无法写出一个RE,来匹配括号左右平衡的所有表达式,所以词法分析器可以用RE做,语法分析还得靠上下文无关语法。

匹配C字符串

(.|(\\\"))*

匹配Pascal注释
注意()*都是保留字

\(\*.*\*\)

匹配Ada浮点常量

首先匹配digit:
0|1|2|3|4|5|6|7|8|9
然后是unsigned_integer:
(0|1|2|3|4|5|6|7|8|9)(0|1|2|3|4|5|6|7|8|9)*
必须前面放一个,因为会匹配空串 也可以这样
(0|1|2|3|4|5|6|7|8|9)+
Pascal的unsigned_number:
(0|1|2|3|4|5|6|7|8|9)+(.(0|1|2|3|4|5|6|7|8|9)+)|()((e(+|-|())(0|1|2|3|4|5|6|7|8|9)+)|())

2.4

见2.3

2.5

^(?:(?!file).)+(?:(?!from).)+(?:(?!for).)$

使用捕获和零宽断言完成,非常麻烦
有否定运算符+字符串捕获就不用使用零宽断言了

2.6

a)
看起来好像是由a和b组成的任意字符串
b)在这里插入图片描述
c)是LL(1)
我的程序给出的预测分析表如下
在这里插入图片描述

2.7

grep支持正则,具体可以参考:
https://linux.cn/article-6941-1.html

2.8

为正文中的计算机语言写一个扫描器。
下面是我使用FLEX写的简单扫描器:

%{
	#include<stdio.h>
	#include<stdlib.h>
	int space=0;
%}

SPACE [ \n\r\t]
IO read|write
DIGIT [0-9]
INTEGER [1-9]{DIGIT}*
FLOAT [0-9]*[.][0-9]+([eE][+-]?[0-9]*|[0])?f?
ADD_OP \+|\-
MULT_OP \*|\/
ASSIGMENT :=
USER_DEFINE [a-zA-Z][a-zA-Z0-9]*

%%

{SPACE} {++space;}
{IO} {printf("get IO: %s\n", yytext);}
{DIGIT} {printf("get DIGIT: %s\n", yytext);}
{INTEGER} {printf("get INTEGER: %s\n", yytext);}
{FLOAT} {printf("get FLOAT: %s\n", yytext);}
{ADD_OP} {printf("get ADD_OP: %s\n", yytext);}
{MULT_OP} {printf("get MULT_OP: %s\n", yytext);}
{ASSIGMENT} {printf("get ASSIGMENT: %s\n", yytext);}
{USER_DEFINE} {printf("get USER_DEFINE: %s\n", yytext);}

%%
int yywrap(){
	return 1;
}
int main(int argc,char** argv){
  if(argc>1){
    if(!(yyin=fopen(argv[1],"r"))){
      perror(argv[1]);
      return 1; 
    }
  }
	while(yylex());
	printf("Get space total: %d\n", space);
	return 0;
}

对于正文中的输入:

read A
read B
sum := A + B
write sum
write sum / 2

输出如下:
在这里插入图片描述
flex是很好用的文本处理工具(就是全文正则匹配 )和词法分析器生成器,值得学习
可以参考https://www.cs.ccu.edu.tw/~naiwei/cs5605/LexFlex.html或者Google flex官网

2.9

正则匹配太难了!
我只是实现了一部分的功能,因为很难表示“除了annotation和string之外的所有字符串”
相比之下,替换倒是很简单。
建议参考网站:https://epaperpress.com/lexandyacc/prl.html
程序如下:

%{
	#include<stdio.h>
	#include<stdlib.h>
	int space=0;
%}

SPACE [ \n\r\t]
ANNOTATION \{.*\}|\(\*.*\*\)
STRING \'.*\'
OTHYER [^(?:(?!\{.*\}).)+$]

%%

{ANNOTATION} {printf("This is an annotation.\n");}
{SPACE} {++space;}
{STRING}	{printf("This is a string.\n");}
{OTHYER} {
	for(int i=0;i<yyleng;i++){
		if(*(yytext+i)>='A'&&*(yytext+i)<='Z')
				*(yytext+i)=*(yytext+i)+32;
	}
	ECHO;
}

%%
int yywrap(){
	return 1;
}
int main(int argc,char** argv){
  if(argc>1){
    if(!(yyin=fopen(argv[1],"r"))){
      perror(argv[1]);
      return 1; 
    }
  }
	yyout=fopen("out.txt","ra+");
	while(yylex());
	printf("Get space total: %d\n", space);
	return 0;
}

输入如下:

{实际上是这么一个工具:}
(*忽略例如本行和上一行的注释和字符串,而将其余字母转换为小写*)
'This is string'
Test
AnoTeST
FINALTEST

输出如下:

testanotestfinaltestotestfinaltest

2.10

见上一题

2.11

比如JS:

let squared = 2 ** 2 ** 3;

因为右结合性,所以是256

2.12

2+ 6 * 3 + 1 / 5;
很简单,略过

2.13

构造语法分析器,见我的项目
https://github.com/ztreble/MyCompiler

2.14

应该改为
每个非终结符都能推出注释的形式
// ### 2.15

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值