zoukankan      html  css  js  c++  java
  • 编译原理实践:计算器

    概述

      本博客主要讲述如何利用编译原理的知识实现一个控制台计算器.如果之前利用栈(在学数据结构的时候)实现过计算器,一定会有所印象,写一个计算器程序最重要的就是把握运算优先级了.而本文换一个角度,利用文法的知识来实现一个功能齐全的计算器.虽然用编译原理的理论来做计算器实在有点杀鸡焉用宰牛刀的味道,但如此实践确实是有必要的."合抱之木,生于毫末;九层之台,起于累土;千里之行,始于足下."

      需要说明的是,本文对于不懂编译原理的人来说基本算是天书了,所以,请仔细阅读预备知识.

    预备知识

      需要说明的是,越到后面会越发现,写一个程序并不是你会了几种语言的问题,语言只是一个工具,没有扎实的理论,永远写不出好的程序.

    1. 编译原理的知识.比如:如果你不知道怎么消除左递归,那么完全没必要往下看了.
    2. C语言(用于手工构造),lex/yacc(不懂这一部分可以只看手工构造)

    原理分析

      相信这一部分对于熟悉编译原理的人没有难度.以下是一个计算器输入语句的文法定义

      expression : term | expression + term | expression -  term;

      term : primary_expression | term * primary_expression | term / primary_expression;

      primary_expression : DOUBLE_LITERAL | ( expression ) | - primary_expression;

      以上文法直接运用到yacc是没问题的,因为yacc生成的是一个LALR(1)解析器,但手工构造就没那么幸运了,老司机一眼就能看出来,这是一个左递归的BNF,所以需要消除左递归.

      expression : term half_expression;

      half_expression : + term half_expression | - term half_expression | ε;

      term :  primary_expression half_term;

      half_term : * primary_expression | / primary_expression | ε;

      primary_expression : DOUBLE_LITERAL | ( expression ) | - primary_expression;

       求一下这个文法的FIRST和FOLLOW集:

       

      构造一下预测分析表就知道是一个LL(1)文法.   

      以上是简单的原理分析.

    手工构造

      所谓手工构造就是自己编写程序,这里使用的是C语言,当然了,其他语言也可以.不过这里多说一句话,如果完整的实现一个编译器,必然要涉及将源程序转化为机器码,这是个面向底层的工作,十分适合C语言来处理.而且lex/yacc生成的也是C程序,虽然也有了像JavaCC等其他语言的词法/语法分析生成器,但还是建议使用C语言.

      赵裕-GitHub-calculator存放了所有代码,llparser_version目录下就是手工构造的源代码.

      手工构造一般采用递归下降法(也称递归子程序法),每当遇到一个终结符就会调用一个对应的子函数进行解析,这里不对源代码做原理性详细的分析,因为我假设你已经熟悉了理论层面的知识,只是缺乏一个实践的参照,学习理解这个程序最好的办法就是clone下来,自己进行单步调试,这是最有效的学习办法.

      以下是概括性的程序说明:

    1. token.h存放基本的声明,如+,-,*,/,(,)的标记
    2. lexical_analyzer.c存放词法解析程序,起中包含的一个主要函数get_token()用于返回token
    3. parser.c存放语法分析程序,包含主函数,以及每个非终结符对应的函数.
    4. 语法分析程序对于超前读取的字符,如果不需要则退回,也可以保持始终预读一个字符.本代码采用前一种.

    lex/yacc构造

    关于lex/yacc

      关于lex/yacc这里不做过多的介绍,学过编译原理或多或少都知道一点,O'Reilly出版社的Lex&Yacc是为数不多系统讲解这两个工具的书籍,需要深入了解的可以阅读阅读.

    构造计算器

      同样,相关代码放在了lex-yacc_version目录下面,这里也不直接给出代码,相对于手工构造的代码,lex和yacc的代码都十分易读(前提是你十分熟悉正则表达式文法),只是你在使用这两个工具的时候必须熟悉他们的一套规则,这两个工具都有些年头了,所以有些地方或者说有些设计理念可能不是那么优雅,但他们确实十分强大!

      最后,利用这两个工具生成的C代码很有必要打开看看,一般来说词法分析手工构造尚可,但语法分析利用工具确实省时省力又高效,所以,十分有必要看看到底生成的了怎样的代码.

      即使不懂lex/yacc,只要按照如下步骤编译,应该就能得到C语言的目标代码:

      

      使用-dv参数是为了生成一个辅助文件y.output,这个文件包含很多有用信息,而且如果出现了冲突可以给出详细的说明.可以自己打开看看,以下是该文件的一部分:

     1 Grammar
     2 
     3     0 $accept: line_list $end
     4 
     5     1 line_list: line
     6     2          | line_list line
     7 
     8     3 line: expression CR
     9     4     | error CR
    10 
    11     5 expression: term
    12     6           | expression ADD term
    13     7           | expression SUB term
    14 
    15     8 term: primary_expression
    16     9     | term MUL primary_expression
    17    10     | term DIV primary_expression
    18 
    19    11 primary_expression: DOUBLE_LITERAL
    20    12                   | LP expression RP
    21    13                   | SUB primary_expression
    22 
    23 
    24 Terminals, with rules where they appear
    25 
    26 $end (0) 0
    27 error (256) 4
    28 DOUBLE_LITERAL (258) 11
    29 ADD (259) 6
    30 SUB (260) 7 13
    31 MUL (261) 9
    32 DIV (262) 10
    33 CR (263) 3 4
    34 LP (264) 12
    35 RP (265) 12
    36 
    37 
    38 Nonterminals, with rules where they appear
    39 
    40 $accept (11)
    41     on left: 0
    42 line_list (12)
    43     on left: 1 2, on right: 0 2
    44 line (13)
    45     on left: 3 4, on right: 1 2
    46 expression (14)
    47     on left: 5 6 7, on right: 3 6 7 12
    48 term (15)
    49     on left: 8 9 10, on right: 5 6 7 9 10
    50 primary_expression (16)
    51     on left: 11 12 13, on right: 8 9 10 13
    52 
    53 
    54 State 0
    55 
    56     0 $accept: . line_list $end
    57 
    58     error           shift, and go to state 1
    59     DOUBLE_LITERAL  shift, and go to state 2
    60     SUB             shift, and go to state 3
    61     LP              shift, and go to state 4
    62 
    63     line_list           go to state 5
    64     line                go to state 6
    65     expression          go to state 7
    66     term                go to state 8
    67     primary_expression  go to state 9
    68 
    69 
    70 State 1
    71 
    72     4 line: error . CR
    73 
    74     CR  shift, and go to state 10
    75 
    76 
    77 State 2
    78 
    79    11 primary_expression: DOUBLE_LITERAL .
    80 
    81     $default  reduce using rule 11 (primary_expression)
    82 ......
    View Code

    小结

      本文介绍了编译原理在编写计算器上的实践,讲的很简略(因为我没有讲解代码,也没有一步一步分析原理,毕竟这不是一两句话的事),充分理解这些在信息的最好方法就是自己对着代码敲一遍.

      好久没写博客了,感觉之前写的好多质量都不够高,希望从本篇开始自己能够以一个更务实的心态写一些有水平的东西,做一些有深度的总结.

    参考

      <<自制编程语言>>,前桥和弥.

  • 相关阅读:
    jquery tmpl 模板引擎 取小数点 三目运算
    ztree 根据id选中某一点且触发当前点的click事件
    rgb(123,25,62)拆分
    html5 vedio
    同一个dom上加单击事件和双击事件的冲突解决办法,双击事件方法进不去
    axios拦截器
    foreach、for、for in循环方式
    echarts markline 不同颜色
    组件里传值到父级
    vue路由钩子拦截器beforeEach和afterEach及页面路由变化路由监听
  • 原文地址:https://www.cnblogs.com/zhaoyu1995/p/6111717.html
Copyright © 2011-2022 走看看