阅读 81

编译器是怎样工作的?用lex和yacc 写一个计算器(2)


  • 幸运的是

    -----------------------------------------

    现在你可以看明白了上边的规则,问题又来了,我们怎么来处理这种语法的规则呢?

    该不会要自己写一个人肉编译吧?该不会要自己写一个人肉编译吧?该不会要自己写一个人肉编译吧?


    哈哈,不要担心,我们有现成的工具,它的名字叫另一种C编译器(yacc , yet another C compiler) ,

     Gnu 的实现叫一头野牛(bison),而不用重新发明轮子。

     yacc 可以直接读取我们上边所写的规则 ,执行我们所要作的,只是将匹配的规则规定出来一个动作就行。

    比如:

    如果我们遇到了expression + number  这个语法 ,我们就把两个表达式加起来就行了

    遇到了number 语法,把这个数返回就行了

    这两条规则,在yacc 里写出来是这样的:


    expression:

    number    {     $$  = $1 ;}

    |  expression  ‘+’  number   {  $$ = $1 + $3 }

    yacc 能接受的语法是:

       语法   {动作}


    就是说,遇到了一条能接受的语法,我们执行什么样的动作。

    对于每个能接受的语法,每个语法中的符号(比如number ,express ‘+’),

    yacc 都会把它们添加到$1,$2,$3…中去,在action语句里,直接使用它,整条语句的结果,用$$表示


    这样完成以后,我们的整个的语法文件如下所示:


    ch3-01.y:

     

    %token  NUMBER
    %%
    result:	expression	 { printf("%d\n", $1); }
    ;
    
    
    expression:
    expression '+' expression { $$ = $1 + $3; } 
    |	NUMBER	 { $$ = $1; }
    ;



    我们看一看,里边的各个变量的推导,除了NUMBER,其他所有的量,都可以由其他规则由NUMBER推导出来

    那么我们怎么从输入里取出这个符号变量呢?我们怎么判断输入符合我们规定的语法呢?

    【抓狂:心里有个声音总在呼喊:快人肉它~】

    哈哈,你还是不用人肉了,因为我们有lex ,这个google前 CEO shmit(他同是还是一个世界资产排名100之内的亿万富翁)发明的工具,就是为我们分析词法用的。

    在使用lex 之前,你得学会正则表达式,不过就本例来说,不会也不大碍事,因为可以稍微讲解一下

    首先,我们要取出数字

    数字由什么组成?数字有多个连续的0-9的数连续组成。

    0-9之间任意一个数在正则表达式里,用[0-9]表示

    多个连续的数?  在后边添加一个加号就行了:

    [0-9]+

    所以我们有了以下lex 规则,lex 规则是不是和yacc 长得差不多啊

    [0-9]+ {

    printf("N:%s\n", yytext);

    yylval = atoi(yytext);

    return NUMBER;

     }


    在关联动作的代码里,printf语句是为了调试。

    yylval 可以把我们要的数字取出来送给yacc ,这个共享变量有一个规定的名字叫yylval(yy left value)

    最后的return 语句,表明这条语句识别出了哪个token,而这个token就是我们定义在yacc里的


    \n return0/* logical EOF */

    这句表明,我们遇到一个新行(即输入中的回车)时,

    结束整个分析(就是说,我们这个计算器,只是单行的)

    . { printf(".:%s\n", yytext);

    return yytext[0]; }

    这个语句起到的效果是,如果遇上了任意字符(.表示任意字符,\n除外),就把它送给yacc,由于上边我们已经把 number 和空格送给yacc吃掉了,所以,这条语句还要去除数字和空格。

    整个文件就是这样:

     

    %{
    #include "ch3-01.h"
    #include <stdio.h>
    extern int yylval;
    %}
    
    %%
    [0-9]+ {
    printf("N:%s\n", yytext);
    yylval = atoi(yytext);
    return NUMBER;
     }
    
    [ \t] {printf("\\t");} /* ignore white space */
    
    \n return0; /* logical EOF */
    
    . { printf(".:%s\n", yytext);
    return yytext[0]; }
    %%
    
    int main(void)
    {
    yyparse();
    }
    
    int yyerror(char *errmsg)
    {
    printf("%s:%d \n", errmsg,yylineno);
    }


    整个程序的效果:

     

    $ make 
    yacc -d ch3-01.y
    mv y.tab.c ch3-01.c
    mv y.tab.h `echo ch3-01.c | sed 's/c$/h/' `
    cc  -g    -c -o ch3-01.o ch3-01.c
    flex -t ch3-01.l > ch3-01.l.c
    cc  -g    -c -o ch3-01.l.o ch3-01.l.c
    cc  -g   -ly -ll  ch3-01.o ch3-01.l.o   -o ch3-01
    rm ch3-01.c ch3-01.l.c
    
    $ ./ch3-01
    1+2+3 +4 +5
    N:1
    .:+
    N:2
    .:+
    N:3
     :\t
    :\t
    .:+
    N:4
     :\t
    .:+
    N:5
    15
    $ ./ch3-01
    1+2+
    N:1
    .:+
    N:2
    .:+
    syntax error:1


    可以看到,我们的计算器,由yacc和lex 制作而成的计算器,已经成功地识别了输入,计算出了整个表达式的值。

    而错误的语法,无法通过我们的语法检查,出现了语法错误


    一般的计算器解析如下:

    设定语法->给语法规定动作并转换成BNF(就是我们上边写的语法表达式了)->写出识别符号的lex程序

    经过这3步就可以完成。


    那么yacc 如何分析呢?

    它将语法分析时的规则变成一组状态,每个状态都表示在分析过程中可能出现的一种位置。

    当分析程序读取标记时,每次读取一个未完成规则的村记,就把它放到栈中并更新到能代表此符号已经移进的状态

    ,然后扫描这个状态栈,当它发现某个规则被匹配时,它就把这个规则相关的符号都弹出栈,

    并用这个规则的左侧符号来替代右侧的所有相关符号


    举个例子

    读取1+2并分析的过程如下:

    读取1 匹配number 规则,把$$=$1的规则应用,然后把1弹出栈,再放左值的1到栈中:1 , 更新状态:1已经移入,

    读取+  不匹配规则,放入栈中:1 +,没有发现栈中可以更新的规则    :更新状态  +已经移入

    读取2 匹配 number 规则,把2放入栈中  更新状态: 2已经移入

    发现一条可以匹配 express : express + number 的规则,把1+2 弹出栈,把我们规定的结果:$1+$3(1+2=3)  即3 移入栈中。

    读取完毕:发现最终要实现的规则匹配到栈中的3:应用规则(printf(‘%d’ , $1);然后把3移出,更新状态,3已经移出


    woo , ČÕÖŁ,我们实现了刚才我们人肉计算器一样的计算过程。


    所有的代码都可以在这里找到:

    https://github.com/lbaby/javalearn/tree/master/lexYacc

    ----------------

    练习:

    添加 -的支持,让程序支持减法

    如果不加 statement  :  expression;这条规则 ,最终程序会停在什么情况下?验证一下

    如果将 expression '+' NUMBER换成  expression  ‘+’ expression,程序执行中间状态会不会不同,结果呢?验证一下

    创建能解析如下语法的规则的程序,并测试它:

    (+    (+  (+  1  2)  3  )   (+  4     5) ) 



    ------------

    休息一下,广告之后更精彩。


    后续的故事:没有变量和函数的日子不幸福。

  • 相关阅读:
    【转发】【小程序】微信小程序日常开发中常遇到的错误代码
    正则表达式:看懂正则表达式只需要几个实例就够了
    CSS响应式:根据分辨率加载不同CSS的几个方法,亲测可用
    [微信小程序] 微信小程序获取用户定位信息并加载对应城市信息,wx.getLocation,腾讯地图小程序api,微信小程序经纬度逆解析地理信息
    [NodeJs] 用Nodejs+Express搭建web,nodejs路由和Ajax传数据并返回状态,nodejs+mysql通过ajax获取数据并写入数据库
    [微信小程序] 微信小程序富文本-wxParse的使用
    [AngularJS] “多重路由”嵌套模块——AngularJS“路由”嵌套学习资料教程
    [AngularJS] “路由”的定义概念、使用详解——AngularJS学习资料教程
    学习笔记:JavaScript-进阶篇
    学习笔记:JavaScript-入门篇

  • 原文地址:https://www.cnblogs.com/javawebsoa/p/3028942.html

  • 最新文章

  • [转]string和stringstream用法总结
    [转]让opencv输出人脸检测的得分(置信率)
    [转]人脸识别的总结
    学习Java Web篇:MVC设计模式
    学习jsp篇:jsp Session介绍
    学习jsp篇:jsp Cookie介绍
    学习jsp篇:jsp简单实例之二登录
    学习jsp篇:jsp简单实例之一注册
    学习JSP篇:jsp简单介绍
    java篇之JDBC原理和使用方法

  • 热门文章

  • SpringMVC主要组件
    Spring与Struts2整合时action自动注入的问题
    hibernate中的一级缓存与闪照区
    Java中的常用的输入输出流
    Struts2中的拦截器
    Struts2中的过滤器
    JSP中的请求转发与重定向
    关于CSS3样式中的前缀问题
    [微信小程序]微信开发工具出现 1not found 编译 .wxss文件信息错误怎么办?
    不到两年的前端小白2017个人年终总结:今年的年终总结是为了更好的自己



文章分类
代码人生
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐