A. 一道《编译原理》求follow集题目,在线等答案
哥们,你这个问题中的一个产生式E’→+TE’| e,应该是E->+TE’ |ε这样吧!否则不可能获得如此结果。
关于求follow集合,龙书中说得很清楚,依据三条规则即可:
1、任何FOLLOW(S)都包含输入终止符号,其中S是开始符号。
适用该条,因此FOLLOW(E’)中包含终止符号#。
2、如果存在产生式,A->αBβ,则将FIRST(β)中除ε以外的符号都放入FOLLOW(B)中。
该条不适用,因为在上述所有产生式中不存在形如E‘->αE’β这样的产生式。
3、如果存在产生式,A->αB,或A->αBβ,其中FIRST(β)中包含ε,则将FOLLOW(A)中的所有符号都放入FOLLOW(B)中。
适用该条,因为存在这样的产生式E->+TE’,使得FOLLOW(E’)=FOLLOW(E)成立。而FOLLOW(E)适用上述第二条,根据产生式F→(E)可求得为FOLLOW(E)={#,)}。
综上,FOLLOW(E’)=FOLLOW(E)={#,)}。
B. 怎么学习ollydbg原理是什么基本使用方法是什么
最简单的方法是启动OllyDbg,点击File|Open,然后选择你想调试的程序。程序需要命令行参数输入对话框下方的文本栏。
重新开始调试最后一个程序的快捷键是Ctrl+F2,并且OllyDbg使用相同的参数。
你也可以点选历史记录。把程序拖入OllyDbg也可以开始调试。
当然,当启动OllyDbg时,你在命令行中也能指定被调试的程序名和参数。比如:
你可以创建桌面快捷方式指向OllyDbg,选择属性,到快捷方式,把程序名加入目
标栏。每次你双击这个快捷图标,OllyDbg自动装载被调试程序。
你可以attach OllyDbg到某个正在运行的进程。点击File|Attach,从列表中选择
该进程。注意:当你关闭OllyDbg,这个进程也会终止。不要试图attach系统进程
,这很可能使系统完全死机。(事实上,大多数情况下,OS不允许attach敏感进
程)
OllyDbg能作为just-in-time debugger。这需要在注册表中登记。点击
Options|Just-in-time debugging,在对话框中按“Make OllyDbg just-in-time
debugger”。现在,当一些程序崩溃,你会被询问是否调试它。这样,操作系统
可以启动OllyDbg并直接停在异常发生处。如果你选择attaching without
confirmation,OllyDbg不会询问直接启动。要恢复原来的just-in-time
debugger,在出现的对话框中按相应的按纽就行了。
其它方法还有,把OllyDbg加入可执行文件的弹出式菜单。点击Options|Add to
Explorer,按“Add OllyDbg to menu in Windows Explorer”。以后你可以右击
可执行文件点选OllyDbg。这个选项创建注册表键
HKEY_CLASSES_ROOT/exefile/shell/Open with OllyDbg and
HKEY_CLASSES_ROOT/exefile/shell/Open with OllyDbg/command
OllyDbg可以调试控制台(基于文本)程序。
注意:WindowsNT或Windows2000下,你必须有管理员权限
OllyDbg的help-通用快捷键(翻译)
Golbal shortcuts(通用快捷键)
这些快捷键在OllyDbg中通用,不依赖于当前活动窗口。
Ctrl+F2---OllyDbg重置,重新开始调试。如果没有活动程序,OllyDbg装入历史列表中的第一个程序。OllyDbg重置会释放内存移除硬断点。
Alt+F2--关闭被调试的程序。如果程序还在活动状态,你会被询问是否执行该操作。
F3--显示“Open 32-bit .exe file”对话框,这里可以选择可执行文件和指定参数
Alt+F5--使OllyDbg显示在屏幕最前方。如果被调试程序中断时显示窗口遮住OllyDbg的一些区域,不继续运行又不能移动或最小化它。激活OllyDbg按Alt+F5可以解决。如果你再次按Alt+F5,OllyDbg会恢复为普通窗口。OllyDbg的当前状态显示在状态栏。
F7--step into,执行下一条简单命令。如果该命令是一个函数调用,进入调用函数内部。如果命令有REP前缀,执行该命令的一步操作。
Shift+F7--与F7相同,除了当被调试程序遇到某些异常时,首先尝试调用程序自己的异常处理过程。
Ctrl+F7--animate into,一步一步执行程序,也进入函数调用(就象你一直按着F7,不过更快)。当你执行一些单步或继续命令,程序到达有效断点,一些异常发生时,Animation终止。每步执行,OllyDbg重画所有窗口。要加速animation,关闭所有窗口而不要只改变现有窗口的大小。终止animation,也可按Esc。
F8-step over,执行下一条简单命令。如果该命令是一个函数调用,立刻执行完该函数(除非函数内部有断点或产生异常)如果命令有REP前缀,执行所有重复操作,并停于下一条命令。
Shift+F8--与F8相同,除了当被调试程序遇到某些异常时,首先尝试调用程序自己的异常处理过程。
Ctrl+F8--animate over,一步一步执行程序,但不进入函数调用(就象你一直按着F8,不过更快)。当你执行一些单步或继续命令,程序Animation到达有效断点或异常发生时,Animation终止。每步执行,OllyDbg重画所有窗口。要加速animation,关闭所有窗口而不要只改变现有窗口的大小。终止animation也可按Esc。
F9--继续执行程序。
Shift+F9--与F9相同。除了当被调试程序遇到某些异常时,首先尝试调用程序自己的异常处理过程。
Ctrl+F11--run trace into,一步一步执行程序,要进入函数调用,记录寄存器内容。Run trace 不重绘CPU窗口.
F12-悬挂所有线程以停止程序执行。最好用继续键和菜单命令(像F9)恢复执行线程,而不要用其它手动方法恢复。
Ctrl+F12--run trace over,一步一步执行程序,不进入函数调用,记录寄存器内容。Run trace不重绘CPU窗口。
ESC--如果animation或跟踪正在进行,将被终止。如果CPU窗口显示跟踪时的数据,此时转为显示实际数据。
Alt+B--打开或恢复Breakpoint窗口。这里可以编辑,删除和观察断点。
Alt+C--打开或恢复CPU窗口。
Alt+E--打开或恢复模块列表
Alt+E--打开或恢复Call stack窗口。
Alt+L--打开或恢复Log窗口。
Alt+M--打开或恢复Memory窗口。
Alt+O--打开Option对话框
Ctrl+P--打开Patch窗口。
Ctrl+T--打开Pause run trace对话框
Alt+X-中断OllyDbg。
多数窗口可以使用下列快捷键:
Alt+F3--关闭活动窗口。
Ctrl+F4--关闭活动窗口。
F5--最大化或恢复活动窗口。
F6--激活下一个窗口。
Shift+F6--激活前一个窗口。
F10--打开激活窗口或面板的右键菜单。
LeftArrow--左移一个字符。
Ctrl+LeftArrow--左移一行。
RightArrow--右移一个字符。
Ctrl+RightArrow--右移一行。
OllyDbg的help-分析模块介绍(翻译)
分析
OllyDbg集成了快速超强的代码分析器。装载它,可以用弹出式菜单或者CPU窗口
的反汇编栏按Ctrl+A或者在执行模块选“Analyze all moles”。
分析器非常有用。它在数据中分辨代码,标记入口点和jump的目标,辨认switch
tables,ASCII和UNICODE字符串,定位过程、循环、高级switch语句和解码标准
API函数参数(看范例)。OllyDbg的其它部分也广泛用于数据分析。
这怎么可能呢?我稍微介绍一下原理。首先,OllyDbg反汇编代码区所有可能的地
址,记下所有发现的调用及指向的目标。当然,许多这样的调用不正确,但是未
必会有两个错误的调用指向同一条命令,并且三个或三个以上更不可能。这样,
三个或更多调用指向同一地址,我就确信该地址是经常使用的子过程入口。从这
个入口开始,我跟踪所有的跳转和调用,反复操作。这种方法使我99.9%确定所
有命令。然而,某些字节不在这条链中,我采用大约20个更高效的方法(最简单
的如:“MOV [RA],Ra"是无效命令).
过程检测也简单。过程,从分析器的角度看,就是从入口开始的代码的连续区域
,(理论上)可以到达其它命令(除NOP或对齐填充外)。你可以指定三种识别级
。Strict级严格确认一个入口和至少一个出口。heuristical级,分析器尝试确认
入口。如果你选择fuzzy级,或多或少相容的代码区被分离为过程。现代编译器把
过程分成几个部分作公用代码优化。这种情况下,fuzzy级特别有用!然而,误解
的概率也相当高。
类似的,loop是一个封闭的连续命令序列,这里最后的命令跳到开头、一个入口
几个出口。Loop相当于高级操作语言中的do,while和for。OllyDbg能认识任何复
杂的嵌套循环。在反汇编区,他们用长长的大括号标记。如果入口不是循环的第
一条命令,OllyDbg用个小三角形作标记。
实现switch语句,多数编译器把switch变量装入寄存器,然后减去一部分,像下
面代码序列一样:
MOV EDX,<switch variable>
SUB EDX,100
JB DEFAULTCASE
JE CASE100 ; Case 100
DEC EDX
JNE DEFAULTCASE
... ; Case 101
这些序列也可能包含一级或两级switch表,直接比较,优化,填充等等。如果你
深入研究比较和跳转树,很难说哪一条命令是某个case语句。OllyDbg为你代劳。
它标记所有的case语句,包括default,甚至尝试猜测case的意思,如'A',
WM_PAINT 或者 EXCEPTION_ACCESS_VIOLATION。如果命令序列不修改寄存器(只
由比较命令构成),那么不大可能是switch语句,但可能是if嵌套:
if (i==0) {...}
else if (i==5) {...}
else if (i==10) {...}
让OllyDbg解码if嵌套为switch,选择Analysis1中相应的选项。
OllyDbg预置超过1900个常用API函数的描述。包括KERNEL32, GDI32, USER32,
ADVAPI32, COMDLG32, SHELL32, VERSION, SHLWAPI, COMCTL32, WINSOCK,
WS2_32 和 MSVCRT。你也可以加入自己的描述。如果分析器遇到已知函数名的调
用(或跳转到该函数),它尝试解码调用跟前的PUSH命令。因此,你可以粗略翻
译该调用的功能。OllyDbg也内置大约400个标准C函数的描述。如果你利用新库,
我建议你分析前先扫描对象文件。这种情况下,OllyDbg也会解码已知的C函数参
数。
如果选项“Guess number of arguments of unknown functions”被设置,分析
器尝试确认调用过程进栈的双字数目,并标记他们为参数Arg1,Arg2等等。注意:
如果有寄存器参数,OllyDbg还是不认识也不包括在上面的统计中。分析器采用了
一个安全的方法。例如,它不分辨无参数过程和返回前用POP恢复寄存器代替舍弃
参数的case语句。然而,能分辨出的函数数目相当多,并非常有助于提高代码的
可读性。
分析器能跟踪寄存器的值。现代优化编译器,特别是面向Pentium的,经常把常量
和地址装入寄存器便于重复使用或减小内存占用空间。如果一些常量装入寄存器
,分析器会注意它,并用于解码函数及其参数。还能执行简单的算术计算跟踪
push和pop命令。
分析器不能区别不同类型的名字。如果你用已有的名字命名一些函数,OllyDbg会
解码所有该地址的调用为原过程。WinMain,DllEntryPoint和WinProc是特殊的预
定义名。你可以使用这些标号标记主程序入口,DLL入口指针和window过程(注意
:OllyDbg不会检查用户定义标号的唯一性)。当然,最好的方法是显示已定义的
参数。
非常不幸,没有一般的规则用于100%的准确分析。有些情况下,例如当模块包含
p-代码或者代码区嵌入大量数据,分析器可能认为部分数据是代码。如果统计分
析显示代码可能被打包或加密,分析器会警告你。如果你想用hit跟踪方式,我建
议你不要用fuzzy分析方式,否则断点被设置在被误认为代码的数据上的几率很高。
自解压文件通常在主体代码外有解压代码。如果你选择SFX选项“Extend code
section to include self-extractor”,OllyDbg会扩大代码区,形式上允许分
析它并跟踪。
OllDbg的一般原理(翻译)部分
我希望你熟悉80x86兼容CPU的内部结构,并且有汇编写程序的经历。我也希望你
熟悉Microsoft Windows.
OllyDbg是一个单进程多线程的“机器代码级”debugger,用于Windows环境下的32位程序。它允许你debug和patch PE格式的可执行程序。OllDbg仅仅使用列入文档的Win32 API调用,所以可用于下一代32位Windows系统。OllDbg看来也可工作于Windows XP,但是我没有详尽的测试,因此不能保证功能完整。
OllyDbg不是面向编译器。它不含特别的规则显示某些情况下特定的编译程序生成哪些代码序列。因此,你可以一样对待任何编译器编译的,或者汇编书写的任何代码。
OllyDbg与被调试的程序同时工作。你可以浏览代码和数据、设置断点、停止或继续线程,甚至运行期修改内存(有时这叫作软调试方式)。当然,如果被请求的操作不是最基本的,OllyDbg就会短时暂停程序,但这对用户透明。有时不在调试状态运行的程序会意外崩溃。OllyDbg,这个“及时”debugger,会指出异常发生的位置。
OllyDbg强烈面向模块。模块这里指启动时加载的或动态加载的主执行文件或动态连接库。在调试区,你可以设置断点,定义新标号和注释汇编语句。当一些模块从内存卸载后,OllyDbg保存这些信息到扩展名为.UDD名字同被调试模块的文件中。下次当装载这些模块时,OllyDbg自动复原所有调试信息,不论程序是否使用这些模块。比如:你调试使用Mydll的程序Myprog1,并且在Mydll中设置一些断点。那么当你调试Myprog2时,也使用Mydll,你会发现所有在Mydll中的断点还在那里,不管Mydll是否装载在不同的位置。
一些调试器把被调试进程的内存视为单个2**32字节区域。OllyDbg做了别的处理方法。内存由几个独立的块组成。任何内存操作都限制于块内。在大多数案例中,这工作优良并且容易调试。但是模块包含几个执行部分等等,你将不能立刻看到整个代码。然而这些例外不常见。
OllyDbg是内存消耗大户。启动时就要分配3MB内存,甚至更多。每次分析,备份,跟踪或文件转储另外再分配。所以当你调试大工程时消耗40或60M内存很正常。
要有效的调试一些无源代码的程序,你首先必须理解它是如何工作的。OllyDbg提供了大量的手段使理解更容易
C. 关于编译原理first follow 和select
首先要明白这三个集的作用和用途,知道了他们是用来做什么的之后,理解起来就简单一些
First(A)集的作用是标示在替换非终结符A的时候,替换后的文法的首字母集合,语法分析程序根据这个来判断给定的语言是否是合法的,是符合规则的。
Follow(A)的作用是标示那些可以出现在A之后的字符,语法分析程序根据这个,在A可以被替换为e(空)的时候来进行判断,看当前的文法是否是合法的。
这里简单说明下,比如A->b,A->e(空) 当给定的语言是 bXXXXX的时候,根据第一句文法就可以判定句子合法,但是如果给的语言是cXXXXX的时候,因为A->可以替换为空,这时候就需要一句A的follow集来进行判断,若A的follow集里面含有c 则语言是合法的
Select集的作用是将first集和follow集进行合并,如果两个文法的左端都是A,若他们的select集交集为空,表明他们是两个无关的,不会产生不确定性的文法,反之,则表明文法不是LL(1)文法
计算的公式很繁杂,理解了意思之后,看就能看出来。。。。
D. 编译原理试题·
Lex和Yacc应用方法(一).初识Lex
草木瓜 20070301
Lex(Lexical Analyzar 词法分析生成器),Yacc(Yet Another Compiler Compiler
编译器代码生成器)是Unix下十分重要的词法分析,语法分析的工具。经常用于语言分
析,公式编译等广泛领域。遗憾的是网上中文资料介绍不是过于简单,就是跳跃太大,
入门参考意义并不大。本文通过循序渐进的例子,从0开始了解掌握Lex和Yacc的用法。
一.Lex(Lexical Analyzar) 初步示例
先看简单的例子(注:本文所有实例皆在RetHat linux下完成):
一个简单的Lex文件 exfirst.l 内容:
%{
#include "stdio.h"
%}
%%
[\n] ;
[0-9]+ printf("Int : %s\n",yytext);
[0-9]*\.[0-9]+ printf("Float : %s\n",yytext);
[a-zA-Z][a-zA-Z0-9]* printf("Var : %s\n",yytext);
[\+\-\*\/\%] printf("Op : %s\n",yytext);
. printf("Unknown : %c\n",yytext[0]);
%%
在命令行下执行命令flex解析,会自动生成lex.yy.c文件:
[root@localhost liweitest]flex exfirst.l
进行编译生成parser可执行程序:
[root@localhost liweitest]cc -o parser lex.yy.c -ll
[注意:如果不加-ll链结选项,cc编译时会出现以下错误,后面会进一步说明。]
/usr/lib/gcc-lib/i386-redhat-linux/3.2.2/../../../crt1.o(.text+0x18): In function `_start':
../sysdeps/i386/elf/start.S:77: undefined reference to `main'
/tmp/cciACkbX.o(.text+0x37b): In function `yylex':
: undefined reference to `yywrap'
/tmp/cciACkbX.o(.text+0xabd): In function `input':
: undefined reference to `yywrap'
collect2: ld returned 1 exit status
创建待解析的文件 file.txt:
title
i=1+3.9;
a3=909/6
bcd=4%9-333
通过已生成的可执行程序,进行文件解析。
[root@localhost liweitest]# ./parser < file.txt
Var : title
Var : i
Unknown : =
Int : 1
Op : +
Float : 3.9
Unknown : ;
Var : a3
Unknown : =
Int : 909
Op : /
Int : 6
Var : bcd
Unknown : =
Int : 4
Op : %
Int : 9
Op : -
Int : 333
到此Lex用法会有个直观的了解:
1.定义Lex描述文件
2.通过lex,flex工具解析成lex.yy.c文件
3.使用cc编译lex.yy.c生成可执行程序
再来看一个比较完整的Lex描述文件 exsec.l :
%{
#include "stdio.h"
int linenum;
%}
%%
title showtitle();
[\n] linenum++;
[0-9]+ printf("Int : %s\n",yytext);
[0-9]*\.[0-9]+ printf("Float : %s\n",yytext);
[a-zA-Z][a-zA-Z0-9]* printf("Var : %s\n",yytext);
[\+\-\*\/\%] printf("Op : %s\n",yytext);
. printf("Unknown : %c\n",yytext[0]);
%%
showtitle()
{
printf("----- Lex Example -----\n");
}
int main()
{
linenum=0;
yylex(); /* 进行分析 */
printf("\nLine Count: %d\n",linenum);
return 0;
}
int yywrap()
{
return 1;
}
进行解析编译:
[root@localhost liweitest]flex exsec.l
[root@localhost liweitest]cc -o parser lex.yy.c
[root@localhost liweitest]./parser < file.txt
----- Lex Example -----
Var : i
Unknown : =
Int : 1
Op : +
Float : 3.9
Unknown : ;
Var : a3
Unknown : =
Int : 909
Op : /
Int : 6
Var : bcd
Unknown : =
Int : 4
Op : %
Int : 9
Op : -
Int : 333
Line Count: 4
这里就没有加-ll选项,但是可以编译通过。下面开始着重整理下Lex描述文件.l。
二.Lex(Lexical Analyzar) 描述文件的结构介绍
Lex工具是一种词法分析程序生成器,它可以根据词法规则说明书的要求来生成单词识
别程序,由该程序识别出输入文本中的各个单词。一般可以分为<定义部分><规则部
分><用户子程序部分>。其中规则部分是必须的,定义和用户子程序部分是任选的。
(1)定义部分
定义部分起始于 %{ 符号,终止于 %} 符号,其间可以是包括include语句、声明语句
在内的C语句。这部分跟普通C程序开头没什么区别。
%{
#include "stdio.h"
int linenum;
%}
(2) 规则部分
规则部分起始于"%%"符号,终止于"%%"符号,其间则是词法规则。词法规则由模式和
动作两部分组成。模式部分可以由任意的正则表达式组成,动作部分是由C语言语句组
成,这些语句用来对所匹配的模式进行相应处理。需要注意的是,lex将识别出来的单
词存放在yytext[]字符数据中,因此该数组的内容就代表了所识别出来的单词的内容。
类似yytext这些预定义的变量函数会随着后面内容展开一一介绍。动作部分如果有多
行执行语句,也可以用{}括起来。
%%
title showtitle();
[\n] linenum++;
[0-9]+ printf("Int : %s\n",yytext);
[0-9]*\.[0-9]+ printf("Float : %s\n",yytext);
[a-zA-Z][a-zA-Z0-9]* printf("Var : %s\n",yytext);
[\+\-\*\/\%] printf("Op : %s\n",yytext);
. printf("Unknown : %c\n",yytext[0]);
%%
A.规则部分的正则表达式
规则部分是Lex描述文件中最为复杂的一部分,下面列出一些模式部分的正则表达式字
符含义:
A-Z, 0-9, a-z 构成模式部分的字符和数字。
- 指定范围。例如:a-z 指从 a 到 z 之间的所有字符。
\ 转义元字符。用来覆盖字符在此表达式中定义的特殊意义,
只取字符的本身。
[] 表示一个字符集合。匹配括号内的任意字符。如果第一个字
符是^那么它表示否定模式。例如: [abC] 匹配 a, b, 和C
的任何一个。
^ 表示否定。
* 匹配0个或者多个上述模式。
+ 匹配1个或者多个上述模式。
? 匹配0个或1个上述模式。
$ 作为模式的最后一个字符时匹配一行的结尾。
{ } 表示一个模式可能出现的次数。 例如: A{1,3} 表示 A 可
能出现1次或3次。[a-z]{5} 表示长度为5的,由a-z组成的
字符。此外,还可以表示预定义的变量。
. 匹配任意字符,除了 \n。
( ) 将一系列常规表达式分组。如:{Letter}({Letter}|{Digit})*
| 表达式间的逻辑或。
"一些符号" 字符的字面含义。元字符具有。如:"*" 相当于 [\*]。
/ 向前匹配。如果在匹配的模式中的"/"后跟有后续表达式,
只匹配模版中"/"前面的部分。如:模式为 ABC/D 输入 ABCD,
时ABC会匹配ABC/D,而D会匹配相应的模式。输入ABCE的话,
ABCE就不会去匹配ABC/D。
B.规则部分的优先级
规则部分具有优先级的概念,先举个简单的例子:
%{
#include "stdio.h"
%}
%%
[\n] ;
A {printf("ONE\n");};
AA {printf("TWO\n");};
AAAA {printf("THREE\n");};
%%
此时,如果输入内容:
[root@localhost liweitest]# cat file1.txt
AAAAAAA
[root@localhost liweitest]# ./parser < file1.txt
THREE
TWO
ONE
Lex分析词法时,是逐个字符进行读取,自上而下进行规则匹配的,读取到第一个A字符
时,遍历后发现三个规则皆匹配成功,Lex会继续分析下去,读至第五个字符时,发现
"AAAA"只有一个规则可用,即按行为进行处理,以此类推。可见Lex会选择最长的字符
匹配规则。
如果将规则
AAAA {printf("THREE\n");};
改为
AAAAA {printf("THREE\n");};
./parser < file1.txt 输出结果为:
THREE
TWO
再来一个特殊的例子:
%%
title showtitle();
[a-zA-Z][a-zA-Z0-9]* printf("Var : %s\n",yytext);
%%
并输入title,Lex解析完后发现,仍然存在两个规则,这时Lex只会选择第一个规则,下面
的则被忽略的。这里就体现了Lex的顺序优先级。把这个例子稍微改一下:
%%
[a-zA-Z][a-zA-Z0-9]* printf("Var : %s\n",yytext);
title showtitle();
%%
Lex编译时会提示:warning, rule cannot be matched.这时处理title字符时,匹配
到第一个规则后,第二个规则就无效了。
再把刚才第一个例子修改下,加深下印象!
%{
#include "stdio.h"
%}
%%
[\n] ;
A {printf("ONE\n");};
AA {printf("TWO\n");};
AAAA {printf("THREE\n");};
AAAA {printf("Cannot be executed!");};
./parser < file1.txt 显示效果是一样的,最后一项规则肯定是会忽略掉的。
C.规则部分的使用变量
且看下面示例:
%{
#include "stdio.h"
int linenum;
%}
int [0-9]+
float [0-9]*\.[0-9]+
%%
{int} printf("Int : %s\n",yytext);
{float} printf("Float : %s\n",yytext);
. printf("Unknown : %c\n",yytext[0]);
%%
在%}和%%之间,加入了一些类似变量的东西,注意是没有;的,这表示int,float分
别代指特定的含义,在两个%%之间,可以通过{int}{float}进行直接引用,简化模
式定义。
(3) 用户子程序部分
最后一个%%后面的内容是用户子程序部分,可以包含用C语言编写的子程序,而这些子
程序可以用在前面的动作中,这样就可以达到简化编程的目的。这里需要注意的是,
当编译时不带-ll选项时,是必须加入main函数和yywrap(yywrap将下后面说明)。如:
...
%%
showtitle()
{
printf("----- Lex Example -----\n");
}
int main()
{
linenum=0;
yylex(); /* 进行Lex分析 */
printf("\nLine Count: %d\n",linenum);
return 0;
}
int yywrap()
{
return 1;
}
三.Lex(Lexical Analyzar) 一些的内部变量和函数
内部预定义变量:
yytext char * 当前匹配的字符串
yyleng int 当前匹配的字符串长度
yyin FILE * lex当前的解析文件,默认为标准输出
yyout FILE * lex解析后的输出文件,默认为标准输入
yylineno int 当前的行数信息
内部预定义宏:
ECHO #define ECHO fwrite(yytext, yyleng, 1, yyout) 也是未匹配字符的
默认动作
内部预定义的函数:
int yylex(void) 调用Lex进行词法分析
int yywrap(void) 在文件(或输入)的末尾调用。如果函数的返回值是1,就停止解
析。 因此它可以用来解析多个文件。代码可以写在第三段,这
样可以解析多个文件。 方法是使用 yyin 文件指针指向不同的
文件,直到所有的文件都被解析。最后,yywrap() 可以返回1
来表示解析的结束。
lex和flex都是解析Lex文件的工具,用法相近,flex意为fast lexical analyzer generator。
可以看成lex的升级版本。
相关更多内容就需要参考flex的man手册了,十分详尽。
四.关于Lex的一些综述
Lex其实就是词法分析器,通过配置文件*.l,依据正则表达式逐字符去顺序解析文件,
并动态更新内存的数据解析状态。不过Lex只有状态和状态转换能力。因为它没有堆栈,
它不适合用于剖析外壳结构。而yacc增加了一个堆栈,并且能够轻易处理像括号这样的
结构。Lex善长于模式匹配,如果有更多的运算要求就需要yacc了。
E. 编译原理中正规式(ba|a)*如何转换成NFA
F. 编译原理空字符ε与空集区别
不知你说的空集是为何指?据我所猜应该是指某个文法所能推导的语句的集合为空,这里的空集意思是不存在匹配该文法的句子。而ε则是指某个包含非终结符号的文法符号串的推导为空,例如A->ε。咋看上去好像差不多,其实它们却有本质的区别,空集是面向结果的,即一个文法所有可能推导的最终语句;而ε则是面向定义的,即某个非终结符号可以推导为空,这样的定义可以在推导过程重复使用。
最后给你来点哲学的。为什么会存在ε?古代有句话叫,其大无外,其小无内,大小之间转化的奥秘在编译原理中真实的被呈现了出来,就看你有没有发现。可以肯定的说,ε的存在正是应了无穷的需要。例如:A->aA|ε,这里ε既可以A可以表达任意多的a串,又可以动态的将其终止,不至无休止的无限下去。
你终会明白,理解了ε,就是理解了形式语言的整个灵魂。