编译原理:词法分析器 Flex工具的使用(简单易懂)

目录

  • 词法分析器 & Flex工具的使用
    • 背景:编译器和解释器
      • 概念
      • 区别
      • 编译器的实现
    • 词法分析器(Lexer)
    • 工具(Flex)
      • 安装Flex
      • 目标程序:verilog代码
      • Flex程序格式
        • Declarations
        • Definitions
        • Rules
        • User subroutines
        • ==如何通过flex读入文件?==
      • 完整程序
      • 编译&运行
    • 进阶:Flex生成Coolc的词法分析器
      • Definitions
      • Nested Comments
      • Inline Comments
      • String
      • Operator
      • Keyword
      • All Others
    • 补充:正则表达式
    • 补充:有限状态机

词法分析器 & Flex工具的使用

背景:编译器和解释器

概念

编译器是一个程序(常见为C/C++程序),它阅读某种语言(源语言)编写的程序,并将其翻译为一种等价的、用另一种语言(目标语言)编写的程序

解释器是常见的另一种语言处理器,但是它并不同编译器一样翻译生成目标程序,而是直接利用用户提供的输入执行源程序指定的操作

例子:C和C++编译产生中间代码,如汇编代码和可重定位文件等,因此是编译型语言;Java和Python不产生中间代码,通过虚拟机直接运行源程序,因此是解释型语言

区别

通常来讲,编译器产生的程序比解释器运行速度快地多;然而,解释器的错误诊断效果通常更好,因为它的编译和执行是同时进行的。

编译器的实现

回顾一下编译器实现的几个阶段:

  1. 词法分析(lex analysis)
  2. 解析(parse)
  3. 语法分析(syntax analysis)
  4. 代码优化(code optimization)
  5. 代码生成(code gen)

我们暂且只关注词法分析的部分,来看看编译器完成的第一个工作是什么

词法分析器(Lexer)

来看一个简单的例子:This is a sentence

这是一串英文文本,人在解读它时,人就类似于编译器,请想一想人解读文本的第一步是什么?

为了解读文本,事实上人们总是需要先识别出单词的划分,尽管这是如此自然以致于你很可能会忽略这一步。然而,对于机器,这可能比你想象中更为复杂

如上的例子会被如此划分:

  1. “This” - 代词(Pronoun)
  2. “is” - 动词(Verb)
  3. “a” - 冠词(Article)
  4. “sentence” - 名词(Noun)

思考:那么对一段程序情况如何呢?

if (i==j)z=0;
else z=1;

我们先直接陈述结论:词法分析器为了后续的解析所作的工作是,从【空格】【换行】【制表】等符号(统称whitespace)的间隔中区分出单词,常见的词性有六种。

  1. whitespace-空白符
  2. identifiers-标识符
  3. keywords-关键字
  4. numbers-数字
  5. string-字符串
  6. operator-操作符/运算符

结果(标注所用首字母与上文对应)

工具(Flex)

Flex是一个用于生成词法分析器(lexer)的工具。

根据所输入的正则表达式,Flex能够直接生成需要的词法分析器的C++代码。

安装Flex

在旧版flex环境下进行实验

sudo apt install flex-old
flex --version
# should print flex 2.5.35(In Unbuntu 22.04LTS)

目标程序:verilog代码

我们考虑一段简单的verilog代码,并通过flex工具自动化生成可用的词法分析器程序

module My_not(A, Y)
input   A;
output  Y;pmos(Y, VCC, A);nmos(Y, GND, A);
endmodule

Flex程序格式

%{
Declarations	//包括头文件、枚举、宏名、函数申明等
%}
Definitions		//定义正则表达式和状态
%%
Rules			//定义匹配动作
%%
User subroutines//用户定义的程序
Declarations

首先,定义程序需要用到的词性如下

enum TokenType {KEYWORD = 1,	//关键字USER_DEF,		//用户定义的标识符SYMBOL,			//符号NUMBER,			//数字NONE			//异常(空)
};

定义我们返回Token需要的函数

Token make_token(const std::string& text, int type) {return std::make_pair(text, type);
}

定义行数:

int curLine
Definitions

因为没有特别复杂的正则表达式,因此在Definitions段我们就不声明特殊的正则表达式了,如果你需要声明,格式如下

KEYWORD 	module|input|output|wire|pmos|nmos|endmodule
USER_DEF	[a-zA-Z_][a-zA-Z0-9_]* //星号(*)表示0个或多个,加号(+)表示1个或多个 
DIGIT		[0-9]+ //数字0到9
SYMPOL	 	[,\.\(\);:\[\]]	//中括号表示从所有字符中匹配其中一个
WHITESPACE	[ \t\n\r\v]	 
Rules

此处定义匹配正则表达式的动作

module|input|output|wire|pmos|nmos|endmodule { current_token = make_token(yytext, KEYWORD); return current_token.second; 
}
[a-zA-Z_][a-zA-Z0-9_]*  { 	current_token = make_token(yytext, USER_DEF);return current_token.second; 
}
[0-9]+ {current_token = make_token(yytext, NUMBER);return current_token.second; 
}
[,\.\(\);:\[\]] {	current_token = make_token(yytext, SYMBOL);return current_token.second; 
}
[ \t\n\r\v] { 	                      if (*yytext == '\n') ++curLine;
}
.  {	//"."表示匹配除了"\n"外的所有字符std::cerr << "Unrecognized token: " << yytext << " at line " << curLine << std::endl; }
User subroutines

这部分可以执行的操作和C语言一模一样,你可以定义函数,变量和main函数

while ((token = yylex())) {// Processing codestd::cout <<"#"<< curLine <<" "<< TokenTypeToString(current_token.second) <<  " "  << current_token.first << std::endl;
}

我们在main函数中通过flex提供的yylex()接口不断获取文件中的下一个token,并打印在标准输出上

如何通过flex读入文件?

flex标准的输入YY_INPUT默认为FILE*形式,无法读入C++的std::iftream,因此我们呢需要在Declarations段声明其使用C++的标准输入,这样我们通过file.open打开的文件就成为了flex的输入

/* Define YY_INPUT to read from the std::ifstream object */
#undef YY_INPUT
#define YY_INPUT(buf, result, max_size) \if (!file.eof() && file.readsome(buf, max_size) > 0) \result = file.gcount(); \else \result = YY_NULL;
//在main函数中
file.open(argv[1]);

完整程序

//lexer.l
%{
#include <iostream>
#include <fstream>
#include <string>
#include <regex>enum TokenType {KEYWORD = 1,USER_DEF,SYMBOL,NUMBER,NONE
};using Token = std::pair<std::string, int>;std::ifstream file;
int curLine = 1;
Token current_token;Token make_token(const std::string& text, int type) {return std::make_pair(text, type);
}#define YY_DECL int yylex()int yylex();
void yyerror(const char* msg);/* Define YY_INPUT to read from the std::ifstream object */
#undef YY_INPUT
#define YY_INPUT(buf, result, max_size) \if (!file.eof() && file.readsome(buf, max_size) > 0) \result = file.gcount(); \else \result = YY_NULL;
%}
%option noyywrap%%module|input|output|wire|pmos|nmos|endmodule     { current_token = make_token(yytext, KEYWORD); return current_token.second; }
[a-zA-Z_][a-zA-Z0-9_]*                         { current_token = make_token(yytext, USER_DEF); return current_token.second; }
[0-9]+                                         { current_token = make_token(yytext, NUMBER); return current_token.second; }
[,\.\(\);:\[\]]                                { current_token = make_token(yytext, SYMBOL); return current_token.second; }
[ \t\n\r\v]                                   {if (*yytext == '\n') ++curLine;}
.                                              { std::cerr << "Unrecognized token: " << yytext << " at line " << curLine << std::endl; }%%
std::string TokenTypeToString(int tt) {switch (tt) {case KEYWORD:return "KEYWORD ";case USER_DEF:return "USER_DEF";case SYMBOL:return "SYMBOL  ";case NUMBER:return "NUMBER  ";case NONE:return "NONE    ";default:return "UNKNOWN ";}
}
int main(int argc, char** argv) {if (argc < 2) {std::cerr << "Usage: " << argv[0] << " <input file>" << std::endl;return 1;}file.open(argv[1]);if (!file.is_open()) {std::cerr << "Failed to open file: " << argv[1] << std::endl;return 1;}int token;while ((token = yylex())) {// Processing codestd::cout <<"#"<< curLine <<" "<< TokenTypeToString(current_token.second) <<  " "  << current_token.first << std::endl;}file.close();return 0;
}void yyerror(const char* msg) {std::cerr << "Error: " << msg << std::endl;
}

编译&运行

编译命令:

# 自动生成cpp文件
flex -o lexer.cpp lexer.l
# 编译cpp成为可执行文件
g++ -std=c++14 -o lexer lexer.cpp -lfl	# 通过-fl链接flex库

运行命令

./lexer not.v

输出格式

进阶:Flex生成Coolc的词法分析器

这是CS143课程的一个课程pj,接下来的实现来自skyzluo大佬,作为深度了解高级语言词法分析器的关键一步

注意:请参考代码框中的注释理解分词逻辑

Definitions

重点:通过%符号定义有限状态机的状态,此处只需要有个印象即可

DARROW		=>
DIGIT		[0-9]
%Start		COMMENTS		//注释
%Start		INLINE_COMMENTS	//单行注释
%Start		STRING			//字符串

Nested Comments

<INITIAL,COMMENTS,INLINE_COMMENTS>"(*" { //遇到(*时增加注释层次,并进入注释状态comment_layer++;BEGIN COMMENTS;
}
<COMMENTS>[^\n(*]*  { } //跳过\n,(,*之外的所有连续字符
<COMMENTS>[()*] { }	//跳过(,*,)的单个字符
<COMMENTS>"*)" {	//匹配到*)时减少注释层次,到0时退出注释状态comment_layer--;if(comment_layer==0){BEGIN 0;}
}
<COMMENTS><<EOF>> {	//注释中遇到EOF进行报错yylval.error_msg = "EOF in comment";BEGIN 0;return ERROR;
}
"*)" {				//未在注释状态遇到*)时报错yylval.error_msg = "Unmatched *)";return ERROR;
}

Inline Comments

<INITIAL>"--" { BEGIN INLINE_COMMENTS; }//遇到特殊字符--开始单行注释
<INLINE_COMMENTS>[^\n]* { } //跳过除\n外的所有字符
<INLINE_COMMENTS>"\n" {	    //遇到换行符\n结束注释。curr_lineno++;BEGIN 0;
}

String

<INITIAL>(\") {BEGIN STRING;yymore();
}  
<STRING>[^\\\"\n]* { yymore(); } //除了反斜杠,影号和换行符
<STRING>\\[^\n] { yymore(); }	//反斜杠转义字符/* 转义的换行符 * char *str = "This is a long string \* that spans multiple lines \* using escaped newline characters.";*/
<STRING>\\\n {curr_lineno++;yymore();}
<STRING><<EOF>> {yylval.error_msg = "EOF in string constant";BEGIN 0;yyrestart(yyin);return ERROR;
}
<STRING>\n {yylval.error_msg = "Unterminated string constant";BEGIN 0;curr_lineno++;return ERROR;
}
<STRING>\" {std::string input(yytext,yyleng);input = input.substr(1, input.length() - 2);std::string output = "";std::string::size_type pos;//检查是否包含空字符if (input.find_first_of('\0') != std::string::npos) {yylval.error_msg = "String contains null character";BEGIN 0;return ERROR;    }//添加转义字符while ((pos = input.find_first_of("\\")) != std::string::npos) {output += input.substr(0, pos);switch (input[pos + 1]) {case 'b':output += "\b";break;case 't':output += "\t";break;case 'n':output += "\n";break;case 'f':output += "\f";break;default:output += input[pos + 1];break;}input = input.substr(pos + 2, input.length() - 2);}output += input;//检查字符串是否过长if (output.length() > 1024) {yylval.error_msg = "String constant too long";BEGIN 0;return ERROR;    }//添加到字符表cool_yylval.symbol = stringtable.add_string((char*)output.c_str());BEGIN 0;return STR_CONST;
}	

Operator

"<-" { return ASSIGN; }
"<=" { return LE; }
"=>" { return DARROW; }
"+" { return int('+'); }
"-" { return int('-'); }
"*" { return int('*'); }
"/" { return int('/'); }
"<" { return int('<'); }
"=" { return int('='); }
"." { return int('.'); }
";" { return int(';'); }
"~" { return int('~'); }
"{" { return int('{'); }
"}" { return int('}'); }
"(" { return int('('); }
")" { return int(')'); }
":" { return int(':'); }
"@" { return int('@'); }
"," { return int(','); }

Keyword

(?i:class) { return CLASS; }
(?i:else) { return ELSE; }
(?i:fi) { return FI; }
(?i:if) { return IF; }
(?i:in) { return IN; }
(?i:inherits) { return INHERITS; }
(?i:let) { return LET; }
(?i:loop) { return LOOP; }
(?i:pool) { return POOL; }
(?i:then) { return THEN; }
(?i:while) { return WHILE; }
(?i:case) { return CASE; }
(?i:esac) { return ESAC; }
(?i:of) { return OF; }
(?i:new) { return NEW; }
(?i:isvoid) { return ISVOID; }
(?i:not) { return NOT; }

All Others

/* INT_CONST */{DIGIT}+ {cool_yylval.symbol = inttable.add_string(yytext);return INT_CONST;
}/* BOOL_CONST */
t(?i:rue) {cool_yylval.boolean = 1;return BOOL_CONST;
}f(?i:alse) {cool_yylval.boolean = 0;return BOOL_CONST;
}/* White Space */
[ \f\r\t\v]+ { }/* TYPEID */
[A-Z][A-Za-z0-9_]* {cool_yylval.symbol = idtable.add_string(yytext);return TYPEID;
}/* OBJECTID */
[a-z][A-Za-z0-9_]* {cool_yylval.symbol = idtable.add_string(yytext);return OBJECTID;
}
"\n" {curr_lineno++;
} /* error */
[^\n] {yylval.error_msg = yytext;return ERROR;
}

补充:正则表达式

基础:

  1. 空字符串 ϵ \epsilon ϵ

  2. 单字符集R

  3. 并连R+R

  4. 串连RR

  5. 自连R*

特殊:

  1. A+ == AA*(A至少有一个)
  2. A? == A + ϵ \epsilon ϵ​(A是可选的)
  3. [a-zA-Z] == ‘a’+‘b’+…
  4. A|B == A+B
  5. [a-z]的补 == [^a-z]

补充:有限状态机

有兴趣可了解确定性自动机(DFA) & 不确定性自动机(NFA)。

  1. 正则表达式如何通过有限状态机(FSM)表达?
  2. NFA如何转换到DFA?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/44289.shtml

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

相关文章

Unity入门之重要组件和API(3) : Transform

前言 Transform类主要处理游戏对象(GameObject)的位移、旋转、缩放、父子关系和坐标转换。 1.位置和位移 1.1必备知识点&#xff1a;Vector3 Vector3 主要用来表示三维坐标系中的一个点或者一个向量。 【声明】 Vector3 v1 new Vector3(); Vector3 v2 new Vector3(10, 10…

ScrapySharp框架:小红书视频数据采集的API集成与应用

引言 随着大数据时代的到来&#xff0c;数据采集成为了互联网企业获取信息的重要手段。小红书作为一个集社交和电商于一体的平台&#xff0c;其丰富的用户生成内容&#xff08;UGC&#xff09;为数据采集提供了丰富的资源。本文将介绍如何使用ScrapySharp框架进行小红书视频数…

便携式气象站的应用领域

在气象观测的广阔天地中&#xff0c;便携式气象站不仅集便携性、多功能性和高精度于一身&#xff0c;还以其快速部署、实时监测和数据传输等特点&#xff0c;在科研教学、环境监测、农业生产和灾害预警等多个领域发挥着重要作用。 便携式气象站的基本概念 便携式气象站&#…

高智能土壤养分检测仪:农业生产的科技新助力

在科技日新月异的今天&#xff0c;农业领域也迎来了革命性的变革。其中&#xff0c;高智能土壤养分检测仪作为现代农业的科技新助力&#xff0c;正逐渐改变着传统的农业生产方式&#xff0c;为农民带来了前所未有的便利与效益。 高智能土壤养分检测仪&#xff0c;是一款集高科技…

职场必备神器!图片提取文字!OCR文字识别助手:让灵感自由流动!

Hey&#xff0c;创意达人们&#xff01;是否曾在网页上看到令人心动的设计灵感&#xff0c;却因无法复制粘贴而苦恼&#xff1f;别担心&#xff0c;今天我要给你们安利一个办公小秘密——OCR文字识别助手&#xff0c;让你的灵感自由流动&#xff0c;创意无限&#xff01; 功能…

深入了解代理IP常见协议:区别与选择

代理服务器在网络使用中扮演着重要的角色&#xff0c;是您设备和互联网之间的中间层。它不仅可以增强网络访问的安全性和隐私保护&#xff0c;还可以提供许多灵活的应用。使用代理时&#xff0c;不同的协议类型对数据交换具有不同的规则和特征。常见的代理协议包括HTTP代理、HT…

轻松掌握图片压缩技巧,释放存储空间!

前言 在这个充满视觉冲击的时代&#xff0c;我们每天都在创造和分享图片。但你是否发现&#xff0c;手机和电脑的存储空间越来越不够用了&#xff1f;图片文件过大&#xff0c;不仅占用空间&#xff0c;还影响传输速度和网页加载。今天&#xff0c;就让我来教你几招&#xff0…

政安晨【零基础玩转各类开源AI项目】基于Ubuntu系统部署MuseV (踩完了所有的坑):基于视觉条件并行去噪的无限长度和高保真虚拟人视频生成

目录 下载项目 创建虚拟环境 启动虚拟环境&执行项目依赖 基于DOCKER的尝试 A. 安装引擎 B. 下载桌面安装包 C. 安装桌面包 用Docker运行MuseV 1. 拉取镜像 ​编辑 2. 运行Docker镜像 政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 收…

Android APT实战

Android开发中,注解平时我们用的比较多,也许我们会比较好奇,注解的背后是如何工作的,这篇文章帮大家一步步创建一个简单的注解处理器。 简介 APT(Annotation Processing Tool)即注解处理器,在编译的时候可以处理注解然后搞一些事情,也可以在编译时生成一些文件之类的。…

Android Studio音频视频播放器课程设计

这个项目适合刚刚学习Android studio的初学者&#xff0c;实现音视频的基本播放功能&#xff0c;各项功能的页面都做的比较简单&#xff0c;特别适用于初学者&#xff0c;其特点在于本项目抛开了各种花里胡哨的制作&#xff0c;以最接近初学者的样式画面呈现&#xff0c;完全不…

20240711 每日AI必读资讯

&#x1f3a8;Runway Gen-3 Alpha 详细使用教程以及提示词指南大全 - 7月9日&#xff0c;著名生成式AI平台Runway在官网公布了&#xff0c;最新发布的文生视频模型Gen-3 Alpha的文本提示教程。 - 从技术层面来说&#xff0c;输入的文本提示会被转换成“向量”&#xff0c;这些…

深入理解Python密码学:使用PyCrypto库进行加密和解密

深入理解Python密码学&#xff1a;使用PyCrypto库进行加密和解密 引言 在现代计算领域&#xff0c;信息安全逐渐成为焦点话题。密码学&#xff0c;作为信息保护的关键技术之一&#xff0c;允许我们加密&#xff08;保密&#xff09;和解密&#xff08;解密&#xff09;数据。P…

通过Umijs从0到1搭建一个React项目

有一阵时间没写react了&#xff0c;今天通过umi搭建一个demo项目复习一下react&#xff1b;umi是一个可扩展的企业级前端应用框架&#xff0c;在react市场中还是比较火的一个框架。 Umi官方文档&#xff1a;Umi 介绍 (umijs.org) 一、构建项目。 1、安装包管理工具。 官方推…

力扣题解(设计跳表)

1206.设计跳表 已解答 不使用任何库函数&#xff0c;设计一个 跳表 。 跳表 是在 O(log(n)) 时间内完成增加、删除、搜索操作的数据结构。跳表相比于树堆与红黑树&#xff0c;其功能与性能相当&#xff0c;并且跳表的代码长度相较下更短&#xff0c;其设计思想与链表相似。 …

【观成科技】Websocket协议代理隧道加密流量分析与检测

Websocket协议代理隧道加密流量简介 攻防场景下&#xff0c;Websocket协议常被用于代理隧道的搭建&#xff0c;攻击者企图通过Websocket协议来绕过网络限制&#xff0c;搭建一个低延迟、双向实时数据传输的隧道。当前&#xff0c;主流的支持Websocket通信代理的工具有&#xf…

构建高精度室内定位导航系统,从3DGIS到AI路径规划的全面解析

室内定位导航系统是一种利用多种技术实现室内精准定位和导航的智能系统&#xff0c;即便没有卫星信号&#xff0c;也能实现精准导航。维小帮室内定位导航系统是基于自研的地图引擎与先进定位技术&#xff0c;结合智能路径规划算法&#xff0c;解决了人们在大型复杂室内场所最后…

【深度学习】图形模型基础(6):模型优化理论

1.引言 在之前的讨论中&#xff0c;我们构建了一个理论模型来表达最优决策规则&#xff0c;这是建立在我们对数据的概率模型有充分理解的基础上的。相对地&#xff0c;经验风险最小化&#xff08;Empirical Risk Minimization, ERM&#xff09;策略则在缺乏精确概率模型的情况…

Java语言程序设计——篇三(1)

选择结构 概述选择单分支if语句例题讲解 双分支if-else语句例题讲解 条件运算符多分支的if-else语句例题讲解 嵌套的if语句例题讲解 switch语句结构例题讲解代码演示运行结果 概述 Java中的控制结构&#xff0c;包括&#xff1a; 1、选择结构( if、if-else、switch ) 2、循环结…

仕考网:非应届生可以参加公务员考试吗?

往届生有资格参加国家公务员考试。根据《公务员录用规定》&#xff0c;只要满足一系列资格条件&#xff0c;就符合报考资格。 1、年龄在18到35岁之间。 2、具备良好的品德&#xff0c;身体健康且心理素质稳定&#xff0c;拥有拟任职位所需的工作能力。 3、至少为大学专科以上…

【排序 - 归并排序】

归并排序&#xff08;Merge Sort&#xff09;是一种高效的排序算法&#xff0c;基于分治&#xff08;Divide and Conquer&#xff09;策略。它将待排序数组分成两个较小的子数组&#xff0c;分别对它们进行排序&#xff0c;然后将排好序的子数组合并成一个整体有序的数组。归并…