前言
本学期学习了编译原理。参考了教材和龙书以及中科大的编译原理mooc,以一般的例子来完成一个简单的词法分析器,顺便完成本次的课程实验。预计会写一个系列,本文为第一篇。
项目地址
关于文法
采用了一般的通用例子C- -语言,C语言下的一个小子集,考虑到龙书的例子,采取了Java作为编写语言。关于C- - 的BNF描述如下:
<P>::=void main()<block>
<block>::={<sen_string>}
<sen_string>::=<sen_string><sentence>|ε
<sentence>::=<statement_s>|< ignment_s>|<compare_s>|<func_s>
<statement_s>::=<class><id>|<class><ignment_s>
<class>::=int|char
<ignment_s>::=<id>=expr
<integer> ::= <d_string><digit>|<digit>
<d_string> ::= <d_string><digit>|<nonzero_num>
<nonzero_num>::=1|2|3|4|5|6|7|8|9
<digit>::= 0| <nonzero_num>
<char>::=A|B|C|D|…|Z|a|b|c|d|…|z
expr=term| expr +term|expr-term
term::=<term>*<term>|<term>/<term>|<factor>
factor::= <integer> |<expr>|<id>
<id>::=char|_|<id><char>|<id>_|<id>_<digit>
<func_s>::=cin>><id>| cot<<<id>|return <id>
<compare_s>::=<id>><id>|<id><<id>|<id>==<id>|<id>>=<id>|<id><=<id>|<id>!=<id>
<bound_symbol>::=;|{|}|,|(|)
<opera_symbol>::=+|-|*|/|=|<|>|>=|<=|!=
一些补充说明:这里的文法是一个相对较大的子集,包含了申明语句statement_s
(这里允许申明的类只赋值了整型和字符型),赋值语句ignment_s,功能语句func_s(cin,cout,return语句),比较语句compare_s(注:该比较语句只对标识符进行了值比较,运算式不包含在内);扩充一般标识符至+|-|*|/|=|<|>|>=|<=|!=
,单位数字扩充至多位,加减乘除的优先级导致的二义性问题也已经被定义处理好。
保留字关键字:if、int、for、while、do、return、break、continue,void、main、cin、cout
核心类源码
package code;
import java.util.Hashtable;
import java.util.Queue;
public class Lexer {
char peek=' ';//存放读取字符
Hashtable reserveWords = new Hashtable();
void reserve(Word w)
{
reserveWords.put(w.lexeme,w);
}
public Lexer()
{
//保留关键字
reserve(new Word("if",Tag.RWORD));
reserve(new Word("for",Tag.RWORD));
reserve(new Word("else",Tag.RWORD));
reserve(new Word("continue",Tag.RWORD));
reserve(new Word("break",Tag.RWORD));
reserve(new Word("while",Tag.RWORD));
reserve(new Word("do",Tag.RWORD));
reserve(new Word("main",Tag.RWORD));
//重要的标识符
reserve(new Word("int",Tag.CLASS));
reserve(new Word("char",Tag.CLASS));
reserve(new Word("cin",Tag.Func_S));
reserve(new Word("cout",Tag.Func_S));
reserve(new Word("return",Tag.Func_S));
}
void readch(char c)//读入字符
{
peek=c;
}
boolean readch_if(char c1,char c2)//判断读入字符
{
readch(c1);
if(peek!= c2)
{
peek=' ';
return false;
}
peek=' ';
return true;
}
public Token scan(Queue<Character> flute)
{
for(;;readch(flute.poll()))
{
if(peek==' '||peek=='\n'||peek=='\r')continue;
else break;
}
switch(peek) {
//多字符符号处理
case '=':
if(readch_if(flute.element(),'='))
{
flute.poll();
return Word.eq;
}
else return new Symbol('=',Tag.OPERA_S);
case '!':
if(readch_if(flute.element(),'='))
{
flute.poll();
return Word.ne;
}
else return new Token('!');
case '<':
if(readch_if(flute.element(),'='))
{
flute.poll();
return Word.le;
}
else if(readch_if(flute.element(),'<'))
{
flute.poll();
return Word.lemov;
}
else return new Symbol('<',Tag.OPERA_S);
case '>':
if(readch_if(flute.element(),'='))
{
flute.poll();
return Word.ge;
}
else if(readch_if(flute.element(),'>'))
{
flute.poll();
return Word.rimov;
}
else return new Symbol('>',Tag.OPERA_S);
//符号处理
case '+':
peek=' ';
return Symbol.plus;
case '-':
peek=' ';
return Symbol.minus;
case '*':
peek=' ';
return Symbol.times;
case '/':
peek=' ';
return Symbol.divided;
case ',':
peek=' ';
return Symbol.com;
case ';':
peek=' ';
return Symbol.se;
case '(':
peek=' ';
return Symbol.lep;
case ')':
peek=' ';
return Symbol.rip;
case '{':
peek=' ';
return Symbol.lecb;
case '}':
peek=' ';
return Symbol.ricb;
}
if(Character.isDigit(peek))//数字
{
int v=0;
do{
v=10*v+Character.digit(peek,10);
readch(flute.poll());
}while(Character.isDigit(peek));
return new NUM(v);
}
if(Character.isLetter(peek)||peek=='_')//字母或下划线开头的变量标识符
{
StringBuffer b = new StringBuffer();
do{
b.append(peek);
readch(flute.poll());
}while(Character.isLetter(peek)||peek=='_');
String s = b.toString();
Word w =(Word)reserveWords.get(s);
if(w!=null) return w;
w = new Word(s,Tag.ID);
reserveWords.put(s,w);//变量标识符加入保留哈希表
return w;
}
Token tok = new Token(peek);peek=' ';//未定义的标识符
return tok;
}
}
这部分采用的是手工构建,其实没有太大难度。主要卡壳的位置可能在于变量标识符的识别以及输入形式。由于这是一个简单的词法分析器,所以输入采取了从文件中读写单个字符然后针对字符做针对性的切分和区分,输出返回了被切分识别成功的词法单元。
由于未涉及到语法分析,故没有错误输入的矫正分析。
输入输出形式
输入测试源码:
void main ()
{
int a,b;
a = 10;
b = a + 20;
if(a>=b)
{
cin<<a;
}
else
{
while(b>=a)
{
for(;;a=a*b);
}
}
return 0;
}
注:该部分源码未对所有保留字及运算符测试,可以自己编写替换。
输出形式:
在我的分类中将标识符分为7类,做了相应对应。龙书的例子分类更为详尽和贴合实际。建议可以自主查看龙书源码。