精华内容
下载资源
问答
  • 源代码所处位置 https://gitee.com/beipiaoyizu520/lex_yacc_exercise/cal_test 转载于:https://my.oschina.ne...

    源代码所处位置 https://gitee.com/beipiaoyizu520/lex_yacc_exercise/cal_test

    转载于:https://my.oschina.net/u/2322146/blog/1827135

    展开全文
  • 编译原理 yacc lex 制作一个计算器

    千次阅读 2018-05-31 14:14:51
    )Flex工具的使用方法Lex 是一种生成扫描器的工具。 Lex是Unix环境下非常著名的工具,主要功能是生成一个扫描器(Scanner)的C源码。扫描器是一种识别文本中的词汇模式的程序。 这些词汇模式(或者常规表达式)在一种...

    这篇文档是我从别的地方摘抄的,留给自己以后回忆使用。(写的非常详细!)


    Flex工具的使用方法

    Lex 是一种生成扫描器的工具。 Lex是Unix环境下非常著名的工具,主要功能是生成一个扫描器(Scanner)的C源码。

    扫描器是一种识别文本中的词汇模式的程序。 这些词汇模式(或者常规表达式)在一种特殊的句子结构中定义。一种匹配的常规表达式可能会包含相关的动作。这一动作可能还包括返回一个标记。 当 Lex 接收到文件或文本形式的输入时,它试图将文本与常规表达式进行匹配。 它一次读入一个输入字符,直到找到一个匹配的模式。 如果能够找到一个匹配的模式,Lex 就执行相关的动作(可能包括返回一个标记)。 另一方面,如果没有可以匹配的常规表达式,将会停止进一步的处理,Lex 将显示一个错误消息。

    Lex 和 C 是强耦合的。一个 .lex 文件(Lex 文件具有 .lex 的扩展名)通过 lex 公用程序来传递,并生成 C 的输出文件。这些文件被编译为词法分析器的可执行版本。

    Lex程序

    一个典型的Lex程序的大致结构:

    declarations
    %%
    translation rules
    %%
    auxiliary procedures
    

    分别是声明,转换规则和其它函数。%用作在单个部分之间做分隔。

    字符及其含义列表:

    A-Z, 0-9, a-z   构成了部分模式的字符和数字。
    .               匹配任意字符,除了 \n。
    -               用来指定范围。例如:A-Z 指从 A 到 Z 之间的所有字符。
    [ ]             一个字符集合。匹配括号内的 任意 字符。如果第一个字符是 ^ 那么它表示否定模式。
                    例如: [abC] 匹配 a, b, 和 C中的任何一个。
    *               匹配 0个或者多个上述的模式。
    +               匹配 1个或者多个上述模式。
    ?               匹配 0个或1个上述模式。
    $               作为模式的最后一个字符匹配一行的结尾。
    { }             指出一个模式可能出现的次数。 例如: A{1,3} 表示 A 可能出现1次或3次。
    \               用来转义元字符。同样用来覆盖字符在此表中定义的特殊意义,只取字符的本意。
    ^               否定。
    |               表达式间的逻辑或。
    "<一些符号>"     字符的字面含义。元字符具有。
    /               向前匹配。如果在匹配的模版中的“/”后跟有后续表达式,只匹配模版中“/”前 面的部分。
                    如:如果输入 A01,那么在模版 A0/1 中的 A0 是匹配的。
    ( )             将一系列常规表达式分组。
    

    标记声明:

    数字(number)      ([0-9])+                        1个或多个数字
    字符(chars)       [A-Za-z]                        任意字符
    空格(blank)       " "                             一个空格
    字(word)          (chars)+                        1个或多个 chars
    变量(variable)    (字符)+(数字)*(字符)*(数字)*
    

    值得注意的是,lex 依次尝试每一个规则,尽可能地匹配最长的输入流。如果有一些内容根本不匹配任何规则,那么 lex 将只是把它拷贝到标准输出

    Lex 编程可以分为三步:

    1. 以 Lex 可以理解的格式指定模式相关的动作。
    2. 在这一文件上运行 Lex,生成扫描器的 C 代码。
    3. 编译和链接 C 代码,生成可执行的扫描器。

    例如,对于一下的Lex代码:

    %{
    #include <stdio.h>
    
    int k = 0; %} %% [0-9]+ { k = atoi(yytext); if(k % 6 == 0 && k % 8 == 0) { printf("%d\n", k); } } 

    执行:

    lex prog.lex
    gcc lex.yy.c -o prog -ll
    

    然后将会得到一个可执行文件,这个可执行文件的功能是:如果输入的字符串不是数字,原样输出,如果是数字,判断是否为6和8的公倍数,若是,则输出。

    其中,-ll表示链接lex的相关库文件,要想编译时不带-ll选项,就必须实现main函数和yywrap函数(return 1即可)。

    Lex中,一般声明为如下形式:

    %{
    int wordCount = 0; %} chars [A-Za-z\_\'\.\"] numbers ([0-9])+ delim [" "\n\t] whitespace {delim}+ words {chars}+

    模式匹配规则如下例:

    {words} { wordCount++; /* increase the word count by one*/ } {whitespace} { /* do nothing*/ } {numbers} { /* one may want to add some processing here*/ }

    含义为针对不同的模式采取不同的策略(状态机)。

    Lex程序的最后一段一般为C代码,为如下形式:

    void main()
    {
        yylex(); /* start the analysis*/ // ... do some work. } int yywrap() { return 1; } 

    最后一段覆盖了 C 的函数声明(有时是主函数)。注意这一段必须包括 yywrap() 函数。

    在上文中的判断公倍数的例子中,省略了程序的第三段,Lex生成了默认的C风格的main()函数。

    在使用Lex做文法解析时,某些特殊结构的表达式会使由表格转化的确定的自动机成指数增长,并因此造成指数级的空间和时间复杂度消耗。

    Lex变量和函数

    一些常用的Lex变量如下所示:

    yyin        FILE* 类型。 它指向 lexer 正在解析的当前文件。
    yyout       FILE* 类型。 它指向记录 lexer 输出的位置。 缺省情况下,yyin 和 yyout 都指向标准输入和输出。
    yytext      匹配模式的文本存储在这一变量中(char*)。
    yyleng      给出匹配模式的长度。
    yylineno    提供当前的行数信息。 (lexer不一定支持。)
    

    Lex函数:

    yylex()     这一函数开始分析。 它由 Lex 自动生成。
    yywrap()    这一函数在文件(或输入)的末尾调用。 如果函数的返回值是1,就停止解析。
                因此它可以用来解析多个文件。 代码可以写在第三段,这就能够解析多个文件。
                方法是使用 yyin 文件指针(见上表)指向不同的文件,直到所有的文件都被解析。
                最后,yywrap() 可以返回 1 来表示解析的结束。
    yyless(int n)   这一函数可以用来送回除了前 n 个字符外的所有读出标记。
    yymore()    这一函数告诉 Lexer 将下一个标记附加到当前标记后。
    

    Lex内部预定义宏:

    ECHO     #define ECHO fwrite(yytext, yyleng, 1, yyout) 也是未匹配字符的默认动作。
    

    一个简单的Lex的例子:

    %{
    #include <stdio.h>
    %} %% [\n] { printf("new line\n"); } [0-9]+ { printf("int: %d\n", atoi(yytext)); } [0-9]*\.[0-9]+ { printf("float: %f\n", atof(yytext)); } [a-zA-Z][a-zA-Z0-9]* { printf("var: %s\n", yytext); } [\+\-\*\/\%] { printf("op: %s\n", yytext); } . { printf("unknown: %c\n", yytext[0]); } %% 

    Yacc

    Yacc 代表 Yet Another Compiler Compiler。 Yacc 的 GNU 版叫做 Bison。它是一种工具,将任何一种编程语言的所有语法翻译成针对此种语言的 Yacc 语 法解析器。它用巴科斯范式(BNF, Backus Naur Form)来书写。按照惯例,Yacc 文件有 .y 后缀。

    用 Yacc 来创建一个编译器包括四个步骤:

    • 通过在语法文件上运行 Yacc 生成一个解析器。
    • 说明语法:

      • 编写一个 .y 的语法文件(同时说明 C 在这里要进行的动作)。
      • 编写一个词法分析器来处理输入并将标记传递给解析器。 这可以使用 Lex 来完成。
      • 编写一个函数,通过调用 yyparse() 来开始解析。
      • 编写错误处理例程(如 yyerror())。
    • 编译 Yacc 生成的代码以及其他相关的源文件。
    • 将目标文件链接到适当的可执行解析器库。

    Yacc程序

    如同 Lex 一样, 一个 Yacc 程序也用双百分号分为三段。 它们是:声明、语法规则和 C 代码。 每两段内容之间用%%

    一个Yacc程序示例:

    %{
    typedef char* string; #define YYSTYPE string %} %token NAME EQ AGE %% file: record file | record ; record: NAME EQ AGE { printf("name: %s, eq: %d, age: %d\n, $1, $2, $3);  }  ; %% int main() {  yyparse();  return 0; } int yyerror(char *msg) {  printf("ERORR MESSAGE: %s\n", msg); }

    Lex和YACC内部工作原理

    在YACC文件中,main函数调用了yyparse(),此函数由YACC替你生成的,在y.tab.c文件中。函数yyparseyylex中读取符号/值组成的流。你可以自己编码实现这点,或者让Lex帮你完成。在我们的示例中,我们选择将此任务交给Lex。

    Lex中的yylex函数从一个称作yyin的文件指针所指的文件中读取字符。如果你没有设置yyin,默认是标准输入(stdin)。输出为yyout,默认为标准输出(stdout)。

    你可以在yywrap函数中修改yyin,此函数在每一个输入文件被解析完毕时被调用,它允许你打开其它的文件继续解析,如果是这样,yywarp的返回值为0。如果想结束解析文件,返回1

    每次调用yylex函数用一个整数作为返回值,表示一种符号类型,告诉YACC当前读取到的符号类型,此符号是否有值是可选的,yylval即存放了其值。

    默认yylval的类型是整型(int),但是可以通过重定义YYSTYPE以对其进行重写。分词器需要取得yylval,为此必须将其定义为一个外部变量。原始YACC不会帮你做这些,因此你得将下面的内容添加到你的分词器中,就在#include<y.tab.h>下即可:

    extern YYSTYPE yylval;
    

    Bison会自动做这些工作(使用-d选项生成y.tab.h文件)。

    Lex与Yacc配合

    使用Lex和Yacc实现一个高级计算器

     

    Lex代码的内容:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    %{
    #include <stdlib.h>
    #include "test.tab.h"
    extern  int  yyerror( const  char  *);
    %}
     
    %%
    [ " " ; \t]           { }  
    (0(\.[0-9]+)?)|([1-9][0-9]*(\.[0-9]+)?)     { yylval.dv =  strtod (yytext,0); return  NUMBER;}  
    [a-zA-Z]                                    { yylval.cv = *yytext;   return  CHARA;}
     
    [-+*/()^%~!=\n]     { return  *yytext;}
    "&"                  { return  AND;}
    "|"                  { return  OR;}
    "||"                 { return  or;}
    "&&"                 { return  and;}
    "log"                { return  LOG;}
    "cos"                { return  COS;}
    "sin"                { return  SIN;}
    "tan"                { return  TAN;}
    "++"                 { return  PP;}
    "--"                 { return  SS;}
    "<<"                 { return  LOL;} 
    ">>"               { return  LOR;}
    "cot"                { return  COT;}
     
    "ans"                { return  ANS;}
    "drop"               { return  DROP;}
    "list"               { return  LIST;}
    "erase"              { return  ERASE;}
    "clear"              { return  CLEAR;}
    "help"               { return  HELP;}
    %%
    int  yywrap()
    {
         return  1;
    }

     Yacc代码的内容:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    %{
    #define Pi 3.14159265358979
    #include <stdlib.h>
    #include <stdio.h>
    #include <math.h>
     
    int  yylex();
    int  yyerror( char  *);
    void  convert( int  num , int  mode);
    double  vars[26]={0};
    double  last=0;
    long  var;
    int  i;
    int  flag=1;
    %}
    %token ANS
    %token <dv> NUMBER
    %token <cv> CHARA
    %type <dv> expr
    %type <cv> cmdline
     
    % union
    {  
     
       double  dv;  
       char  cv;  
    }  
    %token DROP HELP CLEAR LIST ERASE
    %token  '+'  '-'  '*'  '/'  '^'  '%'  '`'  '~'  '!'  '='
    %token COS SIN TAN OR AND PP SS LOR LOL COT or and
    %token LOG
     
    %left  '='
    %left  '+'  '-'
    %left  '*'  '/'  '%'
    %left AND OR and or
    %left COS SIN TAN LOG PP SS LOR LOL COT
    %left  '^'
    %left  '~'  '!'
    %right  '('  ')'
     
    %%
    program:
             program expr  '\n' 
                     if (flag)
                     {
                      printf "你的结果是:\t=%g\n"  , $2 );
                      last = $2;
                     }
                     else
                            { printf ( "" );}
                     flag=1;
                   }
            | program cmdline  '\n'
            | program stat  '\n'
            |
             ;
     
    stat   :  
             CHARA  '='  expr 
            
                 if ( islower ($1))  
                   i = $1 -  'a' ;  
                 else  
                   i = $1 -  'A' ;
                 vars[i] = $3;
                 flag =1; 
         }
         
     
    expr    :
             NUMBER        { $$ = $1; }
             | ANS         { $$ = last; }
             | CHARA  
                    
                      if ( islower ($1)) 
                        i = $1 -  'a' ;  
                                else 
                         i = $1 -  'A' ;
                       $$ = vars[i]; 
                     }
             | expr  '+'  expr  { $$ = $1 + $3; }
             | expr  '-'  expr  { $$ = $1 - $3; }
             | expr  '*'  expr  { $$ = $1 * $3; }
             | expr  '/'  expr  { $$ = $1 / $3; }
             | expr  '^'  expr  { $$ =  pow ($1, $3);}
             '~'  expr   { 
                        $$=~( int )$2;                  
                  }
                 '!'  expr   {
                     if (!( int )$2)
                      printf ( "true\n" );
                     else
                      printf ( "false\n" );
                      flag=0;
                  }
             | expr  '%'  expr  { $$ = ( int )$1 % ( int )$3; }
             '-'  expr    { $$ = -$2; }
                 '('  expr  ')'   { $$ = $2; }
             | COS expr  { $$ =  cos ($2 * Pi /180); }
             | SIN expr  { $$ =  sin ($2 * Pi /180); }
             | TAN expr  { $$ =  tan ($2 * Pi /180); }
             | COT expr  { $$ =1/ sin ($2 * Pi /180);}
             | expr LOG expr     { $$ =  log ($1)/ log ($3); }
             | expr AND expr { 
                      printf ( "与前的二进制($1):\n" );
                                  convert($1,2);
                      printf ( "\n" );
                          printf ( "与前的二进制($3):\n" );
                                  convert($3,2);
                                  printf ( "\n" );
                                  $$=( int )$1&( int )$3;
                                  printf ( "结果的二进制($$):\n" );
                                  convert($$,2);
                                  printf ( "\n" );
                     }
             | expr OR  expr {
                                  printf ( "或前的二进制($1):\n" );
                                  convert($1,2);
                                  printf ( "\n" );
                                  printf ( "或前的二进制($3):\n" );
                                  convert($3,2);
                                  printf ( "\n" );
                                  $$ =( int )$1|( int )$3;
                                  printf ( "结果的二进制($$):\n" );
                                  convert($$,2);
                                  printf ( "\n" );
                     }
                 | expr and expr {
                     if ( ( int )$1 && ( int )$3)
                       printf ( "true\n" );
                     else
                       printf ( "false\n" );
                              flag=0;
                     }
             | expr or  expr {   
                     if ( ( int )$1 || ( int )$3)
                                       printf ( "true\n" );
                                     else
                                       printf ( "false\n" );
                     flag=0; 
                     }
             | expr PP   {   $$ =$1+1;}
             | expr SS   {   $$ =$1-1;}
             | expr LOL expr { 
                                 printf ( "移位前的二进制:" );
                                 convert($1,2);
                                 printf ( "\n" );
                                 $$ =( int )$1<<( int )$3;
                                 printf ( "移位后的二进制:" );
                                 convert($$,2);
                                 printf ( "\n" );
                             }
             | expr LOR expr { 
                                 printf ( "移位前的二进制:" );
                                 convert($1,2);
                                 printf ( "\n" );
                                 $$ =( int )$1>>( int )$3;
                                 printf ( "移位后的二进制:" );
                                 convert($$,2);
                                 printf ( "\n" );
                             }
             ;
    cmdline :  DROP  {  exit (0);}
             |  CLEAR  { 
                         system ( "clear" );
                     }  
              | LIST   {   
                          for (i=0;i<26;i++)  
                          printf ( "\t%c=%g\n" , 'a' +i,vars[i]); 
                       }  
              | ERASE    {  for (i=0;i<26;i++) vars[i]=0;  printf ( "已经清空所有的寄存器的值!\n" );}  
              | HELP     {  
                         printf ( "命令:\n" );  
                         printf ( ">>help :获取帮助.\n" );  
                         printf ( ">>ans  :列出上次计算的结果.\n" );  
                         printf ( ">>list :列出寄存器中所有的值 'a'/'z'.\n" );  
                                     printf ( ">>erase:重置寄存器.\n" );  
                         printf ( ">>clear:清屏.\n" );  
                         printf ( ">>drop :退出程序.\n" );  
                         }
             ;   
    %%
    int  yyerror( char  *s)
    {
       printf ( "%s\n" , s);
       return  1;
    }
    void  convert( int  num , int  mode)
       {
            if (num/mode==0)
            {
             
             printf ( "\t%d" ,num); return ;}
              else
             {
               convert(num/mode,mode);
               printf ( "%d" ,num%mode);
             }
       }
    int  main( int  argc, char  **argv)
    {
             printf ( "\t  _______________________________________________________________________ \n" );
         printf ( "\t |                      HeFei Noraml University                          |\n" );
         printf ( "\t |   1410441036  计算科学与技术(嵌入式)     编译原理课程设计     童慧林  |\n" );
         printf ( "\t |  _______________                                                      |\n" );  
             printf ( "\t | |_______  ______|    + - * / ^  || &&   操作数 操作符 操作数          |\n" );  
             printf ( "\t |        |  |          ++ --                                            |\n" );  
             printf ( "\t |        |  |      _____        ______        ______                    |\n" );  
             printf ( "\t |        |  |     |     |      |      |      |      |     a=1           |\n" );  
             printf ( "\t |        |  |     |     |      |      |      |      |     b=2           |\n" );  
             printf ( "\t |        |  |     |_____|      |      |_     |______|     a+b           |\n" );  
             printf ( "\t |        |  |                                       |     sin 30        |\n" );  
             printf ( "\t |        |  |            1    +    1                |         =0.5      |\n" );    
             printf ( "\t |        |  |    sin cos tan cot log         |______|     5 log 5       |\n" );  
             printf ( "\t |        |__|    <<  >>  1<<2  3>>1                            =1       |\n" );  
         printf ( "\t |_______________________________________________________________________|\n" );   
         yyparse();
    }

     执行脚本1.sh

    1
    2
    3
    4
    5
    6
    #!/bin/bash
    #
    bison -d test.y
    flex test.l
    gcc lex.yy.c test.tab.c -lm -o test
    ./test

    运行结果:




     

    展开全文
  • 吉森 注意 该存储库包含由GerHobbelt维护的fork。 JISON的原始工作由Zachary Carter完成,可在zaach / jison中获得。 有关所有更改(修复和功能)的概述,请参阅“之处”部分 更进一步。 另请参阅 。...
  • Yacc Lex 资料集 包括: Flex&Bison中 英文版 pdf lexyacc中 英文版 pdf calc 计算器实例源代码 Windows下的flex bison 工具 及其他应用论文 资料等
  • yacc lex桌面计算器

    2009-06-17 23:59:50
    yacclex做的计算器。 包含一个rule.l文件和cal.y文件。
  • 编译原理课程中所用YaccLex快速入门.LexYacc 是 UNIX 两个非常重要的、功能强大的工具。事实上,如果你熟练掌握 LexYacc 的话,它们的强大功能使创建 FORTRAN 和 C 的编译器如同儿戏。
  • windows 下yacc lex

    热门讨论 2009-03-28 12:35:31
    windows 下yacc lex 简单源码; vs6平台
  • Windows平台下学习LexYacc的必备工具,flex-2.5.4a-1.exe 和 bison-2.4.1-setup.exe 以及 cygwin2.738 的安装文件
  • 反向自动微分的yacclex实现 文章目录反向自动微分的yacclex实现0. 写在前面2. lex词法分析器和yacc语法分析器lex难点解答yacc难点解惑3. 生成可执行文件4. 写在最后 0. 写在前面 为了完成编译原理的实验,本人在...

    反向自动微分的yacc和lex实现

    0. 写在前面

    为了完成编译原理的实验,本人在互联网上搜索yacc和lex相关的用法,但是解惑和细节的内容相对来说比较少,有的小部分也没有进行说明。于是在经历了很长时间的外文互联网搜索和阅读英语手册后,我完成了这个实验,并且决定写一写我对于yacc和lex工具的理解。当然源代码在GitHub上,如果觉得我的文章对你有帮助,还请不吝star和点赞和收藏。

    1.反向自动微分

    反向自动微分在互联网上可以找到很多相关资料,各位可以去看知乎上的文章,基本上大同小异。具体的原理就不介绍了,主要就是大一学过的多变量微积分中的链式求导法则,是很好理解的。

    反向自动微分的好处在于,只要扫描两遍图就能输出f(x1,x2,x3…)对于每一个变量的偏导数,相比正向自动微分有n个变量就要扫描n遍,这简直是天大的好事。

    这里给一个例子让大家亲自算一算如何进行反向自动微分。如果可以的话,请拿出纸和笔在纸上和我的步骤一起写,并且仔细体会我写的步骤,这样会有更好的理解。

    首先要创建数据结构,包括内容为:名字,值,梯度值,节点类型,以及它的孩子指针reverse[0]和reverse[1]。由于我们在这里实现的微分最多是二目运算符(+,-,*,/,^等),或者是单目运算符(单个负号,sin,cos,exp等),所以一个节点最多只会有两个孩子,所以只需要两个孩子指针。每次建立节点都进行初始化操作:节点的名字为空,节点的孩子全部设置为null;

    1. 给出算式:f(x=3,y=5):x^2+2*sin(y)

    2. 为这个算式建立一个计算图,节点建立的顺序是按照从左到右扫描以及算符的优先程度建立,相当于编译原理最左规约。具体方法如下:

      1. 读入f(x=1,y=2):为x和y分别建立一个节点。节点的名字是“x“和”y“,节点的类型是“变量”,节点的值设置为变量的值,也就是3和5。节点的序列号是0和1,代表这两个节点是第一个和第二个创建的节点。

      2. 读入f(x=1,y=2):x:x已经创建了节点,所以不需要创建新节点。

      3. 读入f(x=3,y=5):x^2:为2创建一个单独的节点,节点类型是“数字”,节点值为2,节点序列号是2.

      4. 读入f(x=3,y=5):x2+:发现+优先级较低,于是为x2创建一个单独的节点,节点类型是““,节点的值是x2=9,节点的序列号是3,表明是我们第四个创建的节点。此时,将这个节点的两个孩子指针分别指向”x“和”2“的节点,也就是我们在1和3步骤中创建的节点,序号是0和2。这个操作表明:我们创建的3号节点对于x和2进行了乘方操作。

      5. 读入f(x=3,y=5):x^2+2:对新读入的2创建一个新的节点。可能你会问为什么不重新利用前一个已经为数字2创建的节点,当然你可以这么做,但是这么做并不是必要的,并且创建一个新节点可以为我们省去不少麻烦,所以何乐而不为呢?这一步剩下的操作和之前相同。此时节点的序列号应该是4。但是请注意:如果重复使用了x或者y这样的变量,你不能重复创建x或者y的新节点!原因是多元函数的微分应该把微分路径上的偏导数都相加。举个例子:f(x=2):3*x+ln(x)中,3*x节点的孩子是数字3和x,ln(x)的孩子是x,这两个x是同一个x,也就是说这两个x都是我们在第一步中创建的那个节点!原因是:反向计算时,指向x的指针会有两个,也就是3*x节点和ln(x)节点,相当于有两条路径会通向x节点,所以对于x来说,3*x和ln(x)就相当于两个不同的都和x相关的变量y(x)和z(x),那么对x的全微分就是dx=dy+dz,所以后续求x的偏导数时这两个节点的梯度值都应该用上,如果为x创建了两个不同的节点,那这两个节点的结果必须相加才是最终答案!这样既不省空间,也不省力气。

      6. 读入f(x=3,y=5):x^2+2*sin:发现sin的优先级比*乘法更高,于是我们继续向下读,这样可以找到sin的操作对象。

      7. 读入f(x=3,y=5):x^2+2*sin(y):我们发现sin已经读完了,并且我们知道为y创建的节点的序列号是1.现在我们创建一个节点,节点的第一个孩子指向y节点,节点的类型是sin,节点的值就是sin(5),序列号是5。

      8. 读入f(x=3,y=5):x^2+2*sin(y):我们的创建历程并没有结束!我们为2*sin(y)建立一个新的节点,节点的两个孩子指向新创立的2和y节点,节点的类型是*,节点的值就是2*sin(5),序列号是6。

      9. 读入f(x=3,y=5):x2+2*sin(y):我们的创建历程还是没有结束!x2+2*sin(y)创建一个新的节点,节点的两个孩子指向x^2和2*sin(y)节点,节点的类型+,节点的值就是算出来的值,序列号是7。

      10. 最后为了方便,我们创建一个root节点。这个节点的类型是root,孩子指向第9步中的节点,值和孩子相同,序列号是8,但是此时我们将root节点的梯度设置为1。此时我们的创建节点的过程已经结束了。这时候,我们将开始反向进行梯度的计算。你创建的图应该和下方的图是一样的。下图中的数字代表节点的序号。这样我们就从左到右得到了一个可以使用的计算图。
        产生的计算图,反向计算时从8到0计算

        //下方是上图的mermaid代码,可以使用markdown查看
        graph RL
        root,8-->7
        7-->6:2*siny
        7-->3:x平方
        3:x平方-->0:x
        3:x平方-->2:第一个2
        4:第二个2
        5:siny-->1:y
        6:2*siny-->4:第二个2
        6:2*siny-->5:siny
        

        我们将从节点8,也就是root节点开始反向计算。

    3. 处理完算式以后,我们的root里面应该已经算出了整个算式的值。现在我们反向求微分。我们下面都用grad代表节点的梯度值,用Vi代表节点的算式,方便阅读。我们写成Vi形式的时候,其实是把i节点所代表的算式当成一个变量处理,请记住这一点。

      1. 设置root的grad为1;

      2. 对于7节点:f(x) = x^2+2*sin(y) = V7。在这里,我们就把V7当成一个变量,并且求f(x)=V7对于变量V7的偏导数。这里由于f(x)只和V7相关,所以偏导数就是导数。由于y(x)=x的导数是1,我们很容易得到我们要求的偏导数是1。之后根据链式法则:

        dx/dy = (dx/dz)(dz/dy),我们要把这一步算出来的1和前一步算出来的导数相乘,就得到了这个节点的梯度值。也就是1*1 = 1。我们把1填在节点7的grad中。后续所有节点都仿照这样进行计算即可。从8号节点按照递减的顺序向下求,最后到0和1节点,0和1节点里面的值就是我们想求的答案:分别是f对于x和y的偏导数。

      3. 比如2*siny,也就是6号节点。要求V6的grad,我们要看向V7。V7 = V3 + V6 也就是x^2+2*siny。显然V7对于V6的偏导数就是1,对于V3的偏导数也是1。之后我们令V3.grad += V7.grad * 1(为什么是+=在前面加粗的地方说过了。虽然那里说的是x,但是对于每一个有两个入边的节点都成立,所以我们都用+=运算符),V6.grad += V7.grad * 1;

      4. 同理我们最后就能求得结果。在x节点中存储的就是对x的偏导数,y节点中就是对y的偏导数。

    4. 现在你可以亲自动手算一算知乎上或者其他地方都用的这个例子:f(x1=2,x2=3):ln(x)+x1*x2-sin(x2)的偏导数值了。

    2. lex词法分析器和yacc语法分析器

    1. yacc和lex简洁版手册 http://dinosaur.compilertools.net/
    2. 小型c语言编译器 https://github.com/rabishah/Mini-C-Compiler-using-Flex-And-Yacc
    3. lex和yacc小型计算器 https://developer.ibm.com/technologies/systems/tutorials/au-lexyacc/

    我们在写yacc之前,需要一个能够向yacc传递词语的工具。这个工具就是lex。当然你可以在yacc中自己写,但是用lex生成词法分析器是更好的选择:自动,方便,快捷,有丰富的全局变量。lex的主要用法在很多教材上都有。上面推荐一些网站,可以去看一看。看完以后再回来看我的这篇文章,接下来主要是进行一些讲解,但是是建立在你已经知道lex、yacc是什么和基本用法上的基础上的。

    接下来是细节部分的解惑。完整代码在Github上。

    lex难点解答

    下面是一段源代码,我已经给出了较为详细的注释。正规式的语法之类教材或者网上都可以找到,这里不过多讲解。lex文件也可以加上主程序单独生成lex.yy.c,网上也有详细的案例和教程。lex默认输入输出yyin和yyout是stdin和stdout,也就是打印到屏幕,从屏幕读取。你可以修改这两个指向你的文件,这样lex就可以读入或者输出文件。主程序借鉴了https://www.cnblogs.com/wp5719/p/5528896.html,但是只用于说明,不保证可以正常运行。读者应自己编写main函数。

    %{//开头
      //这个部分里面的所有内容都会被原封不动拷贝进生成的lex.yy.c文件,所以可以像C语言代码一样编写。
        #include <stdio.h>					//在调试打印过程中,我们需要调用printf,所以在这里包含头文件
        #include <string.h>
      	//extern char* yytext				
      	//yytext是lex自带的extern类型变量,它指向正在分析的单词的string的头部,可以直接printf或者传递给yacc
        extern int yylval;					
      	//yylval是lex自带的一个extern类型的变量,可以用于在yacc和lex之间传递变量的值。在这里是																	//为了lex文件单独使用时声明的
      	
    
    //    enum op{zs, NUMBER, IDENT ,FUNC ,LEFTPA,RIGHTPA ,COMMA ,COLON,EQUA,PLUS ,MINUS,MUL ,DIV,COS ,SIN, LN ,EXP, POW };
    /*同样是为了lex单独使用,如果和yacc一起使用,你应该在yacc文件中定义这些变量。zs用于占位,这样就不会return 0,虽然可有可无*/
      
    %}//结束
    
    /*一些常规定义,使用了正规式*/
    letter [A-Za-z]			//代表任意一个字母
    digit [0-9]					//代表任意一位数字
      
    %%		//用两个百分号开头表示进入词法规则部分。下面的printf用于调试。此部分注释不能顶格写。
    		
      		//越是排在前面的优先级越高,所以你不能把下方的用于识别标识符的规则放在cos、sin之前,这样lex会认为cos是一个标识符而不是代					表cos这个函数!
      
    "\n"            {
      									//大括号里的部分都会被拷贝进生成的C文件!正常使用即可
      									//注意:lex会分析所有输入的符号,包括回车在内!如果不识别回车,很可能会报错!!!
                        // printf("enter\n");
                        return EOL;
                    }
    
    "cos"            {
                        // printf("%s, cos\n",yytext);
                        return COS;
                    }
    
    "="             {
                        // printf("%s, equal\n",yytext);
                        return EQUA;
                    }
    
    /*
    			这里填写其他的规则,因为篇幅原因略去
    */
    
    	//下面正规式表示:一个字母开头,后面为空或者跟了一串字母和数字的组合,可以是x,x1,x12,xe2r4等
    {letter}({letter}|{digit})*             {   
                                                // printf("%s,ident\n",yytext);
                                                return IDENT;
                                            }
    	//下面表示最少有一位的数字组合,如0,12,08,184819657981465等
    {digit}+        {
                        // printf("%s, number\n",yytext);
                        yylval = atoi(yytext);			
      									//将yytex的字符串转换成数字传给yylval用于yacc使用
                        return NUMBER;
                    }
    
    %%	//如果下方没有函数,这个百分号是不必要的。下面这部分的代码也会被拷贝进生成的c文件,所以可以正常按照C语言的格式写
      
       
      /*
        可以用于编写测试用的main函数等等。你可以使用yylex()、yylval等返回上方的数字,IDENT,COS等并加以利用,可以上网搜索例子
        注意:单独使用lex时,一定要记得定义IDENT等变量!因为这些本应该在yacc中定义!
     
      int main (int argc, char ** argv){
    	int c,j=0;
    	if (argc>=2){
    	  if ((yyin = fopen(argv[1], "r")) == NULL){
    	    printf("Can't open file %s\n", argv[1]);
    	    return 1;
    	  }
    	  if (argc>=3){
    	    yyout=fopen(argv[2], "w");
    	  }
    	}
    	 * yyin和yyout是lex中定义的输入输出文件指针,它们指明了
    	 * lex生成的词法分析器从哪里获得输入和输出到哪里。
    	 * 默认:键盘输入,屏幕输出。 
    
    	while (c = yylex()){
    		writeout(c);
    		j++;
    		if (j%5 == 0) writeout(NEWLINE);
    	}
    	if(argc>=2){
    	  fclose(yyin);
    	  if (argc>=3) fclose(yyout);
    	}
    	return 0;
    }*/
    

    yacc难点解惑

    下面是一段yacc源代码,我已经给出了较为详细的注释。正规式的语法之类教材或者网上都可以找到,这里不过多讲解。lex文件也可以加上主程序单独生成y.tab.c,网上也有详细的案例和教程。根据使用的系统不同,yacc可能是bison等,但是使用上差别不大。也有可能会生成y.tab.h,这时候就请根据网上的其他进行修改。
    众所周知,我们可以使用继承属性和综合属性。下面的代码没有使用继承属性,但是可以参考:https://github.com/aoxy/RevAutoDiff-with-Flex-and-Bison 使用union定义了终结符和非终结符的属性,用struct包含继承和综合属性,从而进行计算。

    %{	
      	//和lex一样,这个部分里面所有的内容都会被拷贝进生成的C文件。你可以自己写额外的文件并包含在这里
        #include <ctype.h>									//包含C代码片段的所需库
        #include <stdio.h>
        #include <string.h>
        #include "lex.yy.c"									//包含lex产生的C文件,里面包含我们所需的词法分析器和yylval等变量
        #include <math.h>
        #define YYSTYPE int									//定义程序使用的栈的数据类型,也就是后面$$或者$3等的类型,这里是int
        int yyparse(void);									//定义yacc的一些函数用于使用,具体实现不需要我们自己写
        int yyerror(char *s);
        int count_id = 0;										//定义一些我们所需的全局变量,分别是标识符节点计数,非标识符节点计数和所有节点数
        int count_node = 0;
        int all_node = 0;
    
        enum OP {zs,ROOT,ID,NU,PL,MN,UMN,DI,MU,P,L,C,S,EX};
      																			//定义一些我们需要进行计算的操作符号,也就是在反向自动微分中的节点类型
    
        typedef struct Node{								//定义反向自动微分中的节点数据结构
            double diff;
            double val;
            char name[10];
            enum OP op;
            struct Node *reverse[10];
        }node;
    
        node *chain[40];										//定义最多40个节点的“钥匙串”,可以按顺序存储生成的节点
    
        node* CreateNode();									//声明一些函数,具体实现在最后,你也可以把它们放在其他文件里,但是记得要include
        void calcVal(node *n);
        int FindID(char name[]);
        void ReverseAutoDiff(node *chain[]);
    %}//结束
    
    
    /*这里是定义lex和yacc中的终结符号。token就是普通的定义,left和right表示运算符左右结合,优先级是越下方的越高,这里MUL乘法的优先级就比PLUS加法高,比cos余弦运算要低。*/
    
    //%start Start 					yacc会默认第一个产生式为开始符号,所以这里可以不写
    /*
    %union  {
        struct  tnode*  node;
        char            name;
        char*           str;
    }
    
    %type   <name>  func_def				你可以为非终结符也设置定义,但是注意:要先用union包含你想使用的定义!左边是一些例子!
    %type   <name>  var_list				不代表可以正常使用,请读者根据需要编写!
    %type   <name>  var_init
    %type   <node>  expr
    */
    %token EQUA
    %left PLUS MINUS
    %left  MUL DIV
    %token COS SIN
    %left LN EXP POW 
    %token NUMBER IDENT FUNC LEFTPA RIGHTPA COMMA COLON EOL
      
    %%  //第一部分结束,进入第二部分。第二部分内容会删减,完整在github
    
    Start : REV_AutoDiff										//这一步是为了可以输入多个表达式进行求值
            | REV_AutoDiff Start
            ;
    
    REV_AutoDiff : func_def EOL							//使用EOL表示已经结束了!这很重要!
        {
            printf("val = %lf\n",chain[all_node-1]->val);				//打印表达式的值
            ReverseAutoDiff(chain);							//调用函数计算偏微分
        }
        ;
    
    func_def : FUNC LEFTPA var_list RIGHTPA COLON expr
        {
            node *x = CreateNode();					//创建节点
            chain[all_node++] = x;					//放进“钥匙串”,总节点数+1,除了标识符节点外的节点数+1
            count_node++;
            x->op = ROOT;										//设置节点类型
            x->reverse[0] = chain[$6];			//将孩子指向钥匙串中的点。
      /*
      				这里为什么是$6呢?因为$$代表冒号左边的值,$1是FUNC,$2是LEFTPA,$3是var_list,以此类推,expr的值是$6
      */
            // printf("x->reverse[0]: %d, val: %lf\n",$6, chain[$6]->val);
            calcVal(x);
        }
        ;
    
    var_init : IDENT 
        {		//注意:这个大括号算$2!!!
            node *x = CreateNode();
            chain[all_node++] = x;
            count_id++;
            x->op = ID;
            strcpy(x->name,yytext);					//这里就使用了yytext变量!
            // printf("yytext: %s, x->name: %s\n",yytext, x->name);
        }
        EQUA NUMBER
        {
            node *x = chain[all_node-1];
            // printf("ident name:%s, ident val:%d\n",x->name,$4);
            x->val = $4;
        //重要!这里为什么是$4?因为IDENT是1,EQUA是3,NUMBER是4!中间的那一段大括号也算在里面,是$2!各位可以试试打印$2的值
        }
        ;
    
    var_list : var_init 
        | var_list COMMA var_init
        ;
    
    expr : IDENT
            {
                int x = FindID(yytext);
                // printf("x value: %d\n",x);
                $$ = x;
                
            }              
        | LEFTPA expr RIGHTPA
            {
                $$ = $2;				//这里甚至可以不用写,因为yacc使用的是栈,这里默认就会把expr的值放在栈顶,不需要赋值
            }  
      
    		/*
    				其他产生式,这里省略
    		*/
        ;
    %%
    extern FILE *yyin;					//可以不写,默认stdin
    int main()
    {
        yyin = stdin;
        return yyparse();				//表示一直进行下去
    }
    
    yyerror(s) 									//会自动产生错误放进stderr,所以直接打印即可
    char *s;
    {
        fprintf(stderr, "%s\n", s );
    }
    
    int yywrap(){
        return 1;								//用于表示有没有分析完成的函数,默认返回1
    }
    
    node* CreateNode(){					//用于实现反向自动微分的函数,完整在GitHub,比如这里给出的创建节点函数
        node* x = (node*)malloc(sizeof(node));
        x->diff = x->val = 0;
        for(int i = 0; i < 10; i++){
            x->name[i] = '\0';
            x->reverse[i] = NULL;
        }
        /* printf("Build node: %d\n",all_node); */
        return x;
    }
    
    /*
    	其他必要函数
    */
    

    3. 生成可执行文件

    在macOS或者Linux的终端中依次输入:

    cd 你的存放.l和.y文件的目录
    lex 你的文件名.l 
    yacc 你的文件名.y 
    gcc y.tab.c -ly -ll
    

    如果无法生成,请检查你是否安装了flex(lex),bison(yacc),并使用homebrew等方式安装

    第一步会产生lex.yy.c

    第二步会产生y.tab.c

    第三步,编译y.tab,c,链接ly和ll库,这两个库中包含了许多我们没有定义或者使用,但是yacc和lex需要的函数,比如yyerror等就在其中。不包含这两个库会导致程序出错!这条指令会根据系统有所不同,非mac用户请搜索具体使用方法。

    最后运行你的程序,双击打开a.out 或者:

    ./a.out
    

    注意:如果你的lex没有处理空格和换行符,那么输入空格或换行符将导致出错!想处理这些,可以在lex中添加规则:

    delim		[\t\n]
    			/* 转义字符,\t表示制表符,\n表示换行符。*/
    ws			{delim}+
    
    {ws}	          {;/* 此时词法分析器没有动作,也不返回,而是继续分析。 */}
      /* 
      	正规式部分用大括号扩住的表示正规定义名,例如{ws}。
      */
    

    4. 写在最后

    编译原理其实不是一门简单的课程,虽然这次试验比较艰难,但是确实能学到不少东西。本人水平有限,有错漏之处还请斧正。

    我的GitHub名:isaacveg

    此项目GitHub地址:https://github.com/isaacveg/Yacc-lex-for-reversed-automatic-differentiation

    如果觉得有用,请点赞或者star,你的肯定对我很重要!欢迎通过各种方式和我联系关于文章内容的错误或者不当之处,但是我不会给你源代码或者帮你编译!自己学习也是很重要的!

    展开全文
  • 编译原理 yacc lex

    2010-06-27 10:40:22
    简单介绍lexyacc的使用及配置方法 还有学习笔记等 附含工具
  • win10下YACCLEX的安装

    千次阅读 2018-11-06 15:19:56
    Win10下yacclex的安装 (后边有问题烦请大神留言解决一下感激不尽) First download the package of unxutils.zip  URL:http://unxutils.sourceforge.net/UnxUtils.zip  Then unzip it to a folder, like ...

    Win10下yacc和lex的安装


    (后边有问题烦请大神留言解决一下感激不尽)


    First download the package of unxutils.zip 

    URL:http://unxutils.sourceforge.net/UnxUtils.zip 

    Then unzip it to a folder, like me D:\UnxUtils

    Set the path in your system environment, just do it like this:

    新建lex.l和yacc.y两个文件。

    自己写一段测试代码进行测试。

    测试lex

    测试bison

    Bison执行时出现的问题,permission denied。

     

    展开全文
  • Windows下的yacc lex工具(Z+源码)

    万次阅读 2009-03-27 22:26:00
    本测试的源码顺利编译通过,有需要的同仁可以从这里下载:http://download.csdn.net/source/1151308或者在csdn资源里搜widnows yacc lex from: http://blog.csdn.net/JsuFcz/archive/2008/11/18/3331107.aspx 2009...
  • yacc&lex-调用C++代码

    2017-02-07 18:40:13
    要点用lex&yacc命令缺省生成的是C文件,但事实上,仅是文件扩展名表示为C文件。可以用g++或者直接改名为C++就可以在lex&yacc中用C++功能。
  • LexYacc学习资料

    2018-04-05 17:25:10
    内含Lexyacc.pdf,Lexyacc从入门到精通.pdf和编译的重要性.pdf文件 供使用lexyacc的朋友学习
  • Lex/Yacc Lex结合Yacc

    千次阅读 2016-06-11 00:15:11
    这次就来尝试lex结合yacc。 简单lex 先写个简单lex,如下: %{ #include "name.tab.h" #include #include extern char* yylval; %} char [A-Za-z] num [0-9] eq [=] name {char}+ age {num}+ %% {name} { ...
  • lex&yacc系列(2)--- lex介绍及实例

    千次阅读 2019-02-02 22:47:34
    A question that sometimes drives me hazy--am I or the others crazy?——Einstein Lex: For a C program, the units are variable names, constants, strings, operators, punctuatio...
  • LexYacc》中文第二版,高清扫描版,包含书中例子的代码实现。
  • centOS yacc lex

    千次阅读 2013-11-28 20:36:32
    yacc command not found configure: error: Your operating system's lex is insufficient to compile  libpcap. flex is a lex replacement that has many advantages, including  being able to ...
  • lexyacc构建的SQL分析器,lexyacc构建的SQL分析器
  • yacc lex 词法解析与语法解析

    千次阅读 2014-10-24 00:39:53
    lexyacc 均包含三部分:定义段 、规则段和、代码段 (1)定义段可以包含任意的C语言文件,符号说明,其代码会被直接拷贝到生成的扫描器代码文件中 声明示例如下 %{ ... %} (2)...
  • Java编译器 2012/6 制作一个可以将Java代码转换为Java字节码的编译器
  • Python Lex Yacc手册

    千次阅读 2020-02-23 22:22:46
    本文是PLY (Python Lex-Yacc)的中文翻译版。转载请注明出处。 如果你从事编译器或解析器的开发工作,你可能对lexyacc不会陌生,PLY是David Beazley实现的基于Python的lexyacc。作者最著名的成就可能是其撰写的...
  • lexyacc环境配置

    千次阅读 2017-06-03 16:01:30
    lexyacc的使用很简单,但环境配置却是各种问题,本章说明lexyacc在windows下的环境配置。 软件需求: 系统 win7-64位(win7-32, win8, win10全部通过) c++编译器: vs2010(2008,2013,2015也全部通过) lex...
  • LexYacc(第二版)超清晰中文版

    热门讨论 2010-02-22 12:58:35
    只包含1-5章,不过看完这5章...第一章 lexyacc 第二章 使用lex 第三章 使用yacc 第四章 菜单生成语言 第五章 分析SQL 第六章 lex规范参考 第七章 yacc语法参考 第八章 yacc歧义和冲突 第九章 错误报告和恢复
  • lexyacc(二)计算器的实现 2011年09月24日 18:44:24ecbtnrt阅读数 4311 构建一个c语言的编译器并不是一件容易的事,我想每个人在学习编译原理的时候并不会常见得它非常简单. 下面将会学习编译器的两个重要组成部分...
  • lexyacc_lexyacc_源码

    2021-10-03 12:23:33
    lex yacc pdf version ebook
  • Javacc的获取 同lexyacc一样,javacc也是一个免费可以获取的通用工具,它可以在很多JAVA相关的工具下载网站下载,当然,javacc所占的磁盘空间比起lexyacc更大一些,里面有标准的文档和examples.相对lexyacc来说,...
  • 一个Lex/Yacc完整的示例(转)

    万次阅读 多人点赞 2017-12-18 15:01:39
    本框架是一个lex/yacc完整的示例,包括详细的注释,用于学习lex/yacc程序基本的搭建方法,在linux/cygwin下敲入make就可以编译和执行。大部分框架已经搭好了,你只要稍加扩展就可以成为一个计算器之类的程序,用于...
  • 很多人都知道lex&yacc,特别是计算机科班毕业的。因为在unix上自带的bshell就有这些功能。 cygwin里面也有这些。 可是,这种基本的功能,在win32平台下,微软似乎忘了。 有个外国人写了个软件Parser Generator (bum...
  • Date: 2020年6月11日 ****************************************************************************/ #include "mysql4lex.h" #include #include #include #include #include #include #include %} %left OR %...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 11,331
精华内容 4,532
关键字:

Yacclex