词法与语法分析器介绍

概述

词法和语法可以使用正则表达式和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.mzph.cn/diannao/17381.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

二叉树的实现(递归实现)

前言&#xff1a;本文讲解通过递归的方式实现二叉树的一些基本接口。 目录 通过左右子树的方式实现二叉树&#xff1a; 二叉树的遍历&#xff1a; 求二叉树结点的个数&#xff1a; 二叉树所有节点的个数&#xff1a; 二叉树叶子节点的个数&#xff1a; 求第k层节点的节点…

4,八种GPIO模式

资料来源:【STM32基础学习】八种GPIO模式总结-云社区-华为云 (huaweicloud.com) 【STM32基础学习】八种GPIO模式总结-云社区-华为云 (huaweicloud.com) 【STM32基础学习】八种GPIO模式总结-云社区-华为云 (huaweicloud.com) 仅作个人自学笔记&#xff0c;如有冒犯&#xf…

电子阅览室解决方案

一.方案概述 “电子阅览室”概念一经提出&#xff0c;就得到了广泛的关注&#xff0c;纷纷组织力量进行探讨、研究和开发&#xff0c;进行各种模型的试验。随着数字地球概念、技术、应用领域的发展&#xff0c;电子阅览室已成为数字地球家庭的成员&#xff0c;为信息高速公路提…

深度解读 chatgpt基本原理

ChatGPT&#xff08;Generative Pre-trained Transformer&#xff09;是由OpenAI开发的一种大规模语言模型&#xff0c;基于Transformer架构&#xff0c;采用自监督学习和生成式预训练方法。以下是ChatGPT的基本原理的深度解读&#xff1a; ### 1. Transformer架构 Transforme…

深入理解C++智能指针系列(五)

引言 前面两篇介绍了std::unique_ptr的自定义删除器以及如何优化删除器的使用。本文将介绍std::unique_ptr在使用过程中的一些“奇技淫巧”。 正文 删除器和std::move std::move是将对象的所有权转移给另一个对象&#xff0c;那如果通过std::move来转移带自定义删除器的std::…

uniCloud云存储uni-cdn七牛云扩展存储-开发uniapp项目节约开发成本

为什么要使用uniCloud的扩展存储&#xff0c;那就是省钱&#xff0c;而且DCloud也一直在推uni-cdn&#xff0c;我在项目中也使用七牛云的扩展存储&#xff0c;确实是省钱&#xff0c;如果你的项目使用到大量的图片后者音视频&#xff0c;这些的算计可以帮你省不少钱。下面就通过…

OSPF的数据库表 +LSA类别

<r1>display ospf sdb 查看OSPF数据库目录 LSDB中装载了所有可以学习到的LSA; LSA--链路状态通告 一条拓扑或一条路由条目被称为一条LSA OSPF协议的数据库是本地所有LSA的集合&#xff0c;不同网络环境下将产生不同类别的LSA LSA 在共享时基于 LSU 数据包传递…

【状态机动态规划】3129. 找出所有稳定的二进制数组 I

本文涉及知识点 动态规划汇总 LeetCode 3129. 找出所有稳定的二进制数组 I 给你 3 个正整数 zero &#xff0c;one 和 limit 。 一个 二进制数组 arr 如果满足以下条件&#xff0c;那么我们称它是 稳定的 &#xff1a; 0 在 arr 中出现次数 恰好 为 zero 。 1 在 arr 中出现…

leetCode.83. 删除排序链表中的重复元素

leetCode.83. 删除排序链表中的重复元素 代码 class Solution { public:ListNode* deleteDuplicates(ListNode* head) {auto p head;while(p){auto q p->next;while(q && p->val q->val) q q->next;if(p->next q) p p->next;else p->next …

dp背包问题

英雄联盟游戏中新出n个英雄&#xff0c;用长度为n的教组 costs 表示每个英雄的定价&#xff0c;其中 costs[i]表示第i个英雄的点券价格。假如你一共有coins点券可以用于消费&#xff0c;且想要买尽可能多的英雄并日选择英雄按costs[i]给出顺序获取。给你价格数组 costs 和金币量…

Golang | Leetcode Golang题解之第116题填充每个节点的下一个右侧节点指针

题目&#xff1a; 题解&#xff1a; func connect(root *Node) *Node {if root nil {return root}// 每次循环从该层的最左侧节点开始for leftmost : root; leftmost.Left ! nil; leftmost leftmost.Left {// 通过 Next 遍历这一层节点&#xff0c;为下一层的节点更新 Next …

Java中的线程同步:确保数据一致性和避免竞态条件

在多线程编程中&#xff0c;线程同步是保证数据一致性和防止竞态条件的关键技术。当多个线程尝试同时访问和修改同一数据资源时&#xff0c;如果没有适当的同步机制&#xff0c;程序可能会产生不可预见的结果。Java提供了多种同步工具和技术&#xff0c;以帮助开发者有效管理线…

vue3 uni-app 中小程序实现 底部tabbar 中间凸起部分 或者说自定义底部tabbar [保姆级别教程]

1、先来看一下效果 2、代码实现 我们还是在 pages.json 中正常配置我们底部的tabbar 但是需要 添加一个字段 "custom": true, //开启自定义tabBar 不填每次原来的tabbar在重新加载时都回闪现 3、 在 pages同一级 或者 里面创建一个 子组件 用来放我们的模版 4、 …

MPLS原理与配置

1.MPLS概述 &#xff08;1&#xff09;传统IP路由转发 &#xff08;2&#xff09;MPLS基本概念 ⦁ MPLS起源于IPv4&#xff08;Internet Protocol version 4&#xff09;&#xff0c;其核心技术可扩展到多种网络协议&#xff0c;包括IPv6&#xff08;Internet Protocol ver…

单片机的内存映射和重映射

内存映射 在单片机内&#xff0c;不管是RAM还是ROM还是寄存器&#xff0c;他们都是真实存在的物理存储器&#xff0c;为了方便操作&#xff0c;单片机会给每一个存储单元分配地址&#xff0c;这就叫做内存映射。 单片机的内存映射是指将外部设备或外部存储器映射到单片…

【软件设计师】——5.数据库系统

目录 5.1 基本概念 5.2 三级模式两级映射 5.3 设计过程和数据模型 5.4 关系代数 5.5 完整性约束 5.6 规范化和反规范化 5.7 控制功能 5.8 SQL语言 5.9 数据库安全 5.10 数据备份 5.11 数据库故障与恢复 5.12 数据仓库、数据挖掘和大数据 5.1 基本概念 相关术语 候选…

三分钟“手撕”顺序表与ArrayList

前言&#xff1a; 实现顺序表的代码放开头&#xff0c;供大家更好的查阅&#xff0c;每个方法都有代码的实现。 其次我会讲解Java自带的ArrayList的实例&#xff0c;扩容机制ArrayList使用方法&#xff0c;遍历以及它的优缺点。 目录 一、自己实现的顺序表 二、Java的ArrayLi…

Python | Leetcode Python题解之第115题不同的子序列

题目&#xff1a; 题解&#xff1a; class Solution:def numDistinct(self, s: str, t: str) -> int:m, n len(s), len(t)if m < n:return 0dp [[0] * (n 1) for _ in range(m 1)]for i in range(m 1):dp[i][n] 1for i in range(m - 1, -1, -1):for j in range(n …

USB摄像头使用记录

USB摄像头使用记录 文章目录 USB摄像头使用记录1.概述1.1 v4l2介绍 2.使用2.1linux应用2.2linux驱动 3.调试3.1应用调试3.1.1 获取并打印摄像头参数3.1.2get_data.c 4.平台信息5.参考记录 1.概述 1.1 v4l2介绍 2.使用 2.1linux应用 2.1.1获取并打印摄像头参数 2.2linux驱动 …

【掌握递归:以斐波那契数列为例】

文章目录 前言斐波那契数列简介使用递归计算斐波那契数递归的优点与缺点优化递归算法结论 前言 递归是编程中一种强有力的技术&#xff0c;它允许一个函数调用自身来解决问题。尽管递归在初学者中可能看起来有些难以理解&#xff0c;但通过实际的例子和练习&#xff0c;它可以…