Yacc 与 Lex 快速入门
Lex和Yacc从入门到精通(PDF)
Lex和Yacc从入门到精通熊春雷Abstract在开发程序的过程中经常会遇到文本解析的问题,例如:解析C语言源程序,编写 脚本引擎等等,解决这种文本解析的方法有很多,一种方法就是自己手动用C或者 C++直接编写解析程序,这对于简单格式的文本信息来说,不会是什么问题,但是 对于稍微复杂一点的文本信息的解析来说,手工编写解析器将会是一件漫长痛苦 而容易出错的事情。
本系列文档就是专门用来由浅入深的介绍两个有名的Unix工 具Lex和Yacc,并会一步一步的详细解释如何用这两个工具来实现我们想要的任何 功能的解析程序,为了方便理解和应用,我会在该系列的文章中尽可能的采用具 体可行的实例来加以阐释,而且这种实例都是尽可能的和具体的系统平台无关的 ,因此我采用命令行程序作为我们的解析程序的最终结果。
1、环境配置篇开发Lex和Yacc程序最需要的程序就是lex和yacc了,如果你是Unix或者Linux系统,则 系统自带了这两个工具,无需安装,不过值得说明的是GNU/Linux下面的Lex是flex, 而Yacc则是bison。
另外需要的就是一个C/C++语言编译器,由于我们采用的是GNU的 lex和yacc,所以,理所当然的我们就使用GNU的编译器了,如果是Unix或者Linux系统 ,那么编译器应该已经安装了。
在这里我重点讨论的是Windows系统环境下的Lex和 Yacc程序的开发,至于为什么选择Windows系统作为开发平台,则是为了尽可能的让初 学者容易入门。
1.1.必备工具言归正传,首先列举Windows平台下面Lex和Yacc开发环境所需要安装的程序:1.Lex(flex.exe)和Yacc(bison.exe)环境2.C/C++编译器1.2.flex和bison值得说明的是,flex.exe和bison.exe是UnxUtils包中的文件,已经将许多 Unix/Linux平台的程序都移植到了Windows平台,可以直接到UnxUtils网站下载,下载解压缩之后在系统的PATH环境变量中增加UnxUtils所有的exe文件所在的目录,使 得DOS命令行可以直接搜索到flex.exe和bison.exe,除此之外还需要从网络上下载 bison需要的bison.simple和bison.hairy两个文件,并且还要分别设置环境变量 BISON_HAIRY指向bison.hairy,BISON_SIMPLE指向bison.simple。
lex和Yacc详解
Lex和Y acc工具介绍――编译原理的实用工具1.词法分词器生成工具lexLex的主要功能是生成一个词法分析器(scanner)的C源码。
描述词法分析器的文件,经过lex编译后,生成一个lex.yy.c的文件,然后由C编译器编译生成一个词法分析器。
词法分析器,简单来说,其任务就是将输入的各种符号,转化成相应的标识符(token),转化后的标识符很容被后续阶段处理。
过程如错误!未找到引用源。
图1现在这个lex 文件可以用来生成一个统计行数、字符个数和单词个数的工具。
规则段是由正则表达式和相应的动作组成的。
p 1 {action 1}p 2 {action 2}……p n {action n }值得注意的是,lex 依次尝试每一个规则,尽可能地匹配最长的输入流。
如果有一些内容根可以看出lex的确按照最长的规则匹配。
程序段部分放一些扫描器的其它模块,比如一些动作执行时需要的模块。
也可以在另一个程序文件中编写,最后再链接到一起。
生成C代码后,需用C的编译器编译。
连接时需要指定链接库。
gcc的连接参数为 -ll。
2.正则表达式正则表达式可以描述有穷状态自动机(Finite Automata)接受的语言,也就是定义一个可以接受的串的集合。
转义字符(也称操作符):" \ [ ] ^ - ? . * + | ( ) $ / { } % < >这些符号有特殊含义,不能用来匹配自身。
如果需要匹配的话,可以通过引号(’’)或者转义符号(\)来指示。
比如C”++”C\+\+都可以匹配C++。
非转义字符:所有除了转义字符之外的字符都是非转义字符。
一个非转义字符可以匹配自身。
比如integer匹配文本中出现的integer。
通配符:通配符就是”.”(dot),可以匹配任何一个字符。
字符集:用一对[]指定的字符构成一个字符集。
比如[abc]表示一个字符集,可以匹配a、b、c中的任意一个字符。
使用–可以指定范围。
Lex和Yacc从入门到精通(3)
Lex和Yacc从入门到精通(3)一个极其简单的lex和yacc程序本文版权归熊春雷所有,我的邮箱:<****************>,欢迎大家和我讨论计算机方面的问题,在我的博客上面还写了很多其他的文档,有空来看看哦。
如果转载,请保留此版权信息,并注明出处。
谢谢:)摘要在本章中,将会首先给出一个最基本的lex和yacc联合使用的框架,这个基本框架最主要的特点就是能够正确的被编译。
在我学习lex 和yacc的过程中经历了无数次的痛苦折磨,我发现一个一开始足够简单而且能够被正确编译的例子往往能够使学习者增加学习的兴趣和信心。
因此我的所有的文章都尽可能的采用这种方式进行描述。
我写这些文档的最大的愿望就是希望能够减少新手学习的痛苦。
希望自己能够做到这一点!目录1. 基本的lex文件2. 基本的yacc文件3. 用C语言编译器编译参考资料1. 基本的lex文件例 3.1. frame.l%{int yywrap(void);%}%%%%int yywrap(void){return1;}lex文件和yacc文件都是被%%分成了上中下三个部分,在这个程序中的yywrap函数需要说明一下:yywraplex源文件中的yywrap函数是必须的!具体的原因就是因为给了这个函数实现之后就可以不需要依赖flex库了。
具体yywrap的作用会在后面的章节应用的时候进行解释。
通常的做法就是直接返回1,表示输入已经结束了。
2. 基本的yacc文件例 3.2. frame.y%{void yyerror(const char *s);%}%%program:;%%void yyerror(const char *s){}int main(){yyparse();return0;}如前所述,yacc文件被%%分成了上中下三个部分,在这个程序中有几个需要说明的地方:program这是语法规则里面的第一个非终结符,注意上面的格式哦:“program”后面紧跟着一个冒号“:”,然后换行之后有一个分号“;”,这表明这个 program是由空串组成的。
LEX和YACC入门
LEX 将这些规则翻译为词法分析器。每一个规则依次包含一 个正则表达式以及该正则表达式得到匹配时要运行的一些代 码。
任何没有得到匹配的文本则简单地拷贝到标准输出。
2020/5/13
6
三、LEX程序设计
LEX正规表达式(1)
expression: value '+' value { System.out.println("Matched a '+' expression.\n"); }
2020/5/13
23
四、YACC程序设计
类似于 LEX, YACC 也有 一套变量和函数可供用户 来进行功能扩展。
YYSTYPE 定义了用来将 值从 lexer 拷贝到解析器 或者 YACC 的 yylval ( 另一个 YACC 变量)的 类型。默认的类型是 int 。 由于字符串可以从
的值。
2020/5/13
26
四、LEX与YACC结合
2020/5/13
27
public static void main(String args[]) {
int n = 1;
mylexer lexer = new mylexer();
if (lexer.yycreate(null)) {
n = lexer.yylex();
}
System.out.println("word count = "+lexer.wordCount);
两个百分号标记指出了 LEX程序中这一段的结束和三段中第二段 的开始。
10
Lex使用指南
Lex使用指南Lex是由美国Bell实验室等人用C语言开发的一种词法分析器自动生成工具,它提供一种供开发者编写词法规则(正规式等)的语言(Lex语言)以及这种语言的翻译器(这种翻译器将Lex语言编写的规则翻译成为C语言程序)。
Lex是linux下的工具,本实验使用的编译工具是cygwin(cygwin在windows下模拟一个linux环境)下的flex,它与lex的使用方法基本相同,只有很少的差别。
一、Lex的基本原理和使用方法Lex的基本工作原理为:由正规式生成NFA,将NFA变换成DFA,DFA经化简后,模拟生成词法分析器。
其中正规式由开发者使用Lex语言编写,其余部分由Lex翻译器完成.翻译器将Lex源程序翻译成一个名为的C语言源文件,此文件含有两部分内容:一部分是根据正规式所构造的DFA状态转移表,另一部分是用来驱动该表的总控程序yylex()。
当主程序需要从输入字符流中识别一个记号时,只需要调用一次yylex()就可以了。
为了使用Lex所生成的词法分析器,我们需要将程序用C编译器进行编译,并将相关支持库函数连入目标代码。
Lex的使用步骤可如下图所示:生成词法分析器的过程:使用所生成的词法分析器的过程:二、lex源程序的写法:Lex源程序必须按照Lex语言的规范来写,其核心是一组词法规则(正规式)。
一般而言,一个Lex源程序分为三部分,三部分之间以符号%%分隔。
`[第一部分:定义段]%%第二部分:词法规则段[%%第三部分:辅助函数段]其中,第一部分及第三部分和第三部分之上的%%都可以省略(即上述方括号括起的部分可以省略)。
以%开头的符号和关键字,或者是词法规则段的各个规则一般顶着行首来写,前面没有空格。
Lex源程序中可以有注释,注释由/*和*/括起,但是请注意,注释的行首需要有前导空白。
1. 第一部分定义段的写法:定义段可以分为两部分:第一部分以符号%{和%}包裹,里面为以C语法写的一些定义和声明:例如,文件包含,宏定义,常数定义,全局变量及外部变量定义,函数声明等。
从lexyacc说到编译器(二):flex的使用
从lexyacc说到编译器(二):flex的使用二、flex的使用看了第一篇的关于正则表达式的说明后,下面我们就来通过它,使用flex这个词法分析工具来构造我们的编译器的词法分析器.关于lex的教程应该是很多,这里我就简单地介绍一下,然后着重后面的lex和yacc的配合使用以及其技巧.所以,如果你不看了后还是不太明白lex或者yacc的使用,请你自己上网去查查,这方面的教程是很多的.我知道的一篇常见的就是Yacc 与 Lex 快速入门Lex 与 Yacc 介绍它的作者就是Ashish Bansal.Flex就是fast lex的意思.而lex就是Lexical Analyzar的意思.flex 可以在cygwin或者gnupro中找到.它是unix的一个工具,属于GNU 组织产品.网上也可以找到单独可以在windows下用的版本.我们一般把我们的词法扫描程序要扫描的一些单词(token)用正则表达式写好,然后作为lex的输入文件,输入命令flex xxx.l(xxx.l就是输入文件),lex经过处理后,就能得到一个名字叫lex.yy.c的C源代码.这个C源代码文件,就是我们的词法扫描程序.通常lex为我们生成的词法分析器的C源代码都是十分复杂而且庞大的,我们一般根本不会去查看里面的代码(放心好了,flex这个东西不会出错的)下面让我们看看几个我已经使用过的几个lex输入文件.这是一个前段时间我为GBA上的一个RPG游戏写的脚本引擎所使用的lex输入文件(部分)例2.1%{/* need this for the call to atof() below */#include <stdio.h>#include <stdlib.h>#include <math.h>#include "globals.h"%}digit [0-9]number ("-"|"+")?{digit}+hexnumber "0x"({digit}|[a-fA-F])+letter [a-zA-Z]identifier ({letter}|_)({number}|{letter}|_)* newline [\n]whitespace [ \t]+string \"[^"]*\"comment "#"[^#]*"#"%%{string} { return VM_STRING; } "Logo" { return VMIN_LOGO; } "FaceIn" { return VMIN_FACEIN; } "FaceOut" { return VMIN_FACEOUT; } "LoadTile" { return VMIN_LOAD_TILE; } "CreateRole" { return VMIN_CREATE_ROLE; } "ReleaseRole" { return VMIN_RELEASE_ROLE;} "CreateMap" { return VMIN_CREATE_MAP; } "ReleaseMAP" { return VMIN_RELEASE_MAP;} "ShowBitmap" { return VMIN_SHOWBITMAP; } "CreateDialog" { return VMIN_CREATE_DIALOG; }"ReleaseDialog" { return VMIN_RELEASE_DIALOG;}"Fight" { return VMIN_FIGHT; }"Delay" { return VMIN_DELAY; }"PressA" { return VMIN_PRESS_A; }"PressB" { return VMIN_PRESS_B; }"PressR" { return VMIN_PRESS_R; }"PressL" { return VMIN_PRESS_L; }"PressStart" { return VMIN_PRESS_START; }"PressSelect" { return VMIN_PRESS_SELECT;}{number} { return VM_NUMBER; }{whitespace} { /* skip whitespace */ }{identifier} { return VM_ID; }{newline} ;. ;%%int yywrap(){return 1;}这里的lex输入文件一共有三个部分,用%%分开.第一部分中的%{和}%中的内容就是直接放在lex输出C代码中的顶部.我们通过它可以来定义一些所需要的宏,函数和include一些头文件等等.我的这个lex输入文件中也没什么特别的东西,就是常规的C源文件的include头文件%{/* need this for the call to atof() below */#include <stdio.h>#include <stdlib.h>#include <math.h>#include "globals.h"%}第一部分中,除了前面的%{和}%包含的部分,下面的就是正则表达式的定义.看了第一篇的正则表达式,这样你就能够在这里派上用场了.让我们来看看我这里定义的正则表达式:digit [0-9]number ("-"|"+")?{digit}+hexnumber "0x"({digit}|[a-fA-F])+letter [a-zA-Z]identifier ({letter}|_)({number}|{letter}|_)*newline [\n]whitespace [ \t]+string \"[^"]*\"comment "#"[^#]*"#"digit就不用说了,就是0-9的阿拉伯数字定义,第一篇文章中也举了这个例子.number就是digit的1到无限次的重复,再在其前面加上”+”和”-“符号.注意:“a”: 即使a是元字符,它仍是字符a\a: 当a是元字符时候,为字符aa?: 一个可选的a,也就是说可以是a,也可以没有aa|b: a或b(a): a本身[abc]: 字符a,b或c中的任一个[a-d]: a,b,d或者d中的任一个[^ab]: 除了a或b外的任何一个字符.: 除了新行之外的任一个字符{xxx}: 名字xxx表示的正则表达式这里需要特别说明的就是newline [\n]newline就是新行,这里我使用了[]把\n换行号括起来.因为如果我直接用\n表示的话,那么按照上面的规则,那就会看成\和n两个字符,所以我使用了[\n].有些时候newline也被写成[\n]|[\r\n].因为在文本文件中,一般换行一次,那么就是一个\n(0xA),可是在二进制文件中,换行有时候又是\r\n(0xD,0xA)一共两个字符号.第二部分就是定义扫描到正则表达式的动作.这些动作其实就是C代码,它们将会被镶嵌在lex输出的C文件中的yylex()函数中.上面的例子的动作其实十分平常,就是返回一个值.我们在外部使用这个lex为我们生成C代码的时候,只需要使用它的int yylex()函数.当我们使用一次yylex(),那么就会自动去扫描一个匹配的正则表达式,然后完成它相应的动作.这里的动作都是返回一值,那么yylex就会返回这个值.通常默认yylex返回0时候,表示文件扫描结束,所以你的动作中最好不要返回0,以免发生冲突.当然,动作中也可以不返回一值,那么yylex就会完成这个动作后自动扫描下一个可以被匹配的字符串,一直到扫描到文件结束.当扫描到一个可以被匹配的字符串,那么这个时候,全局变量yytext 就等于这个字符串请大家一定记住这些正则表达式的顺序.如果出现一个字符串,可以同时匹配多个正则表达式,那么它将会被定义在前面的正则表达式匹配.所以我一般把字符串string定义在最前面.如果文件中的字符没有被lex输入文件中任何一个字符匹配,那么它会自动地被标准输出.所以大家一定要记住在每个正则表达式处理完毕后,一定要加上{newline}和.这两个正则表达式的动作.好,让我们看看lex为我们输出C文件中提供一些常量Lex 变量例2.2这是<<编译原理与实践>>书中配套的源代码的lex输入文件.大家可以参考一下,作者为它自己定义的一个Tiny C编译所做的词法扫描器./****************************************************//* File: tiny.l *//* Lex specification for TINY *//* Compiler Construction: Principles and Practice *//* Kenneth C. Louden *//****************************************************/%{#include "globals.h"#include "util.h"#include "scan.h"/* lexeme of identifier or reserved word */char tokenString[MAXTOKENLEN+1];%}digit [0-9]number {digit}+letter [a-zA-Z]identifier {letter}+newline \nwhitespace [ \t]+%%"if" {return IF;} "then" {return THEN;} "else" {return ELSE;} "end" {return END;} "repeat" {return REPEAT;} "until" {return UNTIL;} "read" {return READ;} "write" {return WRITE;} ":=" {return ASSIGN;} "=" {return EQ;} "<" {return LT;} "+" {return PLUS;} "-" {return MINUS;} "*" {return TIMES;} "/" {return OVER;} "(" {return LPAREN;} ")" {return RPAREN;} ";" {return SEMI;} {number} {return NUM;} {identifier} {return ID;}{newline} {lineno++;} {whitespace} {/* skip whitespace */} "{" { char c;do{ c = input();if (c == EOF) break;if (c == ‘\n‘) lineno++;} while (c != ‘}‘);}. {return ERROR;}%%TokenType getT oken(void){ static int firstTime = TRUE;TokenType currentToken;if (firstTime){ firstTime = FALSE;lineno++;yyin = source;yyout = listing;}currentToken = yylex();strncpy(tokenString,yytext,MAXTOKENLEN); if (TraceScan) {fprintf(listing,"\t%d: ",lineno); printToken(currentToken,tokenString);}return currentT oken;}这里有点不同的就是,作者用了另外一个getToken函数来代替yylex作为外部输出函数.其中getToken里面也使用了lex默认的输出函数yylex(),同时还做了一些其它的事情.不过我建议大家不要像作者那样另外写自己的结果输出函数,因为在后面,需要和yacc搭配工作的时候,yacc生成的语法分析程序只认名字叫yylex()的词法结果输出函数.if (firstTime){ firstTime = FALSE;lineno++;yyin = source;yyout = listing;}其中的yyin,yyout,source,listing都是FILE*类型.yyin就是要lex 生成的词法扫描程序要扫描的文件,yyout就是基本输出文件(其实我们通常都不用yyout,即使要生成一些输出信息,我们都是自己通过fprintf 来输出)."{" { char c;do{ c = input();if (c == EOF) break;if (c == ‘\n‘) lineno++;} while (c != ‘}‘);}其中,作者的这个Tiny C是以{}来包括注释信息.作者并没有写出注释信息的正则表达式,但是它可以通过检索“{”,然后用lex内部函数input()一一检查 { 后面的字符是不是 } 来跳过注释文字.(C语言的/* */注释文字正则表达式十分难写,所以很多时候我们都用这种方法直接把它的DFA(扫描自动机)写出来).本文就是通过简单地举出两个比较实际的例子来讲解flex输入文件的.再次说明,如果你是第一次接触lex,那么请看看前面我推荐的文章,你可以在IBM的开发者网上查到.下一篇关于yacc于BNF文法的说明也是如此.请大家先参考一下其它标准的教程.。
编译原理之lex,yacc学习
编译原理之lex,yacc学习写在前⾯的⼏句废话最近在项⽬的过程中接触了lex 和 yacc,他们可以帮助我们来实现⾃⼰的领域语⾔。
最典型的应⽤就是可以帮助我们来实现⾃定义测试脚本的执⾏器。
但是,这⾥也有⼀个限制,就是测试脚本要做的基本事情必须有现成的C语⾔库来实现,否则就做不到了;如果基本的操作是⽤java来做的,那么还可以⽤Antlr,这⾥不对Antlr做详细介绍。
lex是什么?教科书上把lex的作⽤的作⽤叫做“词法分析 lexical analysis ”,这个中⽂叫法⾮常让⼈看不明⽩(叫做“符号提取”更合适),其实从它的英⽂单词lexical上来看他的意思其实是⾮常清楚的。
lexical,在webster上的解释是:of or relating to words or the vocabulary of a language as distinguished from its grammar and construction。
指的是:⼀种语⾔中关于词汇、单词的,与之相对的是这种语⾔的语法和组织这么来看的话 lexical analysis 的作⽤就应该是语⾔中的词汇和单词分析。
事实上他的作⽤就是从语⾔中提取单词。
放到编程语⾔中来说,他要做的事情其实就是提取编程语⾔占⽤的各种保留字、操作符等等语⾔的元素。
所以他的另外⼀个名字scanner其实更形象⼀些,就是扫描⼀个⽂本中的单词。
lex把每个扫⾯出来的单词叫统统叫做token,token可以有很多类。
对⽐⾃然语⾔的话,英语中的每个单词都是token,token有很多类,⽐如non(名词)就是⼀个类token,apple就是属于这个类型的⼀个具体token。
对于某个编程语⾔来说,token的个数是很有限的,不像英语这种⾃然语⾔中有⼏⼗万个单词。
lex⼯具会帮我们⽣成⼀个yylex函数,yacc通过调⽤这个函数来得知拿到的token是什么类型的,但是token的类型是在yacc中定义的。
lex与yacc快速入门
lex与yacc快速⼊门第⼀节、lex和yacc是什么? lex 代表 lexical analyzar(词法分析器),yacc 代表 yet another compiler compiler(编译器代码⽣成器)。
lex和yacc在UNIX下分别叫flex和bison. 可以搜索到很多介绍flex&bison的⽂章,但这类⽂章对初学者来说不太容易看懂。
我们举个简单的例⼦来理解lex和yacc:在linux下,有很多系统配置⽂件,⼀些linux下的软件也有配置⽂件,那么程序是如何读取配置⽂件中的信息的呢?先⽤到lex词法分析器,读取配置⽂件中的关键词(后⾯说到的token标记其实可看做关键词);然后把关键词递交给yacc,yacc对⼀些关键词进⾏匹配,看是否符合⼀定的语法逻辑,如果符合就进⾏相应动作。
上⾯举的例⼦是分析配置⽂件内容的,当然可分析其他⽂件内容,或者制作编译器等。
第⼆节、⼀个简单的lex程序。
1、程序代码。
来看⼀个简单的lex程序,代码见下⾯,这段lex程序的⽬的是:输⼊⼏⾏字符串,输出⾏数,单词数和字符的个数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25/******************************************** Name : test.l* Date : Mar. 11, 2014* Blog : /lucasysfeng/* Description : ⼀个简单的lex例⼦,输⼊⼏⾏字符串,* 输出⾏数,单词数和字符的个数。
*******************************************//* 第⼀段 */%{int chars = 0;int words = 0;int lines = 0;%}/* 第⼆段 */%%[a-zA-Z]+ { words++; chars += strlen(yytext); } \n { chars++; lines++; }. { chars++; }%%/* 第三段 */main(int argc, char **argv){yylex();printf("%8d%8d%8d\n", lines, words, chars);}程序中yytext是lex变量,匹配模式的⽂本存储在这⼀变量中。
Lex和Yacc简明教程
和简明教程 Lex Yacc作者 :Thomas Niemann翻译: 傅惠忠目录序言3 导言4 Lex6 理论6练习7 YACC11 理论11练习,第一部分12练习,第二部分15 计算器18 描述18包含文件20Lex21 输入文件Yacc22 输入文件解释器26编译器27图28Lex34 进阶字符串34保留字35lex35 的调试Yacc37 进阶递归37IfElse37 歧义错误信息38继承属性39内含动作39调试Yacc39 参考书目40lex yacc lex yacc本书将教会你如何使用和构造一个编译器。
Lex与Yacc
|…
进行前执行。
| n { 语义动作n } | 没有{…},则使用缺省的语义动作
; 产生式结束用分号标记
语义动作是C的语句序列(在一对{}中间), 可以引用声明段中定义的C变量、宏等,还可 以使用yacc的伪变量。
规则段(续)
-yacc中的伪变量(类型缺省为整型)
$$ - 产生式左部非终结符的“值”
向前匹配。如果在匹配的模版中的“/”后跟有后 续表达式,只匹配模版中“/”前面的部分。如: 如果输入 A01,那么在模版 A0/1 中的 A0 是匹 配的。 将一系列常规表达式分组。
7
常规表达式举例
常规表达式
含义
joke[rs]
匹配 jokes 或 joker。
A{1,2}shis+ 匹配 AAshis, Ashis, Ashiss, Ashisss。
yywrap()
这一函数在文件(或输入)的末尾调用。如果函 数的返回值是1,就停止解析。 因此它可以用 来解析多个文件。代码可以写在第三段,这 就能够解析多个文件。 方法是使用 yyin 文件 指针(见上表)指向不同的文件,直到所有 的文件都被解析。最后,yywrap() 可以返回 1 来表示解析的结束。
FILE* 类型。 它指向记录 lexer 输出的位置。 缺 省情况下,yyin 和 yyout 都指向标准输入和输出。
yytext 匹配模式的文本存储在这一变量中(char*)。
yyleng 给出匹配模式的长度。
yylineno 提供当前的行数信息。(lexer不一定支持。)
14
Lex 函数
yylex() 这一函数开始分析。 它由 Lex 自动生成。
$i - 产生式右部第i个文法符号的“值”
Lex使用指南
Lex使⽤指南Lex是由美国Bell实验室M.Lesk等⼈⽤C语⾔开发的⼀种词法分析器⾃动⽣成⼯具,它提供⼀种供开发者编写词法规则(正规式等)的语⾔(Lex语⾔)以及这种语⾔的翻译器(这种翻译器将Lex语⾔编写的规则翻译成为C语⾔程序)。
Lex是linux下的⼯具,本实验使⽤的编译⼯具是cygwin(cygwin在windows下模拟⼀个linux环境)下的flex,它与lex的使⽤⽅法基本相同,只有很少的差别。
1.Lex的基本原理和使⽤⽅法Lex的基本⼯作原理为:由正规式⽣成NFA,将NFA变换成DFA,DFA经化简后,模拟⽣成词法分析器。
其中正规式由开发者使⽤Lex语⾔编写,其余部分由Lex翻译器完成.翻译器将Lex源程序翻译成⼀个名为lex.yy.c的C语⾔源⽂件,此⽂件含有两部分内容:⼀部分是根据正规式所构造的DFA状态转移表,另⼀部分是⽤来驱动该表的总控程序yylex()。
当主程序需要从输⼊字符流中识别⼀个记号时,只需要调⽤⼀次yylex()就可以了。
为了使⽤Lex所⽣成的词法分析器,我们需要将lex.yy.c程序⽤C编译器进⾏编译,并将相关⽀持库函数连⼊⽬标代码。
Lex的使⽤步骤可如下图所⽰:2.lex源程序的写法Lex源程序必须按照Lex语⾔的规范来写,其核⼼是⼀组词法规则(正规式)。
⼀般⽽⾔,⼀个Lex源程序分为三部分,三部分之间以符号%%分隔。
[第⼀部分:定义段]%%第⼆部分:词法规则段[%%第三部分:辅助函数段]其中,第⼀部分及第三部分和第三部分之上的%%都可以省略(即上述⽅括号括起的部分可以省略)。
以%开头的符号和关键字,或者是词法规则段的各个规则⼀般顶着⾏⾸来写,前⾯没有空格。
Lex源程序中可以有注释,注释由/*和*/括起,但是请注意,注释的⾏⾸需要有前导空⽩。
1)第⼀部分定义段的写法:定义段可以分为两部分:第⼀部分以符号%{和%}包裹,⾥⾯为以C语法写的⼀些定义和声明:例如,⽂件包含,宏定义,常数定义,全局变量及外部变量定义,函数声明等。
Lex和Yacc入门教程(八).使用堆栈编译语法
Lex和Yacc⼊门教程(⼋).使⽤堆栈编译语法Lex和Yacc⼊门教程(⼋).使⽤堆栈编译语法分类: C++/C/C# 2007-06-05 11:08 4571⼈阅读评论(26) 收藏举报yacccommandshellunix存储linuxLex和Yacc应⽤⽅法(⼋).使⽤堆栈编译语法草⽊⽠ 20070604⼀、序前⾯⼀些系列⽂章着重介绍了递归语法树在编译理论⽅⾯的应⽤。
本⽂则会介绍另⼀种实现⽅式----堆栈。
堆栈在底层系统有⼗分⼴泛的应⽤,同样也⼗分擅长处理语法结构,这⾥通过实际⽰例探讨如何构造堆栈完成语法分析。
重要补充:下⾯是本系列⽂章全⽰例代码统⼀的调试测试环境,另对于lex,yacc⽂件需要存储为Unix格式,这⼀点和Linux,Unix下shell很类似,DOS格式的Shell是不能够被执⾏的,同样bison,lex编译DOS格式⽂件会出错误提⽰:Red Hat Linux release 9 (Shrike)Linux 2.4.20-8gcc version 3.2.2 20030222bison (GNU Bison) 1.35lex version 2.5.4flex version 2.5.4⼆、具体⽰例本⽰例主要完成功能:1 ⽀持整型,浮点型和字符串型2 ⽀持变量存储,变量名可为多个字符3 ⽀持整型,浮点型的+-*/()=运算法则4 ⽀持字符串型赋值5 ⽀持print打印整型,浮点型和字符串型6 ⽀持打印变量值7 ⽀持while if else switch四种控制结构,并⽀持控制结构的嵌套8 ⽀持> >= < <= != == 六种⽐较运算,同时也⽀持字符串的⽐较9 ⽀持 && || 复合⽐较运算10 ⽀持对空格和TAB的忽略处理11 ⽀持#的单⾏注释12 ⽀持{}多重组合13 ⽀持编译错误的具体显⽰14 ⽀持外部变量值传⼊(整型,浮点型和字符型)15 ⽀持外部变量获取(整型,浮点型和字符型)16 完整的企业应⽤模式三、⽰例全代码(略)A.stack.l----------------------------------------------B.stack.y----------------------------------------------C.stack.h----------------------------------------------D.stackparser.c----------------------------------------------E.public.h----------------------------------------------F.main.c----------------------------------------------G.mk 编译shell⽂件----------------------------------------------bison -d stack.ylex stack.lgcc -g -c lex.yy.c stack.tab.c stackparser.car -rl stack.a *.ogcc -g -o lw main.c stack.aH.mkclean shell⽂件----------------------------------------------rm stack.tab.crm stack.tab.hrm lex.yy.crm *.orm *.arm lw四、思路说明上⾯列出的代码是⽬前最长的。
Lex入门学习
Yacc 使用者会发现 yylex 是 Yacc 用于接收语法分析器结果的名字,所以使用 Lex 中的名字 可以简化操作。
Lex 由源文件中的正则表达式产生确定的有穷状态自动机[4]。为了节省空间,这个自动机是 被解释,而不是被编译得到的。结果仍是高速分析器。特别的,Lex 程序识别和分割输入流 的时间与输入的长度成正比。Lex 中规则的数量和复杂度对于执行速度来说是无关的,除非 规则中包含的前向上下文需要大量的反复扫描。与规则的数量和复杂度相关的是有穷自动机 的体积,因此也就是 Lex 生成程序的大小。
%%
(没有 definitions 和 rules),这个程序输入将不加修改地复制到输出。
由上面的 Lex 程序轮廓可知,规则(rules)反映了用户的控制;它是一个表格,左侧是正则 表达式(regular expressions)(参见第 3 节),而右侧是动作(actions),当表达式被识别出 以后,动作的程序片断被执行。所以,一个单独的规则可能是
翻译:彭一凡 北京工业大学计算机科学与技术
摘要
Lex 用于编写一些程序,这些程序能够通过正则表达式识别输入流中的控制流。它能很好的 适用于文本脚本类型的翻译,以及用于语法分析例程的输入分段。 Lex 源文件是一个由正则表达式和相应程序片断构成的表格。表格被转换成程序,该程序读 取输入流、拷贝它到输出流、并且将输入分割成能够匹配给定表达式的字符串。每一次当字 符串被识别后,相应的程序片断被执行。表达式的识别由 Lex 生成的有限状态自动机执行。 输入流中相应的正则表达式被识别后,用户写的程序片断按顺序被执行。 Lex 写就的词法分析器接受二义性的说明书,在每一个输入点上选择最长的可能匹配。如果 必要,输入中会有前向搜索,但是输入流会回退到当前的分割处,这样用户可以在很大程度 上拥有操作的自由。 Lex 可以生成 C 或者 Ratfor 的分析器,Ratfor 是一种可以被自动转换为 Fortran 的语言。在 PDP-11 UNIX、Honeywell GCOS 和 IBM OS 系统上都可以使用 Lex。然而,这个使用手册 只讨论了 UNIX 系统上用 C 语言生成解析器的方法,它只适用于 UNIX Version 7 下的 Lex 形式。对于那些希望联合使用编译器生成器的程序,Lex 的设计使其很容易与 Yacc 一起使 用。
经典编译工具
经典编译工具Typical cpmpile tool一、正则表达式学过编译原理的朋友肯定都接触过LEX这个小型的词法扫描工具. 但是却很少有人真正把LEX用在自己的程序里. 在构造专业的编译器的时候,常常需要使用到lex和yacc. 正是因为这两个工具,使得我们编写编译器,解释器等工具的时候工作变得非常简单.不过话说回来,会使用lex和yacc的人也确实不简单. Lex和yacc里面牵涉到一系列的编译原理的理论知识,不是简单地看看书就能搞懂的. 本文只是简单地介绍一下lex和yacc的使用方法.相关编译理请查看本科教材.国内大学教材里面对于lex和yacc的介绍很少,有些根本就没有,不过在国外的编译原理教材介绍了很多. 按照学科的分类,国内大学本科里面开的<<编译原理>>教程只是讲解编译的原理,并不讲解实践. 而对于实践方面则是另外一门学科<<编译技术>>. 关于编译技术的书籍在国内是少之又少. 前不久, 听说上海交大的计科内部出版过编译技术的教材.可惜我们这些人就无法得见了. 还好,机械工业出版社引进了美国Kenneth C.Louden所著的经典著作<<编译原理及实践>>中,比较详细地介绍lex和yacc的使用.Lex属于GNU内部的工具,它通常都是gcc的附带工具. 如果你使用的Linux操作系统,那么肯定系统本身就有lex和yacc,不过yacc的名字变成了bison. 如果你使用的Windows操作系统,那么可以到cygwin或者GNUPro里面找得到. 网上也有windows版本lex和yacc,大家可以自己去找一找.本文一共有两篇,一篇是介绍lex,另一篇是介绍yacc. Lex和yacc 搭配使用, 我们构造自己的编译器或者解释器就如同儿戏. 所以我把本文的名字叫做黄金组合.本文以flex( Fase Lex)为例,两讲解如何构造扫描程序.Flex可以通过一个输入文件,然后生成扫描器的C源代码.其实扫描程序并不只用于编译器 .比如编写游戏的脚本引擎的时候,我看到很多开发者都是自己写的扫描器,其算法相当落后(完全没有DFA 的概念化), 甚至很多脚本引擎开发者的词法扫描器都没有编写,而是在运行过程中寻找token(单词). 在现代的计算机速度确实可以上小型的脚本引擎在运行中进行词法扫描, 但是作为一个合格的程序员, 或者说一个合格的计算机本科毕业生而来说, 能够运用编译原理与技术实践,应该是个基本要求.如果要说到词法分析的扫描器源代码编写, 其实也很简单, 会C语言的人都会写. 可是Kenneth Louden在<<编译原理及技术>里面,花了50多页,原因就是从理论角度,介绍标准的,可扩展的,高效的词法扫描器的编写. 里面从正则表达式介绍到DFA(有穷自动机),再到NFA(非确定性有穷自动机),最后才到代码的编写. 以自动机原理编译扫描器的方法基本上就是现在词法扫描器的标准方法, 也就是Lex使用的方法. 在Lex中,我们甚至不需要自己构造词法的DFA, 我们只需要把相应的正则表达式输入, 然后lex能够为我们自己生成DFA,然后生成源代码,可谓方便之极.本文不讲DFA, lex的输入是正则表达式, 我们直接先看看正则表达式方面知识就可以了.1.正则表达式(regular expression):对于学过编译原理的朋友来说,这一节完全可以不看.不过有些东西还是得注意一下,因为在flex中的正则表达式的使用有些具体的问题是在我们的课本上没有说明的.先看看例子:例1.1name Tangl_99这就是定义了name这个正则表达式,它就等于字符串Tangl_99.所以,如果你的源程序中出现了Tangl_99这个字符传,那么它就等于出现一次name正则表达式.例1.2digit 0|1|2|3|4|5|6|7|8|9这个表达式就是说,正则表达式digit就是0,1,2,…,9中的某一个字母.所以无论是0,2,或者是9…都是属于digit这个正则表达式的.“|”符号表示”或者”的意思.那么定义正则表达式name Tangl_99|Running,同样的,如果你的源程序中出现了Tangl_99或者Running,那么就等于出现了一次name 正则表达式.例1.3one 1*“*”符号表示”零到无限次重复”那么one所表示的字符串就可以是空串(什么字符都没有), 1, 11, 111, 11111, 11111111111, 11111111…等等.总之,one就是由0个或者N 个1所组成(N可以为任意自然数).与”*”相同的有个”+”符号.请看下面的例子1.4例1.4realone 1+“+”符号表示”1到无限次重复”那么realone和one不同的唯一一点就是,realone不包含空串,因为”+”表示至少一次重复,那么realone至少有一个1.所以realone 所表达的字符串就是1,11,111, 1111, 11111…,等等.例1.5digit [0-9]letter [a-zA-Z]这里的digit等于例1.2中的digit,也就是说,a|b|c就相当于[a-c].同理,letter也就是相当于a|b|c|d|e|f|…|y|z|A|B|C|D…|Z不过注意的一点就是,你不能把letter写成[A-z],而必须大写和小写都应该各自写出来.例1.6notA [^A]“^”表示非,也就是除了这个字符以外的所有字符所以notA表示的就是除了A以外的所有字符.下面让我们来看看一些一般高级程序语言中常用的综合例子.digit [0-9]number {digit}+letter [a-zA-Z_]digit前面多次提起过,就是0-9的阿拉伯数字.number就是所有的数字组合,也就是整数.Letter前面也提起过,唯一不同的就是多了一个下划线.因为一般我们的C语言中容许有下划线来表示定义的变量名,所以我也把下划线当成英语字母来处理了. 这里number中使用上面定义的digit正则表达式.在lex中,用{digit}就是表示正则表达式digit.newline [\n]whitespace [ \t]+newline就是提行的意思.这里我们使用的是\n这个符号,它和C语言中表示提行号一致.问题是大家可能要问到为什么要使用[]符号.因为在lex中,如果你使用[],那么里面表示的肯定就是单个字符号,而不会被理解成”\”和”n”两个字符. Whitespace就是空格符号的意思.一般的高级程序语言中有两种,一种就是简单的空格,还有一种就是\t制表符.使用了”+”符号,就表示了这些空白符号的无限组合.二、flex的使用看了第一篇的关于正则表达式的说明后,下面我们就来通过它,使用flex这个词法分析工具来构造我们的编译器的词法分析器.关于lex的教程应该是很多,这里我就简单地介绍一下,然后着重后面的lex和yacc的配合使用以及其技巧.所以,如果你不看了后还是不太明白lex或者yacc的使用,请你自己上网去查查,这方面的教程是很多的.我知道的一篇常见的就是Yacc 与 Lex 快速入门Lex 与 Yacc 介绍它的作者就是Ashish Bansal.Flex就是fast lex的意思.而lex就是Lexical Analyzar的意思.flex可以在cygwin 或者gnupro中找到.它是unix的一个工具,属于GNU 组织产品.网上也可以找到单独可以在windows下用的版本.我们一般把我们的词法扫描程序要扫描的一些单词(token)用正则表达式写好,然后作为lex的输入文件,输入命令flex xxx.l(xxx.l就是输入文件),lex经过处理后,就能得到一个名字叫lex.yy.c的C源代码.这个C源代码文件,就是我们的词法扫描程序.通常lex为我们生成的词法分析器的C源代码都是十分复杂而且庞大的,我们一般根本不会去查看里面的代码(放心好了,flex这个东西不会出错的)下面让我们看看几个我已经使用过的几个lex输入文件.这是一个前段时间我为GBA上的一个RPG游戏写的脚本引擎所使用的lex输入文件(部分)例2.1%{/* need this for the call to atof() below */#include#include#include#include "globals.h"%}digit [0-9]number ("-"|"+"?{digit}+hexnumber "0x"({digit}|[a-fA-F])+letter [a-zA-Z]identifier ({letter}|_)({number}|{letter}|_)*newline [\n]whitespace [ \t]+string \"[^"]*\"comment "#"[^#]*"#"%%{string} { return VM_STRING; }"Logo" { return VMIN_LOGO; }"FaceIn" { return VMIN_FACEIN; }"FaceOut" { return VMIN_FACEOUT; } "LoadTile" { return VMIN_LOAD_TILE; } "CreateRole" { return VMIN_CREATE_ROLE; } "ReleaseRole" { return VMIN_RELEASE_ROLE;} "CreateMap" { return VMIN_CREATE_MAP; } "ReleaseMAP" { return VMIN_RELEASE_MAP;} "ShowBitmap" { return VMIN_SHOWBITMAP; } "CreateDialog" { return VMIN_CREATE_DIALOG; } "ReleaseDialog" { return VMIN_RELEASE_DIALOG;} "Fight" { return VMIN_FIGHT; } "Delay" { return VMIN_DELAY; }"PressA" { return VMIN_PRESS_A; }"PressB" { return VMIN_PRESS_B; }"PressR" { return VMIN_PRESS_R; }"PressL" { return VMIN_PRESS_L; } "PressStart" { return VMIN_PRESS_START; } "PressSelect" { return VMIN_PRESS_SELECT;} {number} { return VM_NUMBER; } {whitespace} { /* skip whitespace */ }{identifier} { return VM_ID; }{newline} ;. ;%%int yywrap(){return 1;}这里的lex输入文件一共有三个部分,用%%分开.第一部分中的%{和}%中的内容就是直接放在lex输出C代码中的顶部.我们通过它可以来定义一些所需要的宏,函数和include一些头文件等等.我的这个lex输入文件中也没什么特别的东西,就是常规的C源文件的include头文件%{/* need this for the call to atof() below */#include#include#include#include "globals.h"%}第一部分中,除了前面的%{和}%包含的部分,下面的就是正则表达式的定义.看了第一篇的正则表达式,这样你就能够在这里派上用场了.让我们来看看我这里定义的正则表达式:digit [0-9]number ("-"|"+"?{digit}+hexnumber "0x"({digit}|[a-fA-F])+letter [a-zA-Z]identifier ({letter}|_)({number}|{letter}|_)*newline [\n]whitespace [ \t]+string \"[^"]*\"comment "#"[^#]*"#"digit就不用说了,就是0-9的阿拉伯数字定义,第一篇文章中也举了这个例子.number就是digit的1到无限次的重复,再在其前面加上”+”和”-“符号.注意:“a”:即使a是元字符,它仍是字符a\a: 当a是元字符时候,为字符aa?: 一个可选的a,也就是说可以是a,也可以没有aa|b: a或b(a): a本身[abc]: 字符a,b或c中的任一个[a-d]: a,b,d或者d中的任一个[^ab]: 除了a或b外的任何一个字符.: 除了新行之外的任一个字符{xxx}: 名字xxx表示的正则表达式这里需要特别说明的就是newline [\n]newline就是新行,这里我使用了[]把\n换行号括起来.因为如果我直接用\n表示的话,那么按照上面的规则,那就会看成\和n两个字符,所以我使用了[\n].有些时候newline也被写成[\n]|[\r\n].因为在文本文件中,一般换行一次,那么就是一个\n(0xA),可是在二进制文件中,换行有时候又是\r\n(0xD,0xA)一共两个字符号.第二部分就是定义扫描到正则表达式的动作.这些动作其实就是C代码,它们将会被镶嵌在lex输出的C文件中的yylex()函数中.上面的例子的动作其实十分平常,就是返回一个值.我们在外部使用这个lex为我们生成C代码的时候,只需要使用它的int yylex()函数.当我们使用一次yylex(),那么就会自动去扫描一个匹配的正则表达式,然后完成它相应的动作.这里的动作都是返回一值,那么yylex就会返回这个值.通常默认yylex返回0时候,表示文件扫描结束,所以你的动作中最好不要返回0,以免发生冲突.当然,动作中也可以不返回一值,那么yylex就会完成这个动作后自动扫描下一个可以被匹配的字符串,一直到扫描到文件结束.当扫描到一个可以被匹配的字符串,那么这个时候,全局变量yytext 就等于这个字符串请大家一定记住这些正则表达式的顺序.如果出现一个字符串,可以同时匹配多个正则表达式,那么它将会被定义在前面的正则表达式匹配.所以我一般把字符串string定义在最前面.如果文件中的字符没有被lex输入文件中任何一个字符匹配,那么它会自动地被标准输出.所以大家一定要记住在每个正则表达式处理完毕后,一定要加上{newline}和.这两个正则表达式的动作.好,让我们看看lex为我们输出C文件中提供一些常量Lex 变量yyinFILE* 类型。
使用lex+yacc分析simple语言
使用lex yacc分析simple语言简单语句(2007-03-18 18:01:07)转载关于lex和yacc的理论知识大家可以从网上搜到一些,比较好的资料是机械工业出版社《lex与yacc》中文第二版,当然也可以从网上下到电子版看一看,这里就不赘述了。
下面介绍一个简单的算术表达式和变量说明语句的lex和yacc程序:LEX程序部分:%{//头文件,全局变量定义#include "myparser.h"#include <stdio.h>#include <string.h>#include <stdlib.h>int c;extern int yylval;%}/////////////////////////////////////////////////////////////// declarations section 说明部分可以为空// place any declarations here%%//////////////////////////////////////////////////////////// rules section 规则部分不可或缺// place your Lex rules here/***********************空格,关键字的识别*********************/// return语句返回token标记yacc进行语法识别" " ;var { printf("%10s KEY_var \n",yytext);return (VAR);}real { printf("%10s KEY_real \n",yytext);return (REAL);}integer { printf("%10s KEY_integer \n",yytext);return (INTEGER);} char { printf("%10s KEY_char \n",yytext);return (CHAR);}bool { printf("%10s KEY_bool \n",yytext);return (BOOL);}/*************************界线符的识别************************/ : { printf("%10s SYM_colon \n",yytext);return (COLON);}, { printf("%10s SYM_comma \n",yytext);return (COMMA);}/*************************标志符的识别************************/ [A-Za-z]+ {c = yytext[0];yylval = c - 'a';return(LETTER);printf("%10s \n",yytext);}/*************************整数的识别*********************/ [0-9]+ {printf("%10s \n",yytext);// c = yytext[0];//yylval = c - '0';yylval = atof(yytext);return (DIGIT);}/********************其他符号的识别***************************/c = yytext[0];return(c);}%%///////////////////////////////////////////////////////////////// programs section 辅助程序部分YACC程序部分:%{#include "mylexer.h"#include <stdio.h>#include <ctype.h>#include <stdlib.h>#include <assert.h>int regs[26];%}///////////////////////////////////////////////////////////////// // declarations section// attribute type%include {#ifndef YYSTYPE#define YYSTYPE int}// place any declarations here%start list //文法从list开始识别%token DIGIT LETTER VAR //标记%token COLON COMMA%token REAL INTEGER CHAR BOOL%left '+' '-' //左结合%left '*' '/'%left UMINUS /*supplies precedence for unary minus */%%/////////////////////////////////////////////////////////////// // rules section// place your YACC rules here (there must be at least one)list: /*empty */|list stat '\n'|list VARDECLARE '\n'|list error '\n'{yyerror;};VARDECLARE: VAR IDS COLON TYPE {printf("变量声明成功\n");} ;IDS: IDS COMMA LETTER| LETTER {$$=$1;};TYPE: REAL|INTEGER|CHAR {$$=$1;}|BOOL|{yyerror("未声明变量类型\n");yyerrok();};stat: expr{printf("算术表达式分析成功结果为:%d\n",$1);}|LETTER '=' expr//赋值语句识别{regs[$1] = $3;};expr: '(' expr ')'//算术表达式的规约识别{$$ = $2;}|'(' expr error$$=$2;yyerror("missing')'");yyerrok(); }|error expr ')'{$$=$2;yyerror("missing'('");yyerrok(); }|expr '*' expr{$$ = $1 * $3;}|expr '/' expr{ if ($3==0)yyerror("Divide by zero\n");else$$ = $1 / $3;}|expr '+' expr{$$ = $1 + $3;}|expr '-' expr{$$ = $1 - $3;|'-' expr %prec UMINUS{$$ = -$2;}|LETTER{$$ = regs[$1];}|DIGIT{$$=$1;};%%///////////////////////////////////////////////////////// programs sectionmain(){printf("*******用lex和yacc完成simple语言简单语句的分析*******\n"); printf("请输入要编译的语句:\n");return yyparse();}void yyerror(char *s){fprintf(stderr, "%s\n",s);return 0;}yywrap(){return 1;}//////////////////END///////////////////////当一起使用lex扫描程序和yacc语法分析程序时,语法分析程序(parser)是较高级别的例程。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
含义 匹配 jokes 或 joker。 匹配 AAshis, Ashis, AAshi, Ashi。 匹配在 A 出现位置后跟随的从 b 到 e 的所有字符中的 0 个或 1个。
Lex 中的标记声明类似 C 中的变量名。每个标记都有一个相关的表达式。 (下表中给出了标记和表达式的例子。) 使用这个表中 的例子,我们就可以编一个字数统计的程序了。 我们的第一个任务就是说明如何声明标记。 标记声明举例 标记 数字(number) 字符(chars) 空格(blank) 字(word) 变量(variable) Lex 编程 Lex 编程可以分为三步: 1. 以 Lex 可以理解的格式指定模式相关的动作。 2. 在这一文件上运行 Lex,生成扫描器的 C 代码。 3. 编译和链接 C 代码,生成可执行的扫描器。 注意: 如果扫描器是用 Yacc 开发的解析器的一部分,只需要进行第一步和第二步。 关于这一特殊问题的帮助请阅读 Yacc和 将 Lex 和 Yacc 结合起来部分。 现在让我们来看一看 Lex 可以理解的程序格式。一个 Lex 程序分为三个段:第一段是 C 和 Lex 的全局声明,第二段包括模式(C 代码),第三段是补充的 C 函数。 例如, 第三段中一般都有 main() 函数。这些段以%%来分界。 那么,回到字数统计的 Lex 程 序,让我们看一下程序不同段的构成。 C 和 Lex 的全局声明 这一段中我们可以增加 C 变量声明。这里我们将为字数统计程序声明一个整型变量,来保存程序统计出来的字数。 我们还将进行 Lex 的标记声明。 字数统计程序的声明
终端和非终端符号
终端符号 : 代表一类在语法结构上等效的标记。 终端符号有三种类型: 命名标记: 这些由 %token 标识符来定义。 按照惯例,它们都是大写。 字符标记 : 字符常量的写法与 C 相同。例如, -- 就是一个字符标记。 字符串标记 : 写法与 C 的字符串常量相同。例如,"<<" 就是一个字符串标记。 lexer 返回命名标记。 非终端符号 : 是一组非终端符号和终端符号组成的符号。 按照惯例,它们都是小写。 在例子中,file 是一个非终端标记而 NAME 是一个终端标记。 用 Yacc 来创建一个编译器包括四个步骤: 1. 通过在语法文件上运行 Yacc 生成一个解析器。 2. 说明语法: 编写一个 .y 的语法文件(同时说明 C 在这里要进行的动作)。 编写一个词法分析器来处理输入并将标记传递给解析器。 这可以使用 Lex 来完成。 编写一个函数,通过调用 yyparse() 来开始解析。 编写错误处理例程(如 yyerror())。 3. 编译 Yacc 生成的代码以及其他相关的源文件。 4. 将目标文件链接到适当的可执行解析器库。 用 Yacc 编写语法
yyless(int n) yymore()
对 Lex 的讨论就到这里。下面我们来讨论 Yacc... Yacc Yacc 代表 Yet Another Compiler Compiler。 Yacc 的 GNU 版叫做 Bison。它是一种工具,将任何一种编程语言的所有语法翻译 成针对此种语言的 Yacc 语 法解析器。它用巴科斯范式(BNF, Backus Naur Form)来书写。按照惯例,Yacc 文件有 .y 后缀。编译 行如下调用 Yacc 编译器:
%{ int wordCount = 0; %} chars [A-za-z\_\'\.\"] numbers ([0-9])+ delim [" "\n\t] whitespace {delim}+ words {chars}+ %%
相关表达式 ([0-9])+ [A-Za-z] "" (chars)+ (字符)+(数字)*(字符)*(数字)*
C 代码 Lex 编程的第三段,也就是最后一段覆盖了 C 的函数声明(有时是主函数)。注意这一段必须包括 yywrap() 函数。 Lex 有一套可 供使用的函数和变量。 其中之一就是 yywrap。 一般来说,yywrap() 的定义如下例。我们将在 高级 Lex 中探讨这一问题。 字数统计程序的 C 代码段
1/10
https:///developerworks/cn/linux/sdk/lex/
13-10-30
Yacc 与 Lex 快速入门
"<一些符号>" / () 常规表达式举例 常规表达式 joke[rs] A{1,2}shis+ (A[b-e])+
字符的字面含义。元字符具有。 向前匹配。如果在匹配的模版中的“/”后跟有后续表达式,只匹配模版中“/”前 面的部分。如:如 果输入 A01,那么在模版 A0/1 中的 A0 是匹配的。 将一系列常规表达式分组。
3/10
https:///developerworks/cn/linux/sdk/lex/
13-10-30Biblioteka Yacc 与 Lex 快速入门
出。 yytext yyleng yylineno Lex 函数 yylex() yywrap() 这一函数开始分析。 它由 Lex 自动生成。 这一函数在文件(或输入)的末尾调用。 如果函数的返回值是1,就停止解析。 因此它可以用来 解析多个文件。 代码可以写在第三段,这就能够解析多个文件。 方法是使用 yyin 文件指针(见 上表)指向不同的文件,直到所有的文件都被解析。 最后,yywrap() 可以返回 1 来表示解析的 结束。 这一函数可以用来送回除了前�n? 个字符外的所有读出标记。 这一函数告诉 Lexer 将下一个标记附加到当前标记后。 匹配模式的文本存储在这一变量中(char*)。 给出匹配模式的长度。 提供当前的行数信息。 (lexer不一定支持。)
含义 1个或多个数字 任意字符 一个空格 1个或多个 chars
两个百分号标记指出了 Lex 程序中这一段的结束和三段中第二段的开始。 Lex 的模式匹配规则
https:///developerworks/cn/linux/sdk/lex/ 2/10
13-10-30
Yacc 与 Lex 快速入门
void main() { yylex(); /* start the analysis*/ printf(" No of words: %d\n", wordCount); } int yywrap() { return 1; }
上一节我们讨论了 Lex 编程的基本元素,它将帮助你编写简单的词法分析程序。 在 高级 Lex 这一节中我们将讨论 Lex 提供的函 数,这样你就能编写更加复杂的程序了。 将它们全部结合起来 .lex文件是 Lex 的扫描器。它在 Lex 程序中如下表示:
让我们看一下 Lex 描述我们所要匹配的标记的规则。(我们将使用 C 来定义标记匹配后的动作。) 继续看我们的字数统计程序, 下面是标记匹配的规则。 字数统计程序中的 Lex 规则
{words} { wordCount++; /* increase the word count by one*/ } {whitespace} { /* do nothing*/ } {numbers} { /* one may want to add some processing here*/ } %%
$ yacc <options> <filename ending with .y>
在进一步阐述以前,让我们复习一下什么是语法。在上一节中,我们看到 Lex 从输入序列中识别标记。 如果你在查看标记序列,你 可能想在这一序列出现时执行某一动作。 这种情况下有效序列的规范称为语法。Yacc 语法文件包括这一语法规范。 它还包含了序 列匹配时你想要做的事。 为了更加说清这一概念,让我们以英语为例。 这一套标记可能是:名词, 动词, 形容词等等。为了使用这些标记造一个语法正确的句 子,你的结构必须符合一定的规则。 一个简单的句子可能是名词+动词或者名词+动词+名词。(如 I care. See spot run.) 所以在我们这里,标记本身来自语言(Lex),并且标记序列允许用 Yacc 来指定这些标记(标记序列也叫语法)。
$ lex <file name.lex>
这生成了 lex.yy.c 文件,它可以用 C 编译器来进行编译。它还可以用解析器来生成可执行程序,或者在链接步骤中通过选项 �ll 包 含 Lex 库。 这里是一些 Lex 的标志: -c表示 C 动作,它是缺省的。 -t写入 lex.yy.c 程序来代替标准输出。 -v提供一个两行的统计汇总。 -n不打印 -v 的汇总。 高级 Lex Lex 有几个函数和变量提供了不同的信息,可以用来编译实现复杂函数的程序。 下表中列出了一些变量和函数,以及它们的使用。 详尽的列表请参考 Lex 或 Flex 手册(见后文的 资源)。 Lex 变量 yyin yyout FILE* 类型。 它指向 lexer 正在解析的当前文件。 FILE* 类型。 它指向记录 lexer 输出的位置。 缺省情况下,yyin 和 yyout 都指向标准输入和输
13-10-30
IBM
Yacc 与 Lex 快速入门
日本語 登录 (或注册)
技术主题
软件下载
社区
技术讲座
Yacc 与 Lex 快速入门
Lex 与 Yacc 介绍 Ashish Bansal (abansal@), 软件工程师, Sapient 公司 简介: Lex 和 Yacc 是 UNIX 两个非常重要的、功能强大的工具。事实上,如果你熟练掌握 Lex 和 Yacc 的话,它们的强大功能使 创建 FORTRAN 和 C 的编译器如同儿戏。Ashish Bansal 为您详细的讨论了编写自己的语言和编译器所用到的这两种工具,包括常 规表达式、声明、匹配模式、变量、Yacc 语法和解析器代码。最后,他解释了怎样把 Lex 和 Yacc 结合起来。 发布日期: 2000 年 11 月 01 日 级别: 初级 访问情况 : 80020 次浏览 评论: (查看 | 添加评论 - 登录) 平均分 (203个评分) 为本文评分 Lex 代表 Lexical Analyzar。Yacc 代表 Yet Another Compiler Compiler。 让我们从 Lex 开始吧。 Lex Lex 是一种生成扫描器的工具。扫描器是一种识别文本中的词汇模式的程序。 这些词汇模式(或者常规表达式)在一种特殊的句子 结构中定义,这个我们一会儿就要讨论。 一种匹配的常规表达式可能会包含相关的动作。这一动作可能还包括返回一个标记。 当 Lex 接收到文件或文本形式的输入时,它试 图将文本与常规表达式进行匹配。 它一次读入一个输入字符,直到找到一个匹配的模式。 如果能够找到一个匹配的模式,Lex 就执 行相关的动作(可能包括返回一个标记)。 另一方面,如果没有可以匹配的常规表达式,将会停止进一步的处理,Lex 将显示一个 错误消息。 Lex 和 C 是强耦合的。一个 .lex 文件(Lex 文件具有 .lex 的扩展名)通过 lex 公用程序来传递,并生成 C 的输出文件。这些文件 被编译为词法分析器的可执行版本。 Lex 的常规表达式 常规表达式是一种使用元语言的模式描述。表达式由符号组成。符号一般是字符和数字,但是 Lex 中还有一些具有特殊含义的其他 标记。 下面两个表格定义了 Lex 中使用的一些标记并给出了几个典型的例子。 用 Lex 定义常规表达式 字符 A-Z, 0-9, a-z . [] * + ? $ {} \ ^ | 含义 构成了部分模式的字符和数字。 匹配任意字符,除了 \n。 用来指定范围。例如:A-Z 指从 A 到 Z 之间的所有字符。 一个字符集合。匹配括号内的 任意 字符。如果第一个字符是 ^ 那么它表示否定模式。例如: [abC] 匹配 a, b, 和 C中的任何一个。 匹配 0个或者多个上述的模式。 匹配 1个或者多个上述模式。 匹配 0个或1个上述模式。 作为模式的最后一个字符匹配一行的结尾。 指出一个模式可能出现的次数。 例如: A{1,3} 表示 A 可能出现1次或3次。 用来转义元字符。同样用来覆盖字符在此表中定义的特殊意义,只取字符的本意。 否定。 表达式间的逻辑或。