什么是lexical analysis(Lex)?
属于前段编译器,主要用于在前段编译器处理 词法分析
前端编译器:
词法分析(lexical analysis)是计算机科学中将字符序列转换为单词(Token)序列的过程。进行词法分析的程序或者函数叫作词法分析器(Lexical analyzer,简称Lexer),也叫扫描器(Scanner)。词法分析器一般以函数的形式存在,供语法分析器调用。------《来自百度知道》
什么是词法分析?
简单一句话来说就是:
词法分析的目标是识别输入字符流中的特定单词,把输入的字符串翻译成编译器能够明白的语言。
词法分析(Lexical Analysis),它将识别字符串中的单词,单词被称作Token,也叫标记。什么是Token呢?以编程语言为例,主要分为以下几点等...
(注:token是下面几点的统称,token可以被称为Token Class 或者 Class)
关键字(keyword)(比如if、for、int、long等)
标识符(Identifier)(变量名、函数名等)
运算符 ( operational)(+、-、*、/等)
常量(字符串常量、数值常量(Intger)等)
界定符(Demiliter,如空格(whitespace)、分号、括号等有特殊含义的符号)
Token的定义和具体编程语言密切相关。
关键字介绍:
断词:
a = b + 2;
代码编写的很简单的一句话
我们可以拆分成一下,也就是断词
- a
- =
- b
- 2
- ;
在解析时候不只有空白字符,还有换行符,注解等等
可以根据换行符得知行号等信息。在解析时候会对各种变量进行解析,进行不同类的分类
a : identifier
= : assign
b : identifier
+ : plus
2 : number
; : semicolon
当我们遇到identifier时候就会将相关的信息插入到 symbol table表中
现在我们得到了a,b的信息,后续会将symbol table 提供给下一个syntax analyzer
词法分析例子:
if(i==j)
z=0;
else
z=1;
一段很简单的代码 ,我们需要将它进行解析,将其翻译成我们对应的语言的词法
- 先获取对应的我们需要的单位token(比如关键字信息)
比如读取完毕以后得到的内容就是如下(为了方便分析,以去掉多余空格,正常代码是需要标识空格的):
if(i==j)\n\z=0;\n\else\n\z=1;\n
Lex会去解析哪些是关键字,哪些是标识符,比如当我们解析到一个else字段的时候,需要判断到底是一个关键字还是一个keyword(字段名字)
经过词法分析的分析以后(包含着各种token),不同的语言可能会有不同的分析规则,会传输到解析器。
我们先拿个更简单的demo:
a=1
所生成的每个token为 <Class,string> 模板
比如a=1 这句会生成三个token
分析器传入到解析器内部大概的样子是
<标识符(Identifier),"a">
<运算符(operational),“=”>
<数值常量(Intger),"1">
总结如下主要分为两项:
1,识别子串
2,找出Token
在解析C++语法时候有两个常见语法
比如:
Foo<ARE>
cin>>var
如果我想写
Foo<ARE<ARE>>
这么写完全没什么问题
但是前段编译器可能会认为 上面代码后面的 >>
是一个输入流
不过还好,编译器已经修复了这个Bug
在识别的过程中可能会有很多的算法,包括各种正则表达式,比如BNF,和一些特殊符号
介绍一下比较常用的
BNF
- BNF的基本语法: <符号> ::= <使用符号的表达式>
- 双引号(" ")中的字符串(“word”)代表这些字符本身,而double_quote代表双引号。
- 双引号外的字符串(有可能带下划线)代表语法部分。
- 尖括号(< >)中的内容为必选项。
- 方括号([ ])中的内容为可选项。
- 大括号({ })中的内容为可重复0至无限次的项。
- 竖线(|)表示其左右两侧任选一项,相当于 OR 的意思
例:Java中for语句的BNF:
FOR_STATEMENT ::=
"for" "(" ( variable_declaration |
( expression ";" ) | ";" )
[ expression ] ";"
[ expression ] ";"
")" statement
正则表达式
下面是Lex 的正则表达式符号定义,更多语言可参考:http://www.greenend.org.uk/rjk/tech/regexp.html
表达式 | 匹配 | 例子 |
---|---|---|
r1r2 | 连接 | |
r1|r2 | 或 | |
® | 不改变r表示,主要是用于确定运算优先关系 | |
r* | 零个或多个实例 | |
r+ | 一个或多个实例 | |
r? | 零个或多个实例 | |
[a-c] | 等价于a|b|c,或 [abc] | |
. | 除了换行符以外的任意字符 | |
^ | 一行的开始 | |
$ | 行的结尾 | |
[^abc] | 除了abc以外的任何字符 | |
r{m,n} | 重复出现次数m-n | |
r1/r2 | 后面有r2时的r1 | abc/123 |
\c | 运算类字符的字面值 | *,$,| |
Lex使用流程:
- Lex的输入:词法规则(.l文件,也可以用. flx 或者. flex 作为文件的后缀名。)
- Lex的输出:词法分析器(.yy.c文件)
语法规则写法如下:
先看一张使用示意图
开发者首先要根据lex规则文件的格式编写lex规则文件,然后使用lex工具生成对应的C源码文件(也可以生成C++源文件)。默认生成的源码文件名为lex.yy.c(也可以指定其他文件名)。
接着使用编译工具(gcc、g++、clang等)编译lex.yy.c。其间将用到libl.a(lex提供的静态库)和其他一些依赖的源文件和依赖库等。编译链接后将得到一个可执行程序。该可执行程序就能够进行词法分析了。当然,词法分析所依赖的规则由lex规则文件所指定。
通过图6-5所示的lex规则文件可知,lex规则文件包含三个部分。
定义段(definition section)
定义段可以为复杂的正则表达式定义一个别名(类似C/C++中的宏定义)。
定义段之后的规则段中可以使用这些别名来简化复杂正则表达式的书写。另外,定义段还可以包含一些需要原封不动的输出到源码文件中的内容(这些内容必须包含在%{和%}中)。
注意,lex规则文件中可以没有定义段。
规则段(rules section):
定义段和规则段之间通过%%符号隔开。不管有没有定义段,%%符号必须存在。规则段是lex规则文件的核心,它描述的是输入字符串匹配某个正则表达式时什么样的action将被执行。action可以是一段C/C++代码,也可以是lex定义的某个动作(其实就是lex定义的几个宏)。
用户子程序段(user subroutines section):
lex在生成源码时,会原封不动地将该段中的内容拷贝到输出的源码文件中。此段内容一般是C代码。和定义段类似,该段也不是必须存在的。如果没有这部分内容,最后一个%%符号也不需要。
参考:
《深入理解Android:Java虚拟机Art》
https://blog.csdn.net/wangjron/article/details/48794545
https://www.bilibili.com/video/av40008274/ 斯坦福大学《编译原理》