编译原理:实现简单数学公式排版

这是大三下上编译原理时老师要求做的大作业

1、实验具体要求[题目和测试程序来自 李卫海老师:http://staff.ustc.edu.cn/~whli]

一、         符号集

小写字母a、b、c、d、e、f、g、h、i、j、l、m、n、o、p、q、r、s、t、u、v、w、x、y、z

数字0、1、2、3、4、5、6、7、8、9

特殊符号:\、(、)、{、}、_、^、$

分隔符:空格、换行

二、         记号定义

标识符:由字母开始,由字母和数字组成

数字:无符号整型数

三、         语言规则

句子:

$$α$$

上下标:

上标^{α},下标_{α},上下标同时有用_^{α}{β}其中α是下标,β是上标。

括号:

()表示自己

空格:

\blank

四、         基础文法

Sà$$B$$

BàTB|T

TàR_^{B}{B}

TàR^{B}

TàR_{B}

TàR

Ràid | num | \blank | (B)

五、         输入输出

1. 输入:文本文件,文件名:sample??.txt,一个输入文件中仅包含一个公式。公式后的内容可忽略。

2. 输出:文本文件,文件名:typeset??.txt

3. 样例:

(1):

sample01.txt

$$a^{2}$$

typeset01.txt

240,250,30,a

260,265,20,2

(2):

sample02.txt

$$a_^{c2}{b}$$

typeset02.txt

240,250,30,a

260,235,20,c2

260,265,20,b

六、         题目要求

1、编程语言及环境:

(1)编程语言和环境不限,可以用lex和yacc工具,也可以用java、matlab、c等高级语言。

(2)画布大小500*500点,字体大小可自定义。

2、测试环境:

Windows,不含任何工具包。

如不能保证在该环境下使用,请自己准备笔记本和环境。

提供排版测试程序(matlab),用于测试输出文件是否达到要求。如使用自己的笔记本,请安装matlab,并将排版测试程序准备好。或者也可以自己编写同等功能的测试程序(注意,不得将测试程序与编译器程序合并)。

测试样例中有合法的、也有错误的。

2、词法分析的正规式和确定的有限自动机

正规式如下:

(1)     char → [a-z]

(2)     digit → [0-9]

(3)     num → digit(digit)*

(4)     id → char(char|digit)*

相应的确定有限自动机图如下图1-1和图1-2所示:

3、语法分析器的伪代码

1 voidstart_S(){2 /*S() 函数对应于文法"S->$$B$$"*/

3 match($);4 match($);5 B(start_x,start_y,start_ps); /*初始值*/

6 match($);7 match($);8 }9

10 int B(int x,int y, intps){11 /*B()函数对应于文法 ""B->TB|T" 输入参数 x:公式开始的横坐标;y:公式开始的纵坐标;ps:公式字体大小 返回值为表达式公式的结束横坐标*/

12 finish_x =T(x, y, ps);13 if(lookahead == |id|num|\blank|){14 endx =B(finish_x,y,ps);15 }16 returnfinish_x;17 }18

19 int T(int x, int y, intps){20 /*T() 函数对应于文法21 T -> R_^{B}{B}22 T -> R^{B}23 T -> R_{B}24 T -> R25 */

26 finish_x =R(x,y,ps);27 if(lookahead == "_"){28 match("_");29 if(lookahead == "^"){30 match("^");31 match("{");32 finish_x1 = B(sub(x, y, ps)); /*sub 为下标时调用 处理x,y,ps*/

33 match("}");34 mathc("{");35 finish_x2 = B(sup(x, y, ps)); /*sup 为下标时调用 处理x,y,ps*/

36 match("}");37 returnmax{finish_x1,finish_x2};38 }39 else{40 match("{");41 finish_x =B(sub(x, y, ps));42 match("}");43 }44 else if(lookahead == "^"){45 match("^");46 match("{");47 finish_x =B(sup(x, y, ps));48 match("}");49 returnfinish_x;50 }51 else{52 returnx;53 }54 }55 }56

57 int R(int x, int y, intps){58 /*R() 函数对应于文法 R -> id|num|\blank|(B)*/

59 if (lookahead ==id){60 match(id);61 return x+ps*length(id)*factor; //length(id)是计算id的长度 factor常数表示控制因子,用以自定义排版间距

62 }63 else if(lookahead ==num){64 match(num);65 return x+ps*length(num)*factor;66 }67 else if (lookahead == "\blank"){68 match("\blank");69 return x+ps*length("\blank")*factor;70 }71 else if(lookahead == "("){72 match("(");73 end_x =B(x, y, ps);74 match(")");75 returnend_x;76 }77 else{78 error();79 }80 }81

82 voidmatch(terminal t){83 /*match()函数用于匹配输入并取下一个记号*/

84 if(lookahead ==t){85 lookahead =nextToken();86 }87 else{88 error();89 }90 }

语法分析器的伪代码

4、 Python 实现的可运行代码

1 #-*- coding: utf-8 -*-

2 __author__ = 'YOU'

3

4

5 importsys6 importre7 inp = [] #是全局列表变量,记录输入的字符串

8

9

10 defs(init):11 '''s() 函数用于处理文法 "S -> $$B$$" 其中传入的参数init = {"x":init_x,"y":init_y,"ps":init_ps} 分别为公式的初始横坐标、纵坐标、字体大小'''

12 match1("$") #match1() 函数用以匹配相应记号,并将其弹出inp

13 match1("$") #匹配并弹出"$"

14 b(init) #调用 b()函数

15 match1("$") #匹配并弹出 “$”

16 match1("$") #匹配并弹出"$"

17

18

19 defb(a):20 '''b()函数用于处理文法 "B -> TB|T" 输入参数 a={"x":x,"y":y,"ps":ps}分别是记号B的起始横坐标、纵坐标以及字体大小;返回值是记号B的结尾横坐标'''

21 endx = t(a) #由文法 "B -> TB|T" 知需要先处理记号T,

22 #此处t()函数即是对记号T处理,返回记号T的结尾横坐标

23 if lookahead() in ["id", "num", r"\blank", "("]: #判断记号T后面是否还有记号B

24 a['x'] = endx #记号B的开始坐标是记号T的结尾坐标

25 endx = b(a) #处理B记号,返回记号B的结尾横坐标

26 returnendx27

28

29 deft(a):30 '''t()函数用于处理文法" T -> R_^{B}{B}|R^{B}|R_{B}|R" 输入参数 a={"x":x,"y":y,"ps":ps}分别是记号T的起始横坐标、纵坐标以及字体大小;返回值是记号T的结尾横坐标'''

31 endx = r(a) #由文法知需要先处理记号R,

32 #此处r()函数即是对记号R处理,返回记号R的结尾横坐标

33 a['x'] = endx #将T的结尾横坐标作为下一个记号的起始横坐标

34 if lookahead() == "_": #判断下一个输入是否为 "_"

35 match1("_") #匹配记号"_"

36 if lookahead() == "^": #判断下一个记号是否为 "^"

37 match1("^") #匹配记号 "^" 此时对应用文法 "T -> R_^{B}{B}" 处理

38 match1("{")39 endx1 = b(sub(a)) #由文法知需要处理B记号,故调用b()函数,

40 #因为此时B是下标,故调用sub()返回对应下标的参数

41 match1("}")42 match1("{")43 endx2 = b(sup(a)) #sup()返回上标的参数

44 match1("}")45 return max(endx1, endx2) #将上标和下标中的最大值结尾横坐标返回

46 else: #对应于应该按文法 "T -> R_{B}" 处理

47 match1("{")48 endx =b(sub(a))49 match1("}")50 returnendx51 elif lookahead() == "^": #判断下一个输入是否为 "^"

52 match1("^") #对应于文法 "T -> R^{B}"

53 match1("{")54 endx =b(sup(a))55 match1("}")56 returnendx57 else: #对应于文法"T -> R"

58 return endx #返回T的为坐标

59

60

61 defr(a):62 '''r()函数用于处理文法 "R -> id|num|\blank|(B)" 输入参数 a={"x":x,"y":y,"ps":ps}分别是记号R的起始横坐标、纵坐标以及字体大小;返回值是记号R的结尾横坐标'''

63 if lookahead() == "id": #如果下一个记号是id,则表示需要按文法"R ->id"处理

64 c = match2("id") #match2用来匹配id、num、\blank等终结符,

65 #返回c[0]该终结符由多少个字符组成,用来决定结尾横坐标,c[1]为该终结符

66 #id 是终结符,并将它的属性(x,y,ps,id)写入文件

67 fpw.write(str(int(a["x"]))+","+str(int(a["y"]))+","+str(int(a["ps"]))+","+c[1]+'\n')68 print "output:", int(a['x']), ",", int(a['y']), ",", int(a['ps']), ",", c[1] #输出id的各属性

69 return a['x'] + a['ps']*c[0]*0.5 #返回id的结尾横坐标作为r的结尾横坐标,0.5为调节排版因子

70 elif lookahead() == "num": #如果下个记号是num,则需按文法"R ->num"处理

71 c = match2("num") #将num的各属性写入文件

72 fpw.write(str(int(a["x"]))+","+str(int(a["y"]))+","+str(int(a["ps"]))+","+str(c[1])+"\n")73 print "output:", int(a['x']), ",", int(a['y']), ",", int(a['ps']), ",", c[1]74 return a['x'] + a['ps']*c[0]*0.55 #返回num的结尾横坐标作为R的结尾横坐标

75 elif lookahead() == r'\blank': #如果下一个记号是"\blank",按文法"R -> \blank"处理

76 c = match2(r"\blank")77 return a['x']+a['ps']*c[0]*0.6

78 elif lookahead() == "(": #如果下一个记号是"(",那文法"R -> (B)"处理

79 match1("(")80 endx =b(a)81 match1(")")82 returnendx83 else: #如果上面的都没匹配到,这说明输入发生错误

84 errlen = len(list2str(in_string))-len(list2str(inp)) #errlen计算错误在输入字符串中的发生位置

85 #输出错误信息

86 print ("ERROR: while get input char in line 1:"+str(errlen+1)+"\n"+ in_string+"\n"+" "*errlen + "^")87 exit(1) #退出程序

88

89

90 defsub(a):91 '''sub()函数用于求下标记号的参数,返回输入参数对应的下标的参数'''

92 b1 ={}93 b1['x'] = a['x'] #横坐标不变

94 b1['y'] = a['y'] -a['ps']*0.5 #纵坐标减小0.5×ps

95 b1['ps'] = a['ps']*0.6 #字体减小到 ps*0.6

96 return b1 #返回参数

97

98

99 defsup(a):100 '''sup函数用于求上记号的参数,返回输入参数对应的上标的参数'''

101 b1 ={}102 b1['x'] = a['x'] #横坐标不变

103 b1['y'] = a['y'] +a['ps']*0.5 #纵坐标增加0.5×ps

104 b1['ps'] = a['ps']*0.6 #字体变小到 0.6×ps

105 return b1 #返回参数

106

107

108 deflookahead():109 '''lookahead()函数向前搜索记号'''

110 a = inp[0] #a取剩余记号的第一个字符

111 if a in ("$", "^", "_", "(", ")","{","}"):112 return a #直接返回上述记号

113 elif re.match('[a-z]', a, 0): #若第一个字符是以字母开头,匹配到id

114 return "id"

115 elif re.match('[0-9]', a, 0): #若第一个字符是数字,则匹配到num

116 return"num"

117 elif list2str(inp[0:6]) == r"\blank": #若前6个字符是"\blank",则匹配到"\blank"

118 return r"\blank"

119

120

121 defmatch1(get_in):122 '''简单匹配记号 {、}、(、)、^、_'''

123 if get_in == lookahead(): #判断要匹配的记号与lookahead()一致,则匹配成功,从串中弹出该记号

124 a =inp.pop(0)125 else: #否则输出错误信息

126 errlen = len(list2str(in_string))-len(list2str(inp))127 print ("ERROR: while get input char in line 1:"+str(errlen+1)+"\n"+ in_string+"\n"+" "*errlen + "^")128 exit(1)129

130

131 defmatch2(get_in):132 '''匹配记号id、num、\blank'''

133 if get_in == "id": #如果是id

134 i = 1 #i 记录id字符的长度

135 ch =[] #ch 记录该id的组成

136 while re.match('[a-z0-9]', inp[0], 0): #当剩余字符串中的第一个字符在正规式[a-z0-9]中时,执行循环

137 ch.append(inp.pop(0)) #将inp中第一个字符加入ch中

138 i += 1 #ch中的字符个数加1

139 ch1 = ""

140 for j in ch: #将列表的ch转化为字符串类型返回

141 ch1 +=j142 return i, ch1 #返回id的长度和组成

143 elif get_in == "num": #如果要匹配的是id

144 i =1

145 ch =[]146 while re.match('[0-9]', inp[0], 0): #当剩余字符串中的第一个字符在正规式[0-9]中时,执行循环

147 ch.append(inp.pop(0))148 i += 1

149

150 ch1 = ""

151 for j inch:152 ch1 +=j153 return i, eval(ch1) #返回num的长度和组成

154 elif get_in == r"\blank": #若是要匹配\blank

155 for j in range(6): #直接从输入中弹出"\blank"

156 inp.pop(0)157 return 1, ' ' #返回 1长度的 空格

158

159

160 deflist2str(a):161 '''辅助用于输出函数,功能是将列表转化为字符串'''

162 str = ""

163 for i ina:164 str +=i165 returnstr166

167

168 if __name__ == '__main__':169 #主函数,程序运行的入口 命令行下运行需要传入两个参数

170 #in_file为输入文件路径 out_file 为输出文件路径

171 #如 python EQN.py input.txt output.txt

172 in_file = sys.argv[1] #获取输入文件路径

173 out_file = sys.argv[2] #获取输出文件路径

174 fpr = open(in_file, 'r') #输入文件

175 fpw = open(out_file, "w") #输出文件

176 in_string = fpr.read() #读取输入文件内容给in_string

177 file_name = in_file.split("\\")[-1]178 print "input_file:"+file_name179 print "input:", in_string180 inp.extend(list(in_string)) #将in_string 的值转化为列表添加到全局变量inp中

181 init = {'x': 200, 'y': 250, 'ps': 30} #初始的参数设定

182 s(init) #调用 s()函数开始执行文法

可运行代码及注释

5、matlab测试程序

1 function typeset(filename)2 MAX=500;3 scale=1/MAX;4 set(gcf,'color','w');5 set(gcf,'Position',[100 100MAX MAX]);6 set(gca,'Position',[0 0 1 1]);7 axis off;8

9 sf=fopen(filename,'r');10 line=1;11 while (~feof(sf))12 str=fgetl(sf);13 [par,n,~,p]=sscanf(str,'%f,%f,%f,',3);14 if (n<3)15 fprintf('line %d is error! Missing parameters.\n',line);16 line=line+1;17 continue;18 end19 x0=par(1); y0=par(2); fontsize=par(3);20 str=str(p:length(str));21 if(isempty(str))22 fprintf('line %d is error! Missing text.\n',line);23 line=line+1;24 continue;25 end26 str=substitute(str);27 x=x0*scale; y=y0*scale;28 set(text(x,y,str),'FontName','cour.ttf','FontSize',fontsize);29 line=line+1;30 end31 fclose(sf);32 end33

34 function [str]=substitute(str)35 sub={'##';'#blank';'#int';'#sum'};36 rep={'#';' ';'∫';'∑'};37 L=size(sub,1);38 for i=1:L39 str=strrep(str,sub{i},rep{i});40 end41 end

matlab测试程序

6、实验结果

(1)     正确样例

下面是正确的样例的输入输出以及相对应的排版结果。

(2)     错误样例

本程序能够发现错误并且提示是在输入字符串的那个位置出现了错误,用“ ^ ”指出具体位置具体输入以及显示如下所示。

7、体会

就说说自己的实验过程和体会吧,刚开始时准备用lex和yacc写的,也上网和到图书馆找了不少相关的资料,不过都没有找到用lex、yacc实现数学公式排版相关的。找到的都是用来实现计算器、识别SQL相关的;所以就只能自己根据一些lex、yacc知识动手写了,然而悲剧的是写好后能够编译为C代码,但是运行后就出错了;接着就是无聊的不断调试,也不知道到底哪里错了,只知道是在识别ID记号后返回yacc程序时就崩了。网上也找不到原因(关于这方面的资料真的好少),就转投高级语言实现了。我自己是用python写的,感觉高级语言写起来思路比较清楚,调试起来也很方便,所以我很快就写完了。

对比下我自己感觉的用yacc工具和高级语言实现的区别:

Yacc工具实现时更容易让你看到词法分析与语法分析的过程,以及各个记号的继承或综合属性之间的关系,与课本关系更近;缺点是对于不太熟悉yacc工具的人上手比较难。而用高级语言词法分析和语法分析之间的关系不明显,而各个记号之间的属性关系(继承、综合)也不明显,更多的是通过函数的参数传递和返回值的方式建立关联。

公式计算机实现,编译原理:实现容易数学公式排版相关推荐

  1. 编译原理公式计算器C语言,编译原理课程设计-简单计算器实现.doc

    编译原理课程设计-简单计算器实现 课程设计报告课程:编译原理学号:姓名:班级:11级嵌入式应用技术班教师:时间:2014年6月计算机科学与技术系 设计名称:简单计算器的实现设计内容.目的与要求:设计内 ...

  2. 《编译原理》实验教学大纲

    <编译原理>实验教学大纲 课程编号: 773033 课程名称:编译原理 英文名称:Compiler Principle 课程类型: 模块课 学 时:5 学 分:4 适用对象: 软件开发各专 ...

  3. 【编译原理】自上而下分析与LL文法

    初入语法分析. 语法分析是编译原理的核心部分,这一点从语法分析在编译原理教科书中所占的篇幅可见一斑,不论是陈火旺老师的<编译原理>还是Ullman等人的"龙书".语法分 ...

  4. 编译原理学习笔记2——高级程序设计语言概述

    编译原理学习笔记2--高级程序设计语言概述 2.1常用的高级程序设计语言 2.2程序设计语言的定义 2.2.1语法 2.2.1语法 2.2.3程序语言的基本功能和层次机构 2.2.4程序语言成分的逻辑 ...

  5. LaTeX 第五课:数学公式排版

    本期的主要内容是数学公式的排版,包含以下内容: 行内公式与行间公式 数学结构的输入 数学符号的输入 多行公式排版 数学公式的排版是 LaTeX 的看家本领,下面我们就来看看吧! --- 行内公式与行间 ...

  6. 从编译原理看一个解释器的实现

    『设计模式』中有一个模式可以解释特定的语法规则,它就是解释器模式(Interpreter Pattern).不同于常见的策略模式或者是工厂模式,解释器模式在.NET或者JDK中并不常见,而且在业务上也 ...

  7. 大前端开发者需要了解的基础编译原理和语言知识

    转自:https://yq.aliyun.com/articles/180879 在我刚刚进入大学,从零开始学习 C 语言的时候,我就不断的从学长的口中听到一个又一个语言,比如 C++.Java.Py ...

  8. 跟vczh看实例学编译原理——零:序言

    在<如何设计一门语言>里面,我讲了一些语言方面的东西,还有痛快的喷了一些XX粉什么的.不过单纯讲这个也是很无聊的,所以我开了这个<跟vczh看实例学编译原理>系列,意在科普一些 ...

  9. c++ 模板类实现堆栈实验报告_编译原理——小型类C编译器的设计和实现(生成8086汇编代码)之1:问题定义以及总体功能...

    前面花了两篇文章来介绍词法分析和语法分析,接下来才是比较有意思的部分--一个小型类C编译器的设计和实现(其实是编译原理的课程设计啦!~)我用的是python2.7.13+PyQt来做的...事实上,正 ...

最新文章

  1. 全球最大“同性”交友网站GitHub或被微软收购,收购价可能高达 50 亿美元
  2. 暗黑破坏神(背包)(内部模拟)
  3. Mybatis处理表关联(懒加载)
  4. for相关 java_Java学习之for循环相关知识梳理
  5. windows下缩短time_wait的时间
  6. 演练-使用xapth与lxml库进行操作-0223
  7. 【育儿日记2】兔子不要上幼儿园
  8. asp.net gridview添加删除确认对话框
  9. 多商户商城小程序源码有何优势?
  10. 微机综合保护装置怎么选择?在高压柜中起什么作用?
  11. 网站地图在线生成html,如何制作网站地图(sitemap.html和sitemap.xml)?
  12. Vue 下载本地静态资源
  13. aspen怎么做灵敏度分析_灵敏度分析 aspen
  14. 经典对抗攻击Deepfool原理详解与代码解读
  15. SCCM 2012 R2部署,安装SCCM(四)
  16. Java游戏吉他英雄_《吉他英雄》全系列十作美版下载
  17. 苹果cms10好看的模板自适应高端大气免费模板
  18. [已解决]关于win10计算器无法使用的问题
  19. tplink怎么进去_如何进入tp-link无线路由器设置界面
  20. md5解密算法 java_MD5算法解密java

热门文章

  1. Element ui 中将switch开关自定义文字描述(ON/OFF)显示在开关里面
  2. opencv第15天
  3. centos7下mysql卸载
  4. 2022-2028年全球与中国医疗专用气体行业发展趋势及竞争策略研究
  5. 安装opencv_python库 出现:not a supported wheel on this platform问题
  6. java实现将汉字转为拼音
  7. 离职员工讲述易到混乱:CEO在办公室煮面条、逼员工下跪
  8. 跟领导碰面不知道该说什么,给你几个技巧来破冰?
  9. 什么命令能把Linux搞死机,linux 死机相关
  10. Android GangSDK 第三方家族