1、学 号: 课 程 设 计题 目 24 点速算游戏学 院 计算机科学与技术学院专 业 软件工程班 级 软件 zy1302 班姓 名指导教师 饶文碧2016 年 1 月 8 日课程设计任务书学生姓名: 专业班级: 软件 zy1302 班 指导教师: 饶文碧 工作单位: 计算机科学与技术学院 题目:24 点速算游戏1目的通过设计、编制、调试一个 24 点速算程序,加深对语法及语义分析原理的理解,并实现词法分析程序对单词序列的词法检查和分析。2设计内容及要求程序输入:1-12 中的 4 个数字;程序输出:由上述 4 个数字及“+,-,*,/”组成的计算结果为 24 的算术表达式;(1) 学号 5,16
2、,27 的同学选择任意方法完成以上任务,最终输出正确的算术表达式。(2) 写出算术表达式的符合分析方法要求的文法,给出分析方法的思想,完成分析程序设计。(3) 编制好分析程序后,设计若干用例,上机测试并通过所设计的分析程序。3上机时间安排设计时间:第 18 周,周四上午 8:00-12:00,周五下午 2:00-5:30。指导教师签名: 年 月 日系主任(或责任教师)签名: 年 月 日目录课程设计任务书 21 引言 12 文法及中间代码形式的描述 12.1 文法的描述 13 词法分析方法 14 语法分析方法 25 详细的算法描述 25.1 词法分析 25.2 语法分析 66 源程序清单 97
3、简要分析 197.1 程序开发语言和程序结构 197.2 程序输入 198 软件的测试方法和实验结果 198.1 测试方法 198.2 实验结果 209 设计的评价、收获与体会 219.1 设计的评价 219.2 收获与体会 2110 参考文献 22本科生课程设计成绩评定表 2311 引言课程设计是对学生的一种全面综合训练,是与课堂听讲、自学和练习相辅相成的必不可少的一个教学环节。通常,设计题中的问题比平时的练习题要复杂,也更接近实际。编译原理这门课程安排的课程设计的目的是旨在要求学生进一步巩固课堂上所学的理论知识,深化理解和灵活掌握教学内容,选择合适的数据逻 辑结构表示问题,然后编制算法和程
4、序完成设计要求,从而进一步培养学生独立 思考问题、分析问题、解决实际问题的动手能力。要求学生在上机前应认真做好各种准备工作,熟悉机器的操作系统和语言的 集成环境,独立完成算法编制和程序代码的编写。随着科技发展和社会进步,尤其是计算机大范围的普及,计算机应用逐渐由大规模计算的海量数据处理转向大规模的事物处理和对工作流的管理,这就产生以台式计算机为核心的管理系统。 本次课程设计的题目是速算 24 点,是在 80 年代成为一种流行的游戏,在中国把这游戏叫做“24 点游戏” 。计算 24 点游戏:任意输入 4 位数字,利用+,-,*,/四则运算使之得到结果 24。输出所有不同算法的计算表达式,可为运算
5、优先级而使用括号。速算 24 点游戏始于何年何月已无从考究,但它以自己独具的数学魅力和丰富的内涵正逐渐被越来越多的人们所接受。这种游戏方式简单易学,能健脑益智,是一项极为有益的活动。笔者抛弃常用的编程方式(直接根据表达式进行求值判断是否值为 24) ,采用构造编译器的模式,对表达式进行词法分析、语法分析和语义分析,进而得到表达式的值,尽管这样有些大材小用,但是对于我理解编译原理有极大的帮助。2 文法及中间代码形式的描述2.1 文法的描述自己根据表达式的特点归纳了一个初始文法 GE:E E+T | E-T | TT T*F | T/F | FF (E) | d但据观察,上述文法有左递归性,将上述
6、文法消除左递归可得出下面的文法:E TEE +TE | -TE | T *FT | /FT | F (E) | d3 词法分析方法词法分析时编译的第一个阶段,他的主要任务是从左至右逐个字符地对源程序进行扫描,产生一个个单词序列,用以语法分析。执行词法分析的程序称为词 法分析程序或扫描程序。2自定义类 Token 存放识别出的单词属性,Token 数据结构如下:public class Tokenprivate String tokenType; / 单词种别码private String tokenValue; / 单词值public Token(String tokenType, Strin
7、g tokenValue)this.tokenType = tokenType;this.tokenValue = tokenValue;public String getTokenType()return tokenType;public String getTokenValue()return tokenValue;该词法分析器能够识别运算符:+-*/,能够识别界符:(),能够识别数字,其 DFA 在下面的词法分析详细描述部分有介绍。4 语法分析方法语法分析是编译程序的核心部分。语法分析的作用是识别由词法分析给出的单词符号序列是否是给定文法的正确句子,目前语法分析常用的方法有自顶向下分析和
8、自底向上分析两大类。本文采用自顶向下分析中的递归下降法来分析条件表达式。 递归子程序法是比较简单直观易于构造的一种语法分析方法。其实现思想:文法中每个非终结符对应一个递归过程(子程序) ,每个过程的功能是识别由该非终结符推出的串,当某非终结符的产生式有多个候选式时能够符合 LL(1)文法形式可唯一地确定选择某个候选式进行推导。本课设还采用了语法制导翻译技术,在进行语法分析的同时进行语义分析,该表达式的语义也就是该表达式对应的值,对整个单词符号序列的语法分析进行完后,也就能够得到其所对应的值。5 详细的算法描述5.1 词法分析词法分析阶段是编译过程的第一个阶段,是编译的基础。这个阶段的任务是从左
9、到右一个字符3一个字符地读入源程序,即对构成源程序的字符流进行扫描然后根据构词规则识别单词(也称单词符号或符号) 。词法分析程序实现这个任务。词法分析程序可以使用 Lex 等工具自动生成。词法分析是编译程序的第一个阶段且是必要阶段;词法分析的核心任务是扫描、识别单词且对识别出的单词给出定性、定长的处理;实现词法分析程序的常用途径:自动生成,手工生成。本次课程设计采用手工生成,原因:表达式词法分析十分简单,不需要自动生成;笔者希望从此次构造词法分析器的过程中,加深对编译原理的认知。自定义类 Token 存放识别出的单词属性,其结构上文已有介绍。表达式的词法分析程序对应的DFA 如下: +-*/(
10、)0, 1, 2, 3, 4, 5, 6, 7, 8, 90, 1, 2, 3, 4, 5, 6, 7, 8, 9笔者为语法分析器构造了一个接口,凡是实现了此接口的类,都能够实现词法分析的功能,达到了低耦合的效果,下列代码是词法分析的核心代码:ExpressionLexer.java :public interface ExpressionLexer/* 对给定的字符串,进行词法分析,返回单词队列* param string 字符串,分析的对象* return 单词队列*/Queue scan(String string);4ExpressionLexerImpl.java :public c
11、lass ExpressionLexerImpl implements ExpressionLexerString string;int index;private void init(String string)this.string = string;index = 0;private char peekChar()if (index = string.length() - 1 return string.charAt(index);private char pollChar()char c = string.charAt(index);index+;return c;Overridepu
12、blic Queue scan(String s)init(s);Queue tokens = new ArrayDeque T*F | T/F | FF (E) | d消除左递归可得出下面的文法:E TEE +TE | -TE | T *FT | /FT | F (E) | d经推导证明,改写后的文法是 LL(1)文法,可以使用递归下降法构造语法分析器,笔者为语法分析器也设定了接口,达到低耦合的目标,核心代码如下:ExpressionParser.java :public interface ExpressionParser/* 对给定的 token 串进行语法分析,语义分析* param
13、tokens Token 队列,分析的对象* return 算术表达式的值,分析的结果*/double parse(Queue tokens);ExpressionParserImpl.java :public class ExpressionParserImpl implements ExpressionParserprivate Queue tokens;private void init(Queue tokens)this.tokens = tokens;7private Token peekToken()return tokens.peek();private Token pollTo
14、ken()return tokens.poll();Overridepublic double parse(Queue t)init(t);return expression();private double expression()double termValue = term();return expression_(termValue);private double expression_(double termValue)double result = 0.0;switch (peekToken().getTokenType()case “+“:pollToken();result =
15、 termValue + term();result = expression_(result);break;case “-“:pollToken();result = termValue - term();result = expression_(result);break;case “)“:case “end“:result = termValue;break;8default:trythrow new Exception(“expression_()“); catch (Exception e)System.out.println(“expression_(): something wr
16、ong“);e.printStackTrace();return result;private double term()double factorValue = factor();return term_(factorValue);private double factor()double result = 0.0;switch (peekToken().getTokenType()case “(“:pollToken();result = expression();pollToken();break;case “digit“:result = Double.parseDouble(poll
17、Token().getTokenValue();break;default:trythrow new Exception(“factor()“); catch (Exception e)System.out.println(“factor(): something wrong“);e.printStackTrace();return result;9private double term_(double factorValue)double result = 0.0;switch (peekToken().getTokenType()case “*“:pollToken();result =
18、factorValue * factor();result = term_(result);break;case “/“:pollToken();result = factorValue / factor();result = term_(result);break;case “+“:case “-“:case “)“:case “end“:result = factorValue;break;default:trythrow new Exception(“term_()“); catch (Exception e)System.out.println(“term_(): something
19、wrong“);e.printStackTrace();return result;6 源程序清单Token.java :public class Tokenprivate String tokenType; / 单词种别码private String tokenValue; / 单词值10public Token(String tokenType, String tokenValue)this.tokenType = tokenType;this.tokenValue = tokenValue;public String getTokenType()return tokenType;publ
20、ic String getTokenValue()return tokenValue;ExpressionLexer.java :public interface ExpressionLexer/* 对给定的字符串,进行词法分析,返回单词队列* param string 字符串,分析的对象* return 单词队列*/Queue scan(String string);ExpressionLexerImpl.java :public class ExpressionLexerImpl implements ExpressionLexerString string;int index;priva
21、te void init(String string)this.string = string;index = 0;private char peekChar()if (index = string.length() - 1 return string.charAt(index);private char pollChar()char c = string.charAt(index);index+;return c;Overridepublic Queue scan(String s)init(s);Queue tokens = new ArrayDeque();while (index to
22、kens);ExpressionParserImpl.java :public class ExpressionParserImpl implements ExpressionParserprivate Queue tokens;private void init(Queue tokens)this.tokens = tokens;private Token peekToken()return tokens.peek();private Token pollToken()return tokens.poll();Overridepublic double parse(Queue t)init(
23、t);return expression();private double expression()double termValue = term();return expression_(termValue);private double expression_(double termValue)double result = 0.0;switch (peekToken().getTokenType()case “+“:14pollToken();result = termValue + term();result = expression_(result);break;case “-“:p
24、ollToken();result = termValue - term();result = expression_(result);break;case “)“:case “end“:result = termValue;break;default:trythrow new Exception(“expression_()“); catch (Exception e)System.out.println(“expression_(): something wrong“);e.printStackTrace();return result;private double term()doubl
25、e factorValue = factor();return term_(factorValue);private double factor()double result = 0.0;switch (peekToken().getTokenType()case “(“:pollToken();result = expression();pollToken();break;case “digit“:result = Double.parseDouble(pollToken().getTokenValue();15break;default:trythrow new Exception(“fa
26、ctor()“); catch (Exception e)System.out.println(“factor(): something wrong“);e.printStackTrace();return result;private double term_(double factorValue)double result = 0.0;switch (peekToken().getTokenType()case “*“:pollToken();result = factorValue * factor();result = term_(result);break;case “/“:poll
27、Token();result = factorValue / factor();result = term_(result);break;case “+“:case “-“:case “)“:case “end“:result = factorValue;break;default:trythrow new Exception(“term_()“); catch (Exception e)System.out.println(“term_(): something wrong“);e.printStackTrace();16return result;Main.java :public cla
28、ss Mainpublic static void main(String args)ExpressionLexer lexer = new ExpressionLexerImpl();ExpressionParser parser = new ExpressionParserImpl();System.out.println(“请输入 1-12 范围内的 4 个数字(例如:2 11 5 12):“);Scanner scanner = new Scanner(System.in);String digitString = scanner.nextLine();String digits =
29、digitString.trim().split(“ +“);/ 4 个数字的 4*3*2*1 种不同顺序的组合String digitCombination = new String244;int m = 0;for (int i = 0; i 4; i+)for (int j = 0; j 4; j+)if (j != i)for (int k = 0; k 4; k+)if (k != j l 4; l+)if (l != k digitCombinationmj = digits1;digitCombinationmk = digits2;digitCombinationml = di
30、gits3;m+;17/ 3 个运算符的 43 种不同组合String operators = “+“, “-“, “*“, “/“;String operatorCombination = new String643;m = 0;for (int i = 0; i 4; i+)for (int j = 0; j 4; j+)for (int k = 0; k 4; k+)operatorCombinationm0 = operatorsi;operatorCombinationm1 = operatorsj;operatorCombinationm2 = operatorsk;m+;/ 插入
31、括号的 6 种不同组合String string;double result;System.out.println(“计算结果是 24 的可能情况有:“);for (int i = 0; i 24; i+)for(int j = 0; j 64; j+)string = “(“ + “(“ + digitCombinationi0 + operatorCombinationj0 + digitCombinationi1 + “)“+ operatorCombinationj1 + digitCombinationi2 + “)“ + operatorCombinationj2 + digitC
32、ombinationi3;result = parser.parse(lexer.scan(string);if (result = 24)System.out.println(string + “=24“);string = “(“ +digitCombinationi0 + operatorCombinationj0 +“(“+ digitCombinationi1+ operatorCombinationj1 + digitCombinationi2 +“)“+“)“+ operatorCombinationj2 + digitCombinationi3;18result = parse
33、r.parse(lexer.scan(string);if (result = 24)System.out.println(string + “=24“);string = digitCombinationi0 + operatorCombinationj0 +“(“+“(“+ digitCombinationi1+ operatorCombinationj1 + digitCombinationi2+“)“ + operatorCombinationj2 + digitCombinationi3+“)“;result = parser.parse(lexer.scan(string);if
34、(result = 24)System.out.println(string + “=24“);string = digitCombinationi0 + operatorCombinationj0 +“(“+ digitCombinationi1+ operatorCombinationj1 +“(“+ digitCombinationi2 + operatorCombinationj2 + digitCombinationi3+“)“+“)“;result = parser.parse(lexer.scan(string);if (result = 24)System.out.printl
35、n(string + “=24“);string = “(“+digitCombinationi0 + operatorCombinationj0 + digitCombinationi1+“)“+ operatorCombinationj1 + “(“+digitCombinationi2 + operatorCombinationj2 + digitCombinationi3+“)“;result = parser.parse(lexer.scan(string);if (result = 24)System.out.println(string + “=24“);197 简要分析7.1
36、程序开发语言和程序结构开发语言:Java程序结构:7.2 程序输入任意 4 个 1-12 的数字,用空格分割,例如: 2 3 12 3。8 软件的测试方法和实验结果8.1 测试方法抽取 24 点速算游戏所有的可通过表达式,输入表达式中的 4 个数字,检查程序输出的表达式的值是否为 24,如果实验结果均成功输出正确的表达式,证明软件是基本正确的。本程序使用了断点、JUnit 和输出中间结果的方法进行测试。并且在编程的过程中边测试边写代码,避免了大量代码集中测试的情况。208.2 实验结果219 设计的评价、收获与体会9.1 设计的评价对自己的本次的设计,虽然似乎有点大材小用,但是我还是比较满意的
37、。这个 24 点速算游戏的资料非常的少,几乎所有设计全都是自己思考得到的,用了构造编译器的原理,将穷举出的表达式进行词法分析,输出单词序列,再对这些单词序列传参给语法分析器,实现语法分析和并利用语法制导翻译技术实现语义分析,实现了这个速算游戏。而且我还考虑了程序的可扩展性,实现过程中使用了设计模式,达到了低耦合的目标。9.2 收获与体会经过此次的课程设计,我收获颇丰。这次课程设计的网络资料非常的少,但是这并不是阻挡我前进的障碍,反而加强了我独自思考的能力。我们学习的知识有限,遇到的问题却是多种多样的,我们不能保证所有的知识都学习过了,但我们能够在日常的学习过程中学会如何去学习,掌握学习22的方
38、法。 通过这次课程设计,用理论结合实践,我对编译原理这门课程有了更好的掌握。在完成这次课设的过程中,我遇到了非常多的问题,我也总结除了自己的不足。比如对一些基础知识的理解还不够透彻,对所学的算法还不能够熟练应用,动手能力依然需要增强。 这次课程设计也使我深切理解老师说的“这是一门理论性很强,实践性也很强的课程” 。我这次课程设计的完成离不开所有帮助我的人,其中包括老师,同学以及其他生活中也十分关心我的家人,要不是没有这些人,我根本不能完成这次的课设。不管这次实验中有多少跌跌撞撞,但终归是受益匪浅。10 参考文献1 李文新、郭炜、余华山. 程序设计导引及在线实践M. 北京:清华大学出版社2 吕映
39、芝、张素琴、蒋维杜. 编译原理 (第二版).清华大学出版社.2004.113 何炎祥. 编译原理.华中理工大学出版社.2000.104 陈火旺、刘春林等.程序设计语言编译原理 (第 3 版) .国防工业出版社.2003.25 严蔚敏,吴伟民. 数据结构M. 北京:清华大学出版社,1996.6 Robert Sedgewick, Kevin Wayne. 算法M. 北京:人民邮电出版社.7 Cay S. Horstmann, Gary Cornell. Java 核心技术卷一. 北京:机械工业出版社.8 Cay S. Horstmann, Gary Cornell. Java 核心技术卷二. 北京:机械工业出版社.23本科生课程设计成绩评定表班级:软件 zy1302 姓名: 学号: 序号 评分项目 满分 实得分1 学习态度认真、遵守纪律 102 设计分析合理性 103设计方案正确性、可行性、创造性204 设计结果正确性 405 设计报告的规范性 106 设计验收 10总得分/等级评语:注:最终成绩以五级分制记。优(90-100 分) 、良(80-89 分) 、中(70-79 分) 、及格(60-69 分) 、60 分以下为不及格指导教师签名:年 月 日24