devc++源文件未编译_编译工程附录:flex使用

原文在这里,仅部分翻译,以后有时间再补上。大部分是google翻译;感谢google。

https://westes.github.io/flex/manual/​westes.github.io

3 引言

flex是用于生成扫描器的工具。 扫描器是识别文本中的词汇模式的程序。 flex程序读取给定的输入文件,如果没有给出文件名,则读取其标准输入,该输入用于描述要生成的扫描程序。 描述采用成对的正则表达式和C代码(称为规则)的形式。 flex默认情况下会生成一个C源文件lex.yy.c作为输出,该文件定义了例程yylex()。 可以编译此文件并将其与Flex运行时库链接以生成可执行文件。 运行可执行文件时,它将分析其输入以查找与正则表达式的匹配。 只要找到一个,它就会执行相应的C代码。

4. 一些简单例子

首先通过一些简单的例子来了解怎么使用flex。下面的flex输入指定了一个扫描器,只要遇到字符串“username”就会用用户的登录名替换它:

%%
username    printf( "%s", getlogin() );

默认情况下,任何与flex扫描器不匹配的文本都将被复制到输出中,因此该扫描器的实际效果是将其输入文件随同“username”复制到其输出。在这个输入中只有一条规则。 “用户名”是模式(pattern),“printf”是行为(action)。 “%%”标志着规则的开始。

另一个简单例子:

int num_lines = 0, num_chars = 0;

%%
n      ++num_lines; ++num_chars;
.       ++num_chars;

%%
main()
        {
        yylex();
        printf( "# of lines = %d, # of chars = %dn",
                num_lines, num_chars );
        }

这个扫描器计算输入字符的数量和行数(除了计数的最终报告外,它不产生任何输出)。第一行声明了两个全局变量“num_lines”和“num_chars”,这两个全局变量既可以在yylex()内部访问,也可以在第二个“%%”之后声明的main()中访问。这里有两条规则:一条匹配换行符(“ n”)并增加行数和字符数,另一条匹配换行符以外的任何字符(用正则表达式“.”表示)。

一个复杂点的例子:

%{
#include<math.h>
%}

DIGIT	[0-9]
ID		[a-z][a-z0-9]*

%%
{DIGIT}+ {
	printf("An integer:%s (%d)n",yytext,atoi(yytext));
}

{DIGIT}+"."+{DIGIT}+ {
	printf("A float:%s (%g)n",yytext,atoi(yytext));
}

if|then|begin|procedure|function {
	printf("A keyword:%sn",yytext);
}

{ID} {	
	printf("An identifier:%sn",yytext);
}

"+"|"-"|"*"|"/" {
	printf("An operator:%sn",yytext);
}

"{"[^}n]*"}"	/*eat up one-line comments*/

[ tn]+	/*eat up white space
			  ATTENTION: HERE IS A SPACE!!!
			*/

. {
	printf("Unrecognized character: %sn",yytext);
}
%%

int main(int argc,char* argv[]){
	++argv, --argc;
	if(argc>0)
		yyin=fopen(argv[0],"r");
	else
		yyin=stdin;
	yylex();
}

这是一个类Pascal语言的简单扫描器,它识别不同类型的token并报告它所看到的内容。这个例子的细节将在下面的章节中解释。

5. 格式

flex输入文件由三部分组成,每个部分用%%分开:

definitions
%%
rules
%%
user code

5.1 定义部分的格式

定义(definations)部分包含简单名称定义的声明,以简化扫描器规范以及开始条件(start condition)的声明,开始条件将在后面的章节中解释。名称定义的形式如下:

name definition

“名称”是以字母或下划线('_')开头,后跟零个或多个字母、数字、'_'或' - '(短划线)的单词。该定义从名称后面的第一个非空白字符开始,并延续到行尾。随后可以使用“{name}”来引用该定义,该定义将扩展为“(definations)”。例如,

DIGIT    [0-9]
ID       [a-z][a-z0-9]*

将“DIGIT”定义为与单个数字相匹配的正则表达式,而“ID”则是一个字母后跟零个或多个字母或数字的正则表达式。另一个例子:

{DIGIT}+"."{DIGIT}*

([0-9])+"."([0-9])*

等价,匹配的是一个数字连接上小数点再连接上0到9中的任意个数字的字符串。

未缩进的注释(即,以“ / *”开头的行)将逐字复制到输出(lex.yy.c)中,直到下一个“ * /”。

缩进的文本或用'%{'和'%}'括起来的所有文本也会逐字复制到输出中(删除了%{和%}符号)。%{和%}符号本身必须在行上没有缩进。

%top块与'%{'...'%}'块相似,不同之处在于%top块中的代码在任何flex定义之前都重定位到所生成文件的顶部。 当要定义某些预处理器宏或在生成的代码之前包含某些文件时,此功能很有用。 单个字符“ {”和“}”用于分隔%top块,如以下示例所示:

%top{
        /* This code goes at the "top" of the generated file. */
        #include <stdint.h>
        #include <inttypes.h>
    }

允许多个%top块,并保留其顺序。

5.2 规则部分的格式

flex输入的rules部分包含一系列格式如下的规则:

pattern   action

其中,模式(pattern)必须是没有缩进的,并且要和行为(action)处在同一行。

可以参看下面模式部分,阅读更多关于模式和行为的知识。

在规则部分中,出现在第一条规则之前的任何缩进或%{%}括起来的文本都可以用于声明扫描例程本地的变量,以及(在声明之后)每次进入扫描例程时都将执行的代码 。 规则部分中的其他缩进或%{%}文本仍会复制到输出中,但是其含义不明确,很可能会导致编译时错误(此功能符合POSIX。请参见Lex和Posix, 其他此类功能)。

任何缩进的文本或用'%{'和'%}'括起来的文本都将原样复制到输出中(删除了%{和%}符号)。 %{和%}符号本身必须在行上没有缩进。

5.3用户代码段的格式

用户代码部分只需逐字复制到lex.yy.c。 它用于扫描程序调用或调用的伴随例程。 此部分的出现是可选的; 如果没有,也可以省略输入文件中的第二个“ %%”。

5.4 输入中的注释

Flex支持C样式的注释,也就是说,“ / *”和“ * /”之间的任何内容均被视为注释。每当flex遇到注释时,它将原样复制整个注释到生成的源代码中。注释可能出现在任何地方,但以下情况除外:

  • 当flex需要正则表达式的时候,注释就不会出现在“规则”部分中。这意味着注释可能不会出现在行的开头,也不会紧接在扫描器状态列表之后。
  • 注释可能不会出现在“定义”部分的“%选项”行上。

如果想遵循一条简单的规则,请始终在换行处添加注释,并在开头的“ / *”之前添加一个或多个空格字符。此规则将在输入文件中的任何地方起作用。

以下示例中的所有注释均有效:

%{
/ *代码块* /
%}

/ *定义部分* /
%x STATE_X

%%
    / *规则部分* /
ruleA / *正则表达式后* / {/ *代码块* /} / *代码块后* /
        / *规则部分(缩进)* /
<STATE_X> {
RuleC ECHO;
RuleD ECHO;
%{
/ *代码块* /
%}
}
%%
/ *用户代码部分* /

6 模式

输入中的模式使用一组扩展的正则表达式编写。具体如下:

'x'         匹配字符 'x' 
'.’        除换行符外的任何字符(字节)
'[xyz]’    字符类别;在这种情况下,模式匹配‘x’、'y'、'z'
'[abj-oZ]’ 具有范围的“字符类”;匹配“ a”,“ b”,“ j”到“ o”中的任何字母或“ Z”
'[^ A-Z]’  “否定字符类”,即该类中的字符以外的任何字符。在这种情况下,任何字符都不能包含大写字母。
'[^ A-Z n]’ 除大写字母或换行符外的任何字符
'[a-z] {-} [aeiou]’ 小写辅音
'r *’        零个或多个r,其中r是任何正则表达式
'r +’        一个或多个 r
'r?’        零或一个r(即“可选r”)
'r {2,5}’    从两到五​​个
'r {2,}’    两个或多个
'r {4}’      恰好4个
'{名称}'       扩展“名称”定义(请参阅上面的 格式 部分)。
'“ [xyz] ” foo“’ 文字字符串: [[xyz]” foo
'X'           如果X是'a','b','f','n','r','t'或'v',则ANSI-C解释为'x'。
               否则,为文字“ X”(用于转义诸如“ *”之类的运算符)
'0'          一个NUL字符(ASCII代码0)
'123’      八进制值123的字符
'x2a’      十六进制值为2a的字符
'(r)'      匹配“ r”;括号用于覆盖优先级(请参见下文)
'(?r-s:pattern)'      解释模式时,应用选项“ r”并忽略选项“ s”。
                          选项可以是零个或多个字符“ i”,“ s”或“ x”。
                         “ i”表示不区分大小写。 “ -i”表示区分大小写。
                         “ s”会更改“.”语法的含义,以匹配任何单个字节。 
                          “ -s”更改“.”的含义,以匹配“ n”以外的任何字节。
                          “ x”会忽略模式中的注释和空格。除非空格被反斜杠转义,
                            包含在""中或出现在字符类中,否则它将被忽略。
                            以下全部有效:
                           (?:foo)与(foo)相同
                           (?i:ab7)与([aA] [bB] 7)相同
                            (?-i:ab)与(ab)相同
                            (?s :.)与[ x00-  xFF]相同
                            (?-s :.)与[^  n]相同
                            (?ix-s:a.b)与([Aa] [^n] [bB])相同
                            (?x:a b)与(“ ab”)相同
                            (?x:a  b)与(“ a b”)相同
                            (?x:a"" b)与(“ a b”)相同
                            (?x:a [] b)与(“ a b”)相同
                             (?x:a
                           / *评论* /
                           b
                           c)与(abc)相同
'(?#评论)'      省略“()”中的所有内容。遇到的第一个')'字符结束了模式。
                    注释中不能包含')'字符。注释可能会跨越行。
'rs'                正则表达式“ r”后跟正则表达式“ s”;称为串联
'r|s'                “ r”或“ s”
'r/s’              一个“ r”,但前提是其后跟一个“ s”。确定此规则是否为最长匹配项时,
                    将包含用“ s”匹配的文本,但在执行操作之前,该文本将返回到输入。因此,
                   该操作只会看到与“ r”匹配的文本。这种模式称为尾随上下文。 (flex不能正确
                   匹配某些“ r / s”组合。有关危险的尾随上下文,请参见限制。)
'^r’              一个“ r”,但仅在一行的开头(即刚开始扫描时,或在扫描换行符之后)。
'r$’              一个“ r”,但只能在一行的末尾(即,在换行符之前)。等同于“ r /n”。
                   请注意,flex的“换行符”概念与C编译器用来将flex解释为“ n”的情况完全相同。
                   特别是,在某些DOS系统上,您必须自己过滤掉输入中的“ r”,
                   或显式地将“ r /r n”用作“ r $”。
'<s> r’             一个“ r”,但仅在起始条件s中(请参阅“起始条件”以了解起始条件)。
'<s1,s2,s3> r’  同上,但在任何启动条件s1,s2或s3中。
'<*> r’             任何开始条件下的“ r”,甚至是排他条件。
'<< EOF >>'         文件结束。
‘<s1,s2><<EOF>>’   在开始条件s1或s2中的文件结束

请注意,在字符类内部,除转义('')和字符类运算符'-',']]',以及在类开头的字符'^'以外,所有正则表达式运算符均失去其特殊含义。

上面列出的正则表达式按照优先级从高到低的顺序排列。分组在一起的优先级相同(请参见“ --posix” POSIX兼容选项的文档下有关重复运算符“ {}”优先级的特殊说明)。例如,

 foo | bar *         (foo)|(ba(r *))

是相同的。因为'*'运算符的优先级高于串联,并且串联的优先级高于交替('|')。因此,此模式匹配字符串“ foo”或字符串“ ba”,后跟零个或多个“ r”。要匹配字符串“ bar”的“ foo”或零个或多个重复,请使用:

    foo|(bar)*

要匹配零个或多个重复的“ foo”和“ bar”序列:

     (foo|bar)*

除了字符和字符范围外,字符类还可以包含字符类表达式。这些是括在“ [:”和“:]”定界符内的表达式(它们本身必须出现在字符类的“ [”和“]”之间。其他元素也可能出现在字符类内)。有效的表达式是:

    [:alnum:] [:alpha:] [:blank:]
    [:cntrl:] [:digit:] [:graph:]
    [:lower:] [:print:] [:punct:]
    [:space:] [:upper:] [:xdigit:]

这些表达式都指定了与相应的标准C isXXX函数等效的一组字符。例如,“ [:: alnum:]”指定isalnum()返回true的那些字符,即任何字母或数字字符。某些系统不提供isblank(),因此flex将“ [:blank:]”定义为空白或制表符。

例如,以下字符类都是等效的:

  [[:alnum:]]
    [[:alpha:][:digit:]]
    [[:alpha:][0-9]]
    [a-zA-Z0-9]

请注意。在flex输入中看到时,字符类会立即展开。这意味着字符类对在其中执行flex的语言环境敏感,并且生成的扫描程序对运行时语言环境不敏感。这可能是不希望的。

  • 如果您的扫描器不区分大小写(“ -i”标志),则“ [:: upper:]”和“ [:lower:]”等同于“ [:alpha:]”。
  • 如果范围跨越大写或小写字符,则在不区分大小写的扫描器中应谨慎使用带有范围的字符类,例如“ [a-Z]”。 Flex不知道您是否要折叠所有大写和小写字符,或者是否要指定文字数字范围(不区分大小写)。如有疑问,flex会假设您的意思是文字数字范围,并会发出警告。此规则的例外情况是字符范围,例如“ [a-z]”或“ [S-W]”,很明显,您希望将其折叠起来。以下是启用了“ -i”标志的一些示例:

d1bf5e56daa041b6ed087dfc0f9a88e7.png
  • 否定的字符类(例如上面的示例‘[^A-Z]’)将匹配换行符,除非“ n”(或等效的转义序列)是在否定的字符类中明确存在的字符之一(例如,‘[^A-Zn]’)。这与其他许多正则表达式工具处理否定字符类不同,但是不幸的是,这种矛盾在历史上一直根深蒂固。匹配换行符意味着‘[^"]*’之类的模式可以匹配整个输入,除非输入中没有其他引号。

Flex可以通过在POSIX字符类名称前添加“ ^”来否定字符类表达式。

   [:^alnum:] [:^alpha:] [:^blank:]
    [:^cntrl:] [:^digit:] [:^graph:]
    [:^lower:] [:^print:] [:^punct:]
    [:^space:] [:^upper:] [:^xdigit:]

如果表达式“ [:^ upper:]”和“ [:^ lower:]”出现在不区分大小写的扫描程序中,由于其含义不清楚,Flex会发出警告。当前的行为是完全跳过它们,但是在将来的flex版本中,这可能会更改,恕不另行通知。

  • “ {-}”运算符计算两个字符类的差。例如,‘[a-c]{-}[b-z]’代表类‘[a-c]’中所有不在 ‘[b-z]’类中的字符(在这种情况下,只是单个字符‘a’)。 '{-}'运算符保留关联性,因此‘[abc]{-}[b]{-}[c]’与'[a]'相同。注意不要意外创建一个永远不会匹配的空集。
  • “ {+}”运算符计算两个字符类的并集。例如,‘[a-z]{+}[0-9]’与‘[a-z0-9]’相同。当运算符后面有差分运算的结果时,此运算符很有用,例如在“ C”语言环境中‘[[:alpha:]]{-}[[:lower:]]{+}[q]’,它等效于‘[A-Zq]’。
  • 一条规则最多可以包含一个尾随上下文实例(“ /”运算符或“ $”运算符)。起始条件“ ^”和“ << EOF >>”模式只能出现在模式的开头,并且不能与“ /”和“ $”一起放在括号内。规则开头没有出现的“ ^”或规则结尾没有出现的“ $”将失去其特殊属性,并被视为普通字符。
  • 以下是无效的:
    foo/bar$
    <sc1>foo<sc2>bar

请注意,其中第一个可以写为‘foo/barn’。

  • 以下内容将导致“ $”或“ ^”被视为普通字符:
    foo |(bar $)
    foo | ^ bar

如果所需的含义是“ foo”或“ bar”后跟换行符,则可以使用以下内容(特殊的|操作在下面说明,请参见“操作”):

    foo |
    bar $ / *动作在这里* /

在行的开头匹配“ foo”或“ bar”也可以使用类似的技巧。

7. 输入如何匹配

运行生成的扫描程序时,它将分析其输入以查找与任何模式匹配的字符串。如果找到多个匹配项,则采用匹配最多文本的匹配项(对于尾随上下文规则,这包括尾随部分的长度,即使随后将其返回给输入)。如果找到两个或更多相同长度的匹配项,则选择在flex输入文件中最先列出的规则。

一旦确定匹配,就在全局字符指针 yytext 中提供与该匹配相对应的文本(称为token),并在全局整数yyleng中提供其长度。然后执行与匹配的模式相对应的动作(请参阅动作),然后扫描其余输入以查找另一个匹配项。

如果未找到匹配项,则执行默认规则:输入中的下一个字符被视为匹配并复制到标准输出中。因此,最简单的有效flex输入是:

    %%

生成一个扫描器,该扫描器仅将其输入(一次输入一个字符)复制到其输出。

请注意,可以用两种不同的方式定义 yytext:作为字符指针或字符数组。您可以通过在flex输入的第一(定义)部分中包括特殊指令 %pointer 或 %array 中的一个来控制flex使用哪个定义。除非使用'-l'lex兼容性选项,在这种情况下 yytext 将是一个数组,否则默认值为%pointer。使用%pointer的优点是,在匹配非常大的标记时(除非您用尽了动态内存),扫描速度明显更快,并且没有缓冲区溢出。缺点是修改yytext时会受到限制(请参见Actions),并且对unput()函数的调用会破坏yytext的当前内容,这在不同lex版本之间移动时会造成很大的移植麻烦。

%array的优点是您可以随后将yytext修改为您的心脏内容,并且对unput()的调用不会破坏yytext(请参见操作)。此外,现有的lex程序有时会使用以下形式的声明从外部访问yytext:

    extern char yytext [];

与%pointer一起使用时,此定义是错误的,但对于%array是正确的。

%array声明将yytext定义为YYLMAX字符数组,其默认值为相当大的值。您只需在弹性输入的第一部分中将YYLMAX定义为另一个值即可更改大小。如上所述,使用%pointer,yytext会动态增长以容纳较大的token。尽管这意味着您的%pointer扫描程序可以容纳非常大的token(例如匹配整个注释块),但请记住,每次扫描程序必须调整yytext的大小时,它还必须从头开始重新扫描整个token,因此可以证明匹配此类token比较慢。如果对unput()的调用导致太多文本被推回,则yytext目前不会动态增长。相反,会导致运行时错误。

另请注意,不能将%array与C ++扫描程序类一起使用(请参阅Cxx)。

8 动作

规则中的每个模式都有一个对应的动作,该动作可以是任意的C语句。模式在第一个非转义的空白字符处结束;该行的其余部分是它的动作。如果操作为空,则当匹配模式时,将简单地丢弃输入令牌。例如,以下是程序的规范,该规范从其输入中删除所有出现的“ zap me”:

  %%
  "zap me"

本示例将输入中的所有其他字符复制到输出中,因为它们将与默认规则匹配。

这是一个程序,它将多个空格和制表符压缩为单个空格,并丢弃在行尾找到的空格:

    %%
    [ t]+        putchar( ' ' );
    [ t]+$       /* ignore this token */

如果操作包含“ {”,则该操作将持续进行到找到配对的“}”为止,并且该操作可能会跨越多行。flex知道C字符串和注释,不会被它们中的花括号所欺骗,但是还允许动作以'%{'开头,并且将动作视为下一个'%}'之前的所有文本(不管本动作中的其他括号)。

仅包含竖线('|')的动作表示“与下一条规则的动作相同”。

动作可以包括任意C代码,包括用于将值返回到称为yylex()的例程的return语句。每次调用yylex()时,它将从上次中断的地方继续处理token,直到到达文件末尾或执行返回。

可以自由地修改 yytext,除了将其加长外(在其末尾添加字符-这些字符将覆盖输入流中的后续字符)。但是,这在使用%array时不适用(请参见匹配)。在这种情况下,可以任何方式自由修改yytext。

动作可以自由地修改yyleng,除非如果动作还包括yymore()的使用,则不应这样做(请参见下文)。

一个动作中可以包含许多特殊的指令:

Echo 将yytext复制到扫描器的输出。

BEGIN 紧随其后的开始条件的名称将扫描器置于相应的开始条件下(见下文)。

REJECT 指示扫描程序继续执行与输入(或输入的前缀)匹配的“第二个”规则。如上文“匹配”中所述选择规则,并适当设置yytext和yyleng。它可以是与最初选择的规则匹配的文本数量最多但后来出现在flex输入文件中的文本,也可以是与较少的文本匹配的文本。例如,当出现“ frob”时,以下代码将对输入中的单词进行计数,并调用例程special():

     int word_count = 0;
    %%
    frob        special(); REJECT;
    [^ tn]+   ++word_count;

如果没有REJECT,则输入中出现的任何“ frob”都不会被视为单词,因为扫描程序通常每个令牌仅执行一个操作。允许多次使用REJECT,每次都可以找到当前活动规则的下一个最佳选择。例如,当以下扫描程序扫描token“ abcd”时,它将在输出中写入“ abcdabcaba”:

    %%
    a        |
    ab       |
    abc      |
    abcd     ECHO; REJECT;
    .|n     /* eat up any unmatched character */

前三个规则使用特殊的“ |”操作,因此它们共享第四个操作。

就扫描器性能而言,REJECT是一项特别昂贵的功能。如果将其用于扫描器的任何操作,则将减慢所有扫描器的匹配速度。此外,REJECT不能与“ -Cf”或“ -CF”选项一起使用(请参阅扫描器选项)。

还要注意,与其他特殊操作不同,REJECT是一个分支。操作中紧随其后的代码将不会执行。

yymore()

告诉扫描程序下次匹配规则时,应将相应的令牌附加到yytext的当前值上,而不是替换它。例如,在输入为“ mega-kludge”的情况下,以下内容将在输出中写入“ mega-mega-kludge”:

    %%
    mega-    ECHO; yymore();
    kludge   ECHO;

第一个'mega-'被匹配并回显到输出。然后匹配了'kludge',但是前一个'mega-'仍然在yytext的开头徘徊,因此针对'kludge'规则的ECHO实际上会写成'mega-kludge'。

关于yymore()的两个注意事项。首先,yymore()取决于yyleng的值是否正确反映了当前令牌的大小,因此,如果您使用yymore(),则不得修改yyleng。其次,扫描器动作中存在yymore()会导致扫描器匹配速度受到轻微的性能损失。

yyless(n)将当前令牌的除前n个字符外的所有字符返回给输入流,在此位置,当扫描程序寻找下一个匹配项时,将重新扫描它们。 yytext和yyleng已适当调整(例如yyleng现在等于n)。例如,在输入“ foobar”上,以下内容将写出“ foobarbar”:

    %%
    foobar    ECHO; yyless(3);
    [a-z]+    ECHO;

yyless()的参数0将导致再次扫描整个当前输入字符串。 除非您更改了扫描程序随后处理其输入的方式(例如,使用BEGIN),否则将导致无限循环。

注意yyless()是一个宏,只能在flex输入文件中使用,不能在其他源文件中使用。

unput(c)将字符c放回输入流。 这将是下一个扫描的字符。 以下操作将采用当前令牌,并导致对其进行重新扫描,并用括号括起来。

{
    int i;
    /* Copy yytext because unput() trashes yytext */
    char *yycopy = strdup( yytext );
    unput( ')' );
    for ( i = yyleng - 1; i >= 0; --i )
        unput( yycopy[i] );
    unput( '(' );
    free( yycopy );
    }

请注意,由于每个unput()都会将给定字符放回输入流的开头,因此必须从头到尾完成字符串的回退。

使用unput()时一个潜在的重要问题是,如果使用%pointer(默认设置),则对unput()的调用会破坏yytext的内容,从其最右边的字符开始,并在每次调用时向左吞噬一个字符。 如果需要在调用unput()之后保留的yytext值(如上例所示),则必须先将其复制到其他位置,或者改为使用%array构建扫描器(请参见匹配)。

最后,请注意,您不能放回“ EOF”来尝试用文件结尾标记输入流。

input()从输入流中读取下一个字符。 例如,以下是吃掉C注释的一种方法:

 %%
    "/*"        {
                int c;

                for ( ; ; )
                    {
                    while ( (c = input()) != '*' &&
                            c != EOF )
                        ;    /* eat up text of comment */

                    if ( c == '*' )
                        {
                        while ( (c = input()) == '*' )
                            ;
                        if ( c == '/' )
                            break;    /* found the end */
                        }

                    if ( c == EOF )
                        {
                        error( "EOF in comment" );
                        break;
                        }
                    }
                }

(请注意,如果使用C ++编译扫描程序,则将input()称为yyinput(),以避免输入名称与C ++流发生名称冲突。)

YY_FLUSH_BUFFER; 刷新扫描程序的内部缓冲区,以便下次扫描程序尝试匹配令牌时,它将首先使用YY_INPUT()重新填充缓冲区(请参阅生成的扫描程序)。 此操作是更通用的yy_flush_buffer的特例; 功能,如下所述(请参见多输入缓冲区)

yyterminate()可以代替动作中的return语句。 它终止扫描器,并向扫描器的调用者返回0,表示“已完成”。 默认情况下,遇到文件结尾时也会调用yyterminate()。 它是一个宏,可以重新定义。

9 生成的扫描器

flex的输出是文件lex.yy.c,它包含扫描例程yylex(),它用于匹配令牌的许多表以及许多辅助例程和宏。默认情况下,yylex()声明如下:

    int yylex()
        {
        ...这里的各种定义和动作...
        }

(如果您的环境支持函数原型,则它将为int yylex(void。)。可以通过定义YY_DECL宏来更改此定义。例如,您可以使用:

     #define YY_DECL float lexscan( a, b ) float a, b;

为扫描例程命名为lexscan,返回一个float,并将两个float作为参数。请注意,如果使用K&R样式/非原型函数声明为扫描例程提供参数,则必须使用分号(;)终止定义。

flex默认情况下会生成“ C99”函数定义。 Flex过去曾经能够生成过时的,“传统”的函数定义。这是为了在旧系统上支持引导gcc。不幸的是,传统的定义使我们无法使用任何小于int的标准数据类型(例如short,char或bool)作为函数参数。此外,传统的定义支持在骨架文件中增加了额外的复杂性。因此,当前版本的flex仅生成标准的C99代码,而K&R风格的功能留给了历史学家。

每当调用yylex()时,它都会从全局输入文件yyin(默认为stdin)中扫描token。它一直持续到到达文件结尾(此时它返回值0)或它的其中一个动作执行return语句为止。

如果扫描程序到达文件末尾,则除非yyin指向新的输入文件(在这种情况下,扫描将从该文件继续进行),否则将不定义后续调用,否则将调用yyrestart()。 yyrestart()接受一个参数,即FILE *指针(如果已将YY_INPUT设置为从yyin以外的其他源进行扫描,则可以为NULL),并初始化yyin以从该文件进行扫描。本质上,仅将yyin分配给新的输入文件还是使用yyrestart()这样做没有什么区别;后者可用于与早期版本的flex兼容,因为它可用于在扫描过程中切换输入文件。通过使用yyin参数调用它,也可以用来丢弃当前的输入缓冲区。但最好使用YY_FLUSH_BUFFER(请参阅操作)。请注意,yyrestart()不会将启动条件重置为INITIAL(请参见启动条件)。

如果yylex()由于在其中一项操作中执行了return语句而停止扫描,则扫描器可能会再次被调用,它将从中断处继续扫描。

默认情况下(出于效率考虑),扫描程序使用块读取而不是简单的getc()调用从yyin读取字符。可以通过定义YY_INPUT宏来控制如何获取输入。 YY_INPUT()的调用顺序为YY_INPUT(buf,result,max_size)。它的作用是在字符数组buf中最多放置max_size个字符,并在整数变量结果中返回读取的字符数或常量YY_NULL(在Unix系统上为0),以指示“ EOF”。默认YY_INPUT从全局文件指针yyin读取。

这是YY_INPUT的示例定义(在输入文件的“定义”部分中):

%{
    #define YY_INPUT(buf,result,max_size) 
        { 
        int c = getchar(); 
        result = (c == EOF) ? YY_NULL : (buf[0] = c, 1); 
        }
    %}

此定义会将输入处理更改为一次出现一个字符。

当扫描程序从YY_INPUT接收到文件结束指示时,它将检查yywrap()函数。如果yywrap()返回false(零),则假定函数已经执行并设置yyin指向另一个输入文件,然后继续扫描。如果返回true(非零),则扫描程序终止,并向其调用方返回0。请注意,无论哪种情况,启动条件均保持不变。它不会恢复为INITIAL。

如果您不提供自己的yywrap()版本,则必须使用%option noyywrap(在这种情况下扫描程序的行为就像yywrap()返回1),或者必须与'-lfl'链接以获得默认值例程的版本,始终返回1。

要从内存缓冲区中扫描(例如,扫描字符串),请参阅扫描字符串。请参阅多个输入缓冲区。

扫描程序将其ECHO输出写入yyout全局(默认值,stdout),用户只需将其分配给其他FILE指针即可重新定义。

10. 初始条件

flex为有条件地激活规则提供了一种机制。 只有当扫描器处于名为“sc”的开始条件时,其模式前缀为“<sc>”的任何规则才会处于激活状态。 例如,

<STRING>[^"]*        { /* eat up the string body ... */
            ...
            }

只有当扫描仪处于“STRING”开始状态时才会激活,又比如

<INITIAL,STRING,QUOTE> . {/ *handle an escape... * /
            ...
            }

只有在当前启动条件是“INITIAL”,“STRING”或“QUOTE”时才会激活。

开始条件在输入的定义(第一个)部分中以'%s'或'%x'开头,后跟一个名称列表的不带缩进的行来声明。 前者声明的是包含性的(inclusive)开始条件,后者是独占性的(exclusive)开始条件。 开始条件使用BEGIN操作激活。 在执行下一个BEGIN操作之前,具有给定开始条件的规则将处于激活状态,而具有其他开始条件的规则将处于非激活状态。 如果起始条件是包含的(inclusive),那么根本没有开始条件的规则也将被激活。 如果它是独占的(exclusive),则只有符合启动条件的规则才会生效。 根据相同独占开始条件的一组规则描述了独立于flex输入中的任何其他规则的扫描器。 正因为如此,排他的开始条件可以很容易地指定扫描输入中与其余部分在语法上不同的部分(例如注释)的“小型扫描器”。

如果包容性和排他性启动条件之间的区别仍然有点模糊,以下是一个简单的例子,说明两者之间的联系。 规则集:

%s example
%%

<example>foo   do_something();
bar            something_else();

等价于:

%x example
%%

<example>foo   do_something();
<INITIAL,example>bar    something_else();

如果没有`<INITIAL,example>'限定符,则第二个示例中的“bar”模式在启动条件“example”时不会处于活动状态(即,不匹配)。 但是,如果我们只是使用`<example>'来限定`bar',那么它只会在`example'中而不是在INITIAL中有效,而在第一个例子中它在两个中都是有效的,因为在第一个例子中, 开始条件是包容性(`%s')开始条件。

另请注意,特殊的开始条件说明符“<*>”匹配每个开始条件。 因此,上面的例子也可以写成:

%x example
%%

<example>foo   do_something();
<*>bar    something_else();

默认规则('ECHO'任何不匹配的字符)在启动条件下保持有效。 它相当于:

<*>.|n     ECHO;

`BEGIN(0)'返回到只有没有启动条件的规则处于活动状态的原始状态。 这个状态也可以被称为开始条件“INITIAL”,所以`BEGIN(INITIAL)'等同于'BEGIN(0)'。 (开始条件名称周围的圆括号不是必需的,但被认为是很好的风格。)

BEGIN操作也可以在规则部分的开头以缩进代码形式给出。 例如,如果调用“yylex()”且全局变量enter_special为true,则以下操作将导致扫描器输入“SPECIAL”开始条件:

int enter_special;

%x SPECIAL
%%
        if ( enter_special )
            BEGIN(SPECIAL);

<SPECIAL>blahblahblah
...more rules follow...

为了说明启动条件的用法,下面是一个扫描器,它提供了两种不同的字符串解释,例如“123.456”。 默认情况下,它会将它视为三个标识(token),整数“123”,点('。')和整数“456”。 但是如果字符串在字符串“expect-floats”的前面出现,它会将其视为单个标记,即浮点数123.456:

%{
#include <math.h>
%}
%s expect

%%
expect-floats        BEGIN(expect);

<expect>[0-9]+"."[0-9]+      {
            printf( "found a float, = %fn",
                    atof( yytext ) );
            }
<expect>n           {
            /* that's the end of the line, so
             * we need another "expect-number"
             * before we'll recognize any more
             * numbers
             */
            BEGIN(INITIAL);
            }

[0-9]+      {

            printf( "found an integer, = %dn",
                    atoi( yytext ) );
            }

"."         printf( "found a dotn" );

【这里有点难理解;所以验证一下。】

sc.l全文如下:

%{
#include <math.h>
%}
%s expect

%%
expect-floats        BEGIN(expect);

<expect>[0-9]+"."[0-9]+      {
            printf( "found a float, = %fnn",
                    atof( yytext ) );
            }
<expect>n           {
            /* that's the end of the line, so
             * we need another "expect-number"
             * before we'll recognize any more
             * numbers
             */
            BEGIN(NITIAL);
            }

[0-9]+      {

            printf( "found an integer, = %dn",
                    atoi( yytext ) );
            }

"."         printf( "found a dotn" );

%%

int main(int argc, char* argv[]) {
    yylex();
    return 0;
}
int yywrap() { 
    return 1;
}

运行结果如下:

afc3c22127db580b10e30a8ea99bc2d2.png

下面是一个扫描器,可以识别(并丢弃)C注释,同时保持当前输入行的计数值:

%x comment
%%
        int line_num = 1;

"/*"         BEGIN(comment);

<comment>[^*n]*        /* eat anything that's not a '*' */
<comment>"*"+[^*/n]*   /* eat up '*'s not followed by '/'s */
<comment>n             ++line_num;
<comment>"*"+"/"        BEGIN(INITIAL);

comment1.l

%{
int line_num = 1;
%}

%x comment

%%

"/*"         BEGIN(comment);

<comment>[^*n]*        /* eat anything that's not a '*' */
<comment>"*"+[^*/n]*   /* eat up '*'s not followed by '/'s */
<comment>n             ++line_num;
<comment>"*"+"/"        BEGIN(INITIAL);

n      ++line_num, yymore();

%%

int main(int argc, char* argv[]) {
    yylex();
    printf("nThere are %d lines.n",line_num);
    return 0;
}
int yywrap() {
    return 1;
}

该代码运行如下:

3d95ebab35fd70beab5a522aa888474e.png

为了尽可能多地与每个规则匹配文本这个扫描器会遇到一些麻烦。 一般来说,当试图编写高速扫描器时,尽量在每条规则上尽可能匹配,因为这是一个巨大的胜利。

请注意,开始条件名称实际上是整数值,可以像整数一样存储。 因此,上述程序可以按以下方式进行扩展:

%x comment foo
%%
        int line_num = 1;
        int comment_caller;

"/*"         {
             comment_caller = INITIAL;
             BEGIN(comment);
             }

...

<foo>"/*"    {
             comment_caller = foo;
             BEGIN(comment);
             }

<comment>[^*n]*        /* eat anything that's not a '*' */
<comment>"*"+[^*/n]*   /* eat up '*'s not followed by '/'s */
<comment>n             ++line_num;
<comment>"*"+"/"        BEGIN(comment_caller);

此外,您可以使用整数值YY_START宏访问当前的启动条件。 例如,上述的comment_caller分配可以被写入:

comment_caller = YY_START;

Flex提供YYSTATE作为YY_START的别名(因为这是AT&T lex所使用的)。

请注意,开始条件没有自己的名称空间; %s和%x的声明方式与#define相同。

最后,下面是如何使用独占开始条件匹配C风格代"号(quoted)字符串的示例,其中包括扩展转义序列(但不包括检查字符串是否太长):

%x str

%%
        char string_buf[MAX_STR_CONST];
        char *string_buf_ptr;

"      string_buf_ptr = string_buf; BEGIN(str);

<str>"        { /* saw closing quote - all done */
        BEGIN(INITIAL);
        *string_buf_ptr = '0';
        /* return string constant token type and
         * value to parser
         */
        }

<str>n        {
        /* error - unterminated string constant */
        /* generate error message */
        }

<str>[0-7]{1,3} {
        /* octal escape sequence */
        int result;

        (void) sscanf( yytext + 1, "%o", &result );

        if ( result > 0xff )
                /* error, constant is out-of-bounds */

        *string_buf_ptr++ = result;
        }

<str>[0-9]+ {
        /* generate error - bad escape sequence; something
         * like '48' or '0777777'
         */
        }

<str>n  *string_buf_ptr++ = 'n';
<str>t  *string_buf_ptr++ = 't';
<str>r  *string_buf_ptr++ = 'r';
<str>b  *string_buf_ptr++ = 'b';
<str>f  *string_buf_ptr++ = 'f';

<str>(.|n)  *string_buf_ptr++ = yytext[1];

<str>[^n"]+        {
        char *yptr = yytext;

        while ( *yptr )
                *string_buf_ptr++ = *yptr++;
        }

通常,比如在上面的一些例子中,你写了一大堆规则,所有的规则之前都有相同的开始条件。 通过引入启动条件范围的概念,Flex使这一点变得更简单和更清晰。 开始条件范围开始于:

<SCs>{

其中SC是一个或多个起始条件的列表。 在起始条件范围内,每个规则都会自动应用前缀'<SCs>',直到与最初的'{'匹配的'}'为止。 所以,例如,

<ESC>{
    "n"   return 'n';
    "r"   return 'r';
    "f"   return 'f';
    "0"   return '0';
}

等价于:

<ESC>"n"  return 'n';
<ESC>"r"  return 'r';
<ESC>"f"  return 'f';
<ESC>"0"  return '0';

开始条件范围可以嵌套。

有三个例程可用于操作堆栈的启动条件:

`void yy_push_state(int new_state)'

将当前的开始条件推到开始条件堆栈的顶部,并切换到new_state,就像您使用过`BEGIN new_state'(回想起始条件名称也是整数)一样。

`void yy_pop_state()'

弹出堆栈顶部并通过BEGIN切换到它。

`int yy_top_state()'

返回堆栈的顶部而不更改堆栈的内容。

开始条件堆栈动态增长,因此没有内置的大小限制。 如果内存耗尽,程序执行将中止。


以下是使用“.”的一些技巧:

  • 一个常见的错误是将分组括号放在运算符之后,当您确实想将括号放在运算符之前时,例如,您可能希望此(foo | bar)+而不是this(foo | bar +)。
  • 第一个模式与单词“ foo”或“ bar”匹配多次,例如,与文本“ barfoofoobarfoo”匹配。 第二种模式匹配单个foo实例或单个bar实例,后跟一个或多个“ r”,例如,它匹配文本barrrr。
  • “ []”内的“.”仅表示文字“.”(句号),而不是“除换行符外的任何字符”。
  • 请记住,“.”匹配除“ n”(和“ EOF”)以外的任何字符。 如果您确实要匹配任何字符(包括换行符),请使用(.| n)。请注意,正则表达式(.| n)+将与您的整个输入匹配!
  • 最后,如果要匹配文字“.”(句点),请使用“ [.]”或‘“.”’

本节介绍将flex与GNU bison集成时有用的flex功能。如果您的扫描器不使用bison,请跳过本节。在这里,我们仅讨论flex和bison中关于的flex的内容。我们不会详细讨论bison。有关生成bison解析器的更多信息,请参见《 GNU Bison手册》。

通过从命令行调用flex时声明“%option bison-bridge”或提供“ --bison-bridge”,可以生成兼容的bison扫描器。这指示flex可以使用宏yylval。 yylval的数据类型YYSTYPE通常在flex输入文件的第1节中包含的头文件中定义。有关可用功能和宏的列表,请参见bison-functions。

yylex的声明变为

  int yylex ( YYSTYPE * lvalp, yyscan_t scanner );

如果指定了%option bison-locations,则声明变为:

 int yylex ( YYSTYPE * lvalp, YYLTYPE * llocp, yyscan_t scanner );

请注意,宏yylval和yylloc的结果为指针。对yylloc的支持在bison中是可选的,因此在flex中也是可选的。以下是与bison兼容的Flex扫描仪的示例。

   /* Scanner for "C" assignment statements... sort of. */
    %{
    #include "y.tab.h"  /* Generated by bison. */
    %}

    %option bison-bridge bison-locations
    %

    [[:digit:]]+  { yylval->num = atoi(yytext);   return NUMBER;}
    [[:alnum:]]+  { yylval->str = strdup(yytext); return STRING;}
    "="|";"       { return yytext[0];}
    .  {}
    %

如您所见,这里真的没有魔术。我们就像其他变量一样使用yylval。 yylval的数据类型是由bison生成的,并包含在文件y.tab.h中。这是相应的bison解析器:

/* Parser to convert "C" assignments to lisp. */
    %{
    /* Pass the argument to yyparse through to yylex. */
    #define YYPARSE_PARAM scanner
    #define YYLEX_PARAM   scanner
    %}
    %locations
    %pure_parser
    %union {
        int num;
        char* str;
    }
    %token <str> STRING
    %token <num> NUMBER
    %%
    assignment:
        STRING '=' NUMBER ';' {
            printf( "(setf %s %d)", $1, $3 );
       }
    ;

参考:

https://blog.csdn.net/lishichengyan/article/details/79512373​blog.csdn.net
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值