其他分享
首页 > 其他分享> > 03-语法解析

03-语法解析

作者:互联网

词法解析(flex)和语法解析(bison)

自然语言中语言学习包含:单词和语法。

在编程语言中也包含单词和语法,分别有flex和bison来实现。

所以flex和bison的作用就是:用C语言来实现另外一种语言(SQL、python等)

flex :LEX 词法分析 (lexical analysis)

单词提取:提取编程语言占用的各种保留字、操作符等等语言的元素

lex把每个扫描出来的单词叫做token,token可以有很多类。对比自然语言中名词、形容词等,而汽车就是属于名词类型的一个具体token。在编程语言token类型时有限的:关键字。

lex工具会帮我们生成一个yylex函数,yacc通过调用这个函数来得知拿到的token是什么类型的,但是token的类型是在yacc中定义的。

lex的输入文件一般会被命名成 .l文件,通过执行命令 lex XX.l 我们得到输出的文件是lex.yy.c

bison :YACC 语法分析syntactic analysis

语法表达:如何使用LEX中的“单词”组合成语法

yacc会帮我们生成一个yyparse函数,这个函数会不断调用上面的yylex函数来得到token的类型。

yacc的输入文件一般会被命名成 .y文件,通过yacc -d XX.y我们得到的输出文件是y.tab.h y.tab.c,前者包含了lex需要的token类型定义,需要被include进 .l文件中

lex和yacc的输入文件格式

Definition section
%%
Rules section
%%
C code section

.l和.y的文件格式都是分成三段,用%%来分割,含义如下:

可以放C语言的各种各种include,define等声明语句,但是要用%{ %}括起来。

如果是.l文件,可以放预定义的正则表达式:minus “-” 还要放token的定义,方法是:代号 正则表达式。然后到了,Rules Section就可以通过{符号} 来引用正则表达式

如果是.y文件,可以放token的定义,如:%token INTEGER PLUS ,这里的定一个的每个token都可以在y.tab.h中看到

.l文件在这里放置的rules就是每个正则表达式要对应的动作,一般是返回一个token

.y文件在这里放置的rules就是满足一个语法描述时要执行的动作

不论是.l文件还是.y文件这里的动作都是用{}扩起来的,用C语言来描述,这些代码可以做你任何想要做的事情

main函数,yyerror函数等的定义

YACC声明部分

标识符含义
%start指定起始规则
%token定义文法中的终结符
%left左结合
%right右结合
%nonassoc不能结合
%union:声明标识出的符号值可能拥有的所有C类型
%type:声明一些有特定语义值类型的非终结符

实现步骤

定义方式是:正则表达式–>对应操作。如果和yacc一起来使用的话,对应的操作通常是返回一个token类型,这个token的类型要在yacc中提前定义好。

BNF

用于对编程语言、文档格式、指令集、或者通信协议等的语法定义

在YAGG中书写方式如下:

  <symbol> : __expression1__ {operation}
  
  		 | __expression2__ {operation}

operation 是 满足语法时要执行的C语言代码,这里的C语言代码可以使用一些变量,他们是:$$ 1 ! [ ] ( h t t p s : / / g . y u q u e . c o m / g r / l a t e x ? 2 1 ![](https://g.yuque.com/gr/latex?2%E7%AD%89%E7%AD%89%E3%80%82#card=math&code=2%E7%AD%89%E7%AD%89%E3%80%82) 1![](https://g.yuque.com/gr/latex?2代表规约的结果,就是表达式symbol的值,$1代表的是前面 __expression1__中出现的各个word

例子

  expr2: expr3 { $$ == $1; }
  	   | expr2 PLUS expr3 { $$ = plus($1, $3); }
  	   | expr2 MINUS expr3 { $$ = minus($1, $3); }
  ;

移进(shift)和规约(reduce)

移进:用一个寄存符号的先进后出栈,把输入符号一个一个地移进到分析栈里,这就是移进。上述的expr1expr2

规约:当栈顶形成某个产生式的表达式时,即把栈顶的这一部分替换成(归约为)该产生式的左部符号。上述中的expr

移进规约冲突

当移进和规约同事满足时,不知道应该执行移进还是规约。

  expr:
       expr - expr
       | expr * expr
       | - expr

对于输入:

解析完1后,可以继续移进 * ,或者根据规则 expr:-expr 规约为 -1。也就是说,解析方式有两种:

-1 * 2   --->     = (-1)*2

-1 * 2   --->     = -(1*2)

解决方法

定义优先级:先定义的优先级低,后声明的优先级高,同行的优先级相同

  %left '+' '-'
  %right '*' '/'
  %nonassoc '<'

注意:语法解析中很多都是移进规约冲突导致失败,在修改该处代码时需要学习相关语法

编译

  LEX编译	
       flex XXX.l  (编译为lex.yy.c)
       gcc lex.yy.c -lfl  (默认编译为a.out)
       ./a.out
  YACC编译
       bison -d XXX.y   (编译为XXX.tab.c和XXX.tab.h)
       gcc XXX.tab.c   (默认编译为a.out)
       ./a.out

示例

实现简单的计算器语法

calc.y代码如下:

  %{
  #include <stdio.h>
  int yyerror(char *s);
  #include "lex.yy.c"
  %}
  
  %token NUM
  %token ADD SUB MUL DIV
  %token LP RP
  %token EOL
  
  %%
  start:
   | start expr EOL { printf("= %d\n> ", $2); }
   | start EOL { printf("> "); } 
  ;
  
  expr: term { $$ = $1; }
   | expr ADD term { $$ = $1 + $3; }
   | expr SUB term { $$ = $1 - $3; }
   ;
  term: factor { $$ = $1; }
   | term MUL factor { $$ = $1 * $3; }
   | term DIV factor { $$ = $1 / $3; }
   ;
  
  factor: NUM { $$ = $1; }
   | LP expr RP { $$ = $2; }
   ;
  %%
  
  int main(int argc, char **argv)
  {
    printf("> ");
    yyparse();
    return 0;
  }
  
  int yyerror(char *s)
  {
    fprintf(stderr, "error: %s\n", s);
  }
  int yywrap()
  {
  return 1; 
  }

calc.l代码如下:

  %{
  # include "y.tab.h"
  int yylval;
  int lexerror(char *s);
  %}
  
  %%
  "+" { return ADD;}
  "-" { return SUB;}
  "*" { return MUL;}
  "/" { return DIV;}
  "(" { return LP;}
  ")" { return RP;}
  ^[-+][0-9]+ { yylval = atoi(yytext); return NUM;}
  [0-9]+ {yylval = atoi(yytext); return NUM;}
  [\n] { return EOL;}
  [ \t] {/* ignore white space */}
  . { }
  %%
  
  int lexerror(char *s)
  {
      fprintf(stderr, "flex error: %s\n", s);
  }

使用步骤:

  yacc -d calc.y
  lex calc.l
  gcc -o calc y.tab.c
  ./calc

生成lex.yy.c、y.tab.c、y.tab.h文件中相关信息解释

Lex 变量

FILE* 类型。 它指向 lexer 正在解析的当前文件。

FILE* 类型。 它指向记录 lexer 输出的位置。 缺省情况下,yyin 和 yyout 都指向标准输入和输出。

匹配模式的文本存储在这一变量中(char*)。

给出匹配模式的长度。

提供当前的行数信息。 (lexer不一定支持。)

Lex 函数

开始分析函数,由 Lex 自动生成。

在文件(或输入)的末尾调用。 如果函数的返回值是1,就停止解析。 因此它可以用来解析多个文件。 代码可以写在第三段,这就能够解析多个文件。 方法是使用 yyin 文件指针(见上表)指向不同的文件,直到所有的文件都被解析。 最后,yywrap() 可以返回 1 来表示解析的结束。

可以用来送回除了前n个字符外的所有读出标记。

Lexer 将下一个标记附加到当前标记后。

标签:03,return,文件,expr,语法,token,tab,lex,解析
来源: https://blog.csdn.net/weixin_39665703/article/details/121628378