简单的词法分析程序和一种lex for Win32用法示例(上)
学计算机的同学都要学习编译原理,理论是抽象了点,但最终目的毕竟只是让我们了解一下程序语言编译器的构造原理。
尽管没有充分的证据显示lex+yacc能证明一个人学习cp程度的好坏(考试的作用就更难以拿来说明这些了),但好歹大家学过nfa,学过确定化,学过dfa化简,用c语言大概可以写出设定简单的词法分析程序了。
然而教材上都是一些纯理论性的内容,实现技术的精粹还得去找洋人学。因而本科生只需掌握lex和yacc——这已经是为极限,再低就难以搞清楚cp和programming有什么关系…再高?说实话暂时已是心有余而力不足了…
那么lex的作用大概就是这些,尽管在几乎所有的程序设计语言编译器中词法分析都是由语法分析程序不断调用而生效,但对于我们来说分别实现并作演示是再好不过。这就是本文介绍词法分析器lex的目的。
一个lex源程序由四部分构成,基本框架如下:
%{
//包含的头文件、函数声明以及一些全局量的声明
%}
//词法定义
%%
//规则定义
%%
//自定义程序
需要注意的是,带有%的分隔符必须顶格书写,词法分析和规则定义的基本形式为正规式。为了显示词法分析的最终成果,需要定义一个二元结构体,分别存储其单词内容以及归属类别,并存入定义好的目标文件中,同时按国际惯例显示源代码出错信息。
现在给出一个PL/0语言(pascal语言的一种子集)的EBNF描述:
<prog> → program <id>;<block>
<block> → [<condecl>][<vardecl>][<proc>]<body>
<condecl> → const <const>{,<const>}
<const> → <id>:=<integer>
<vardecl> → var <id>{,<id>}
<proc> → procedure <id>(<id>{,<id>});<block>{;<proc>}
<body> → begin <statement>{;<statement>}end
<statement> → <id> := <exp>
|if <lexp> then <statement>[else <statement>]
|while <lexp> do <statement>
|call <id>[(<exp>{,<exp>})]
|<body>
|read (<id>{,<id>})
|write (<exp>{,<exp>})
<lexp> → <exp> <lop> <exp>|odd <exp>
<exp> → [+|-]<term>{<aop><term>}
<term> → <factor>{<mop><factor>}
<factor>→<id>|<integer>|(<exp>)
<lop> → =|<>|<|<=|>|>=
<aop> → +|-
<mop> → *|/
<id> → l{l|d} (注:l表示字母)
<integer> → d{d}
注释:
<prog>:程序 ;<block>:块、程序体 ;<condecl>:常量说明;<const>:常量;
<vardecl>:变量说明 ;<proc>:分程序 ; <body>:复合语句 ;<statement>:语句;
<exp>:表达式 ;<lexp>:条件 ;<term>:项; <factor>:因子 ;<aop>:加法运算符;
<mop>:乘法运算符; <lop>:关系运算符
odd:判断表达式的奇偶性。
利用上述文字中提取出的词法信息,我们可以建立一个简单的词法分析程序:
%{ / coded by 韩翼 from nwu Email:8621316@qq.com */
include <stdio.h>
include <stdlib.h>
include <string.h>
define MAX_COUNT 10000 //最大可接受id数
int process(); //处理函数 int main(int argc,char argv); int nowline = 1; //当前行数计数器 int errorno = 0; //出错计数器 int flag = 0; //分支标识 int count = 0; //id计数器 char desfilename[32]; //目标文件名 char srcfilename; //源文件名 FILE srcfile; //源文件缓冲指针 FILE desfile; //目标文件缓冲指针 struct result //id标记二元组 { char content; char description; }result[MAX_COUNT]; %} d [0-9] l [a-zA-Z] wr ([0-9]+)a-zA-Z integer [0-9]+ id a-zA-Z anotherline [\n] whitespace [\t]+ %% “program” { flag = 0; process(); } “const” { flag = 1; process(); } “:=” { flag = 2; process(); } “var” { flag = 3; process(); } “procedure” { flag = 4; process(); } “begin” | “end” { flag = 5; process(); } “if” | “then” | “else” | “while” | “call” | “read” | “write” { flag = 6; process(); } “+” | “-” { flag = 7; process(); } “” | “/” { flag = 8; process(); } “=” | “<>” | “<” | “<=” | “>” | “>=” { flag = 9; process(); } “(” | “)” | “,” | “;” { flag = 10; process(); } {wr} { flag = 13; process(); } {id} { flag = 11; process(); } {integer} { flag = 12; process(); } {anotherline} { nowline++; } {whitespace} { ; } “ ” { ; } . { flag = 14; process(); } %% int process() { switch(flag) { case 0: result[count].description = “<prog>”; break; case 1: result[count].description = “<condecl>”; break; case 2: result[count].description = “<op>”; break; case 3: result[count].description = “<vardecl>”; break; case 4: result[count].description = “<proc>”; break; case 5: result[count].description = “<body>”; break; case 6: result[count].description = “<statement>”; break; case 7: result[count].description = “<aop>”; break; case 8: result[count].description = “<mop>”; break; case 9: result[count].description = “<lop>”; break; case 10: result[count].description = “<bop>”; break; case 11: result[count].description = “<identifier>”; break; case 12: result[count].description = “<integer>”; break; case 13: errorno++; result[count].description = “<wrong integer or id>”; printf(“%s(%d) : error ‘%s’ : wrong integer or identifier\n”,srcfilename,nowline,yytext); break; case 14: errorno++; result[count].description = “<unknown identifier>”; printf(“%s(%d) : error ‘%s’ : unknown identifier\n”,srcfilename,nowline,yytext); break; } result[count].content = yytext; fprintf(desfile,“%d: %s\t%s \n”,count+1,result[count].content,result[count].description); count++; return 0; } int main(int argc,char argv) { int i = 0; if( argc != 2) { printf(“No sourcecode processed : parameter error\n”); return 0; } srcfilename = argv[1]; strcat(desfilename,argv[1]); while (desfilename[i] != ‘.’) { i++; } desfilename[i] = ‘\0’; strcat(desfilename,“.pl0”); if( (srcfile = fopen(argv[1],“r”)) == NULL ) { printf(“No sourcecode processed : cannot open the file \”%s\“\n”,argv[1]); return 0; } if( (desfile = fopen(desfilename,“a”)) == NULL ) { printf(“No sourcecode processed : cannot write the file \”%s\“\n”,desfilename); return 0; } yyin = srcfile; yylex(); fprintf(desfile,“\n”); fprintf(desfile,“completed! welcome to lex by mp77,this is only a demo for compile principle.\n”); printf(“%s - %d error(s)\n”,desfilename,errorno); printf(“completed! welcome to lex by mp77,this is only a demo for compile principle.\n”); fclose(desfile); fclose(srcfile); return 0; }
接下来我们会介绍如何利用lex工具生成本代码的C语言源代码,并使用vc6.0编译成win32 console程序。