序:貌似很久没有写博文了,到这回有两年之隔了~上回听10届毕业同僚们的找工作交流之谈,说写博文很 重要,要养成这个习惯,可是每次想写的时候都会给自己找一大堆的理由说服自己没有时间 ;后来想想,这与在纸上做笔记是同一个性质,同等重要:即记下当时的某种想法或为这段时间的学习而作总结。正好这两年把记录本都耗光了,所以以后还是改用电子版吧,同时还可以方便地进行归类以及与别人分享和讨论。
一、 编译是这样一个过程:从源文件中取出有意义的词,并按照事先约定好的规则对它们可能的组合进行检测,与此同时对每一个合法组合翻译成目标语言的句子,最后将生成的目标语言文件输入解释器进行执行;
虽然编译的每个过程中都有一套相应的理论对分析进行支撑,但是 编译器实现仍然是个非常庞大且复杂的工作,所以目前所使用的编译器都是由一些非常专业的人来实现,它们的数量随编程语言数量的增加而增加,所以需求量很稳定。
即使在这种几乎没有什么实现需求的情况下,计算机科学与技术仍将“编译原理”列为基础课中非常重要的一门;因为它让读者有了运用前面所学的数据结构以及其它与编程相关的知识的机会之外,还让读者学到了处理某类问题的方法和程序 实现 框架。目前,我所知道的编译原理主要应用领域有对DFA/NFA状态机的使用(比如模式匹配)以及对配置文件的解析(现在绝大多数开源软件都是通过解析配置文件来获取配置参数)。
词法分析就是从待分析的文本中获取有意义的单词(比如常数,变量);但在实现之前,需要对源语言中可能出现的单词进行分类,其实,稍微想想就能发现这些单词就是在后面进行语法分析时所谓的终结符;待单词分类之后,就可以使用DFA对它们进行描述了,这带来的好处是我们可以很容易根据DFA的状态转移图来实现程序,这就是规律:if 转移条件1 else if 转移条件1 ... else if 转移条件n,而在转移的过程中来获取对应的单词;因为语法分析的过程就是检查某些单词所组成的语法单元是否符合构成源语言句子结构的规定的,所以它不管你获取的单词具体是什么内容,它只管构成某一条源语句的这些单词都是些什么类型,比如“ 变量 = 数字 ”这种赋值语句,而不管它具体是什么(a=2);所以在获得具体单词内容的同时还需要使用某一个全局变量来记录获得这个单词的类型以供语法分析时使用。
语法分析就是检查待分析文件内容合法性的过程,同样,为了能够更好地进行程序实现,需要文法这种符号集来对语法进行描述(目前只懂LL(1)文法以及自上而下的分析方法)。LL中第一个L表示从左至右扫描源程序的每一行,而第二个L表示最左推倒(即在进行文法匹配时总是左起第一个非终结符),而自上向下的含义就是从源文件的第一行开始分析直至最后一行。在这个分析过程中,如果不能确定使用哪一条文法中的推倒规则进行分析的话就会出现递归的现象(即)。所以在实现之前需要将这种文法进行化简以及消除递归,LL(1)文法即满足这种条件的文法:
1、无直接或间接左递归
2、推倒规则左部的所有右部first集不相交;
3、含有ε推倒结果的推倒规则右部(非终结符),其first集与fellow集的交集为空
只要使用LL(1)文法对整个语法结构进行描述之后,就可以使用一种规定的程序框架(递归下降分析法)进行实现。这又是一个规律!
二 配置文件解析器的实现
1、文法如下:
/*配置文件文法:(忽略空格和制表符、注释行和空白行)
* config->(#anystring/n)* |exp * | (/n)*
* exp->name=value exp'
* exp'->/n | #anysting(/n|EOF)| EOF“
即一个配置文件的每一行有如下例子描述的四种情况:
#########注释符为”#“##########
(N个空格)QQ = 32371323
(空行)
(N个制表符) username = value #注释只能在开头或最后
2、终结符有: 保留字(QQ, username ) ,VALUE,"=",注释符(#),换行符(DFA就不画了,很简单)
三、部分实现源码:
1、 词法分析:
[ code=C/C++ ]
static void getWord(){
if(global.j==0){
global.line_num++;
if(fgets(global.line,MAXLINE,global.fd)==NULL)
//if(feof(global.fd))
global.isEOF=1;
}
while(global.line[global.j]==' '||global.line[global.j]=='/t') global.j++; //跳过制表符和空格
if(global.line[global.j]=='#'){ //跳过注释,直接到下一行去解析
global.j=0;
global.sym=COMMENT;
}
else if(global.line[global.j]=='/n'){ //跳过空白行
global.j=0;
global.sym=RET;
}
else if(global.line[global.j]=='='){
global.sym=EQ;
global.j++;
}
else{
//是Name或者是value
int i=0;
char *temp=NULL;
NEW(temp,char,MAXSIZE);
do{
temp[i++]=global.line[global.j++];
}while(global.line[global.j]!=' '&&global.line[global.j]!='/n'&&global.line[global.j]!='/0'); //规定每一个词之间必须用空格进行间隔
temp[i]='/0'; //字符串结束符
if(isResv(temp)){
//错误:不能使用sizeof(temp),因为指针永远都是为4(32bit机器)memcpy(global.name,temp,sizeof(temp));
strcpy(global.name,temp);
global.sym=RESV;
}
else{
strcpy(global.value,temp);
global.sym=VALUE;
}
DEL(temp);
}
}
[ /code]
2、对应于 config->(#anystring/n)* |exp * | (/n)*的语法分析部分:
int parse_config(char * filename,config_t* config){ //解析配置文件
config_init(filename); //初始化
memset(config,0,sizeof(config_t));
getWord();
while(global.isEOF==0){
switch(global.sym){
case COMMENT:
case RET:
getWord();
continue;
} //去除空行和注释行
assignment(config);
}
if(global.errmsg->line_num!=0){
error_output();
return 1; //parse fail
}
else return 0; //parse succ
}
3、对应于 * exp->name=value exp'和* exp'->/n | #anysting(/n|EOF)| EOF“的语法分析部分:
注:这里不是很严格地按照一个非终结符需要一段代码来实现这么一条说法来的
static void assignment(config_t* config){
if(global.isEOF==1) return;
if(global.sym==RESV)
{
getWord();
if(global.sym==EQ){
getWord();
if(global.sym==VALUE){
//对config_t进行赋值,并可以考虑进行name值的类型扩展
if(strcmp(global.name,"QQ")==0)
strcpy(config->QQ,global.value);
else if(strcmp(global.name,"username")==0)
strcpy(config->username,global.value);
}else error("Need a value");
}else error("Need an equal");
}else error("Need a Name or Not recognized");
getWord();
if(feof(global.fd)){
global.isEOF=1;
return;
}
if(global.sym==RET ||global.sym==COMMENT)
getWord();
else error(" Comment or Return is OK here");
}
版权归DoubLL(Leeyu&&Ljg)所有,所有转载必须注明来源!