词法与语法分析器介绍

devtools/2024/9/22 18:29:26/

概述

词法和语法可以使用正则表达式和BNF范式表达,而最终描述文法含义的事状态转换图

Lex与YACC

词法分析器Lex

词法分析词Lex,是一种生成词法分析的工具,描述器是识别文本中词汇模式的程序,这些词汇模式是在特殊的句子结构中定义的

Lex 接收到文件或文本形式的输入时,会将文本与常规表达式进行匹配:一次读入一个输入字符,直到找到一个匹配的模式

如果能够找到一个匹配的模式,Lex就执行相关的动作(比如返回一个标记Token)。另外,如果没有可以匹配的常规表达式,将会停止停止进一步的处理,Lex将显示一个错误信息

Lex 和 C语言是强耦合的,一个.lex文件通过Lex解析并生成C的输出文件,这些文件被编译为词法分析器的可执行版本

lex 或 .l 文件在格式上分为以下3段

    1. 全局变量声明部分
    1. 词法规则部分
    1. 函数定义部分

Lex 变量表

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

Lex 函数表

函数名详细解释
yylex()这一函数开始分析。它由Lex自动生成
yywrap()这一函数在文件(或输入)的末尾调用。如果函数的返回值是1,就停止解析。它可以用来解析多个文件
yyless(int)这一函数可以用来输出除来前n个字符外的所有读出标记
yymore()这一函数告诉Lexer将下一个标记附加到当前标记后

代码

文件 a.l

%{
#include <stdio.h>
extern char *yytext;
extern FILE *yyin;
int count = 0;
%}%%// 两个百分号标记指出了 Lex 程序中这一段的结束和第二段的开始
\$[a-zA-Z][a-zA-Z0-9]*  {count++; printf("  变量%s", yytext);}
[0-9\/.-]+  printf("数字%s", yytext);
=           printf("被赋值为");
\n          printf("\n");
[ \t]+      /* 忽略空格 */;
%%// 函数定义部分
int main(int avgs, char *avgr[]) {yyin = fopen(avgr[1], "r");if (!yyin) {return 0;}yylex();printf("变量总数为:%d\n", count);fclose(yyin);return 1;
}

对于以上代码,解释如下

  • 全局变量声明部分: 声明了一个int型全局变量count,用来记录变量的个数
  • 规则部分: 第1个规则是找 符号开头、第 2 个符号为字母且后面为字符或数字的变量,类似于 符号开头、第2个符号为字母且后面为字符或数字的变量,类似于 符号开头、第2个符号为字母且后面为字符或数字的变量,类似于a,并计数加1. 同时,将满足条件的yytext输出;第2个规则是找数字;第3个规则是找"=" 号;第4个规则是输出"\n"; 第5个规则是忽略空格
  • 函数定义部分: 打开一个文件,然后调用yylex 函数进行词法解析,输出变量的技术,最后调用fclose关闭文件

lex 代码编译

lex a.l
gcc lex.yy.c -o test -ll

测试文件 file

$a = 1
$b = 2

执行如下命令

./test file
变量$a被赋值为数字1
变量$b被赋值为数字2
变量总数为:2

语法分析词YACC

YACC(Yet Another Compiler-Compiler) 是 UNIX/Linux 上一个用来生成编译器的编译器(编译器代码生成器). YACC使用BNF范式定义语法,能处理上下文无关文法

YACC 语法规则

YACC 语法包括3部分,即定义段、规则段和用户代码段

… 定义段 …
%%
… 规则段 …
%%
… 用户代码段 …

代码

词法分析文件: cal.l

%{
#include "y.tab.h"
#include <math.h>
%}%%
([0-9]+|([0-9]*\.[0-9]+)([eE][-+]?[0-9]+)?) {yylval.dval = atof(yytext);return NUMBER;
}
[ \t]   ;
\n |
. return yytext[0];
%%

语法分析文件: calc.y

%{
#include <stdio.h>
#include <string.h>
#include <math.h>
int yylex(void);
void yyerror(char *);
%}%union {double dval;
}
%token <dval> NUMBER
%left '-' '+'
%left '*' '/'
%nonassoc UMINUS
%type <dval> expression
%%
statement_list: statement '\n'| statement_list statement '\n';statement: expression { printf("= %g\n", $1); };expression: expression '+' expression {$$ = $1 + $3;}|   expression '-' expression { $$ = $1 - $3; }|   expression '*' expression { $$ = $1 * $3; }|   expression '/' expression{if ($3 == 0.0)yyerror("divide by zero");else$$ = $1 / $3;}|   '-' expression %prec UMINUS { $$ = -$2; }|   '(' expression ')' { $$ = $2; }|   NUMBER { $$ = $1; }
%%void yyerror(char *str) {fprintf(stderr, "error:%s\n", str);
}int yywrap() { return 1;
}int main() {yyparse();
}

从代码中可以看出,规则部分使用BNF范式

  • expression 最终是NUMBER,以及使用+、-、* 、/ 和 ()的组合,对加、减、乘、除、括号、负号进行表达
  • statement 是由expression 组合而成的,可以输出计算结果
  • statement 是由expression 组合而成的,可以输出计算结果
  • statement_list 是statement的组合

lex 编译

lex cal.l
  • 通过这个命令会生成lex.yy.c,里面维护了NUMBER这个Token的有穷自动机

使用 YACC对 calc.y

yacc -d calc.y
  • 会生成y.tab.c、y.tab.h

最终执行结果

gcc -o calc y.tab.c lex.yy.c
./calc1+2= 33+6= 9

Re2C 与 Bison

词法分析器 Re2c

Re2c 是一个词法编译器,可以将符合Re2c规范的生成高效的C/C++代码,Re2c会将正则表达式生成对应的有穷状态机

代码

num.l

#include <stdio.h>
enum num_t {ERR, DEC};static num_t lex(const char *YYCURSOR)
{const char *YYMARKER;/*!re2cre2c:define:YYCTYPE = char;re2c:yyfill:enable = 0;end = "\x00";dec = [1-9][0-9]*;*   {return ERR;}dec end {return DEC;}*/
}int main(int argc, char **argv)
{for (int i = 1; i < argc; ++i) {switch (lex(argv[i])) {case ERR: printf("error\n"); break;case DEC: printf("十进制表示\n"); break;}}return 0;
}

执行以下命令,转换成c代码

re2c num.l -o num.c

num.c

/* Generated by re2c 3.0 on Sun May 26 21:58:19 2024 */
#line 1 "num.l"
#include <stdio.h>
enum num_t {ERR, DEC};static num_t lex(const char *YYCURSOR)
{const char *YYMARKER;#line 11 "num.c"
{char yych;yych = *YYCURSOR;switch (yych) {case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8':case '9': goto yy3;default: goto yy1;}
yy1:++YYCURSOR;
yy2:
#line 14 "num.l"{return ERR;}
#line 32 "num.c"
yy3:yych = *(YYMARKER = ++YYCURSOR);switch (yych) {case 0x00: goto yy4;case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8':case '9': goto yy5;default: goto yy2;}
yy4:++YYCURSOR;
#line 15 "num.l"{return DEC;}
#line 53 "num.c"
yy5:yych = *++YYCURSOR;switch (yych) {case 0x00: goto yy4;case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8':case '9': goto yy5;default: goto yy6;}
yy6:YYCURSOR = YYMARKER;goto yy2;
}
#line 16 "num.l"}int main(int argc, char **argv)
{for (int i = 1; i < argc; ++i) {switch (lex(argv[i])) {case ERR: printf("error\n"); break;case DEC: printf("十进制表示\n"); break;}}return 0;
}

从上面代码中可以看出,这个状态机一共有8种状态,分别是开始状态、yy3至yy9状态,其中yy3状态是错误输出,返回ERR

yy5 状态是对应的正则匹配状态,返回DEC

在这里插入图片描述

YYCURSOR 是指向输入的指针,根据状态的流转,指针加1

语法编译器Bison

对于一条BNF文法规则,其左边是一个非终结符(symbol 或者 non-terminal),其右边则定义该非终结符是如何构成的,也称为产生式(production)

产生式可能包含非终结符,也可能包含终结符(terminal),还可能二者都有

利用BNF文来分析目标文本,比较流行的算法有LL分析(自顶向下的分析,top-down parsing),LR分析(自底向上的分析,bottom-up parsing;或者叫移进-归约分析, shift-down parsing)

其中LR算法有很多不同的变种,按照复杂度和能力递增的顺序依次是LR(0)、SLR、SLR、LALR和LR(1)

Bison是基于LALR分析法实现的,适合上下文无关文法

当Bison读入一个终结符TOKEN时,会将该终结符及其语意值一起压榨,其中这个栈叫做分析器栈(parse stack)

把一个TOKEN压入栈叫作移进。举个例子,对于计算1+2 * 3, 假设现已经读入来1 + 2 * ,那么下一个准备读入的是3,这个栈当前就有4个元素,即1、+、2和*

当已经移进的后n个终结符和组(grouping)与一个文法规则相匹配时,它们会根据该规则结合起来,这叫归约(reduction)

栈中哪些终结符和组会被单个的组(grouping)替换。同样,以1 + 2 * 3;为例,最后一个输入的字符为分号,表示结束,那么按照下面的规则进行归约:

expression '*' expression { $$ = $1 * $3 }

http://www.ppmy.cn/devtools/43285.html

相关文章

【UE5.1 多线程 异步】“Async Blueprints Extension”插件使用记录

目录 一、异步生成Actor示例 二、异步计算示例 参考视频 首先需要在商城中下载“Async Blueprints Extension”插件 一、异步生成Actor示例 2. 创建一个线程类&#xff0c;这里要指定父类为“LongAsyncTask”、“InfiniteAsyncTask”、“ShortAsyncTask”中的一个 在线程类…

Neo4j安装部署及python连接neo4j操作

Neo4j安装部署及python连接neo4j操作 Neo4j安装和环境配置 安装依赖库&#xff1a; sudo apt-get install wget curl nano software-properties-common dirmngr apt-transport-https gnupg gnupg2 ca-certificates lsb-release ubuntu-keyring unzip -y 增加Neo4 GPG key&…

C语言之指针详解(4)

文章目录 一、回调函数二、qsort使用举例2.1使用qsort函数排序整型数据2.2使用qsort函数排序结构体数据 三、qsort函数的模拟实现 一、回调函数 首先我们先来了解一下什么是回调函数 回调函数通俗来讲就是一个通过函数指针调用的函数。 如果你把函数的指针&#xff08;地址&am…

容器(Container)的详细介绍

容器&#xff0c;作为现代软件开发和部署的核心技术之一&#xff0c;已经成为云计算、微服务架构等领域的基石。容器技术通过提供轻量级的虚拟化环境&#xff0c;实现了应用程序的快速部署、迁移和扩展&#xff0c;极大地提高了软件开发的效率和灵活性。本文将详细介绍容器的概…

el-table-column两种方法处理特殊字段,插槽和函数

问题&#xff1a;后端返回的字段为数字 解决办法&#xff1a; {{ row[item.prop] 1 ? "启用" : "禁用" }} {{ row[item.prop] }} 最终果&#xff1a; 另外&#xff1a;如果多种状态时可用函数 {{ getStatus(row[item.prop]) }} {{ row[item.prop…

python数据分析——apply 2

参考资料&#xff1a;活用pandas库 1、向量化函数 使用apply时&#xff0c;可以按行或按列应用函数。如果想应用自定义的函数&#xff0c;必须重写它&#xff0c;因为整列或整行传递到了函数的第一个参数中。可以利用向量化函数和装饰器对所有函数进行向量化。对代码进行向量化…

2023年广东省程序设计大赛

C 双指针&#xff0c;排序 买便宜的&#xff0c;用最贵的卖出 #include<bits/stdc.h>using namespace std; #define int long long const int N 2e5 10; int n,m; int re[2]{1,4}; int bl[4]{2,3,5,6}; int f; struct node {int x,y; }a[N]; bool cmp(node W,node Q…

TCP 连接排故:使用 BPF BCC工具包进行网络跟踪

写在前面 博文内容为 BCC 进行网络跟踪常见工具介绍tcpconnect:主动的 TCP 连接跟踪tcpaccept:被动的 TCP 连接跟踪tcpretrans:重传的 TCP 连接跟踪tcptracer:已建立的 TCP 连接跟踪tcpconnlat:测量出站 TCP 连接的延迟tcpdrop:被内核丢弃的 TCP 数据包跟踪tcplife: TCP 会话追…