以前尝试阅读《编译原理》,但都没有读下来,现在看《编程语言实现模式》,感觉轻松多了。其实,目前我只对解析感兴趣,只要看这本书的第一部分解析起步就可以了,确实没有必要去啃《编译原理》。下面就是学习的内容了。
解析,分为两步,先是进行词发分析,将输入转换成一个一个的Token,然后是进行语法分析。一个一个的Token组成语句,对应一定的语法。根据这些Toke,匹配一定的语法。词法分析器,lexer,是语法分析器,parser,的基础。
先来看看词发分析器。例如语句,1024+ 78*pi,一个简单的表达式,
词发分析就是要得出上面的语句由这些Token组成,1024, + , 78, * ,pi。Token可以是运算符,可以是数字,也可以是字符串。在处理原来的字符串时,遇到第一个字符'1',把它放入一个buf里面,然后遇到0,仍然是合法的数字,放入buf里面,直到遇到'+',这时就可以把buf返回,得到1024这一个token。然后从刚才的位置继续,'+‘,直接返回这个Token,可以把其作为运算符一类,也可以是运算符细分之后的‘+’一类。后面遇到空格,跳过。后面的类似处理。
在做这些操作的时候,由lexer的数据结构支持,所以lexer需要保存原始的字符串,需要知道当前的位置,而Token应该有两个元素,一个保存它的内容,另一个保存它的类型。在解析的时候,就可以知道所得到的Token类型,直接保存下来,便于以后处理。而对应的操作,就需要有,判断当前字符是什么类型,字母,数字,或是运算符等。然后就是收集Token的操作。当遇到一个数字的时候,需要循环处理,知道遇到的不再是数字,这是对简单的整数,如果需要处理浮点数,科学记数法等,就会复杂一些了。当遇到一个字母时,如果不允许字符串里面包含数字,那么就只收集字母,如果可以包含数字,那么就是遇到运算符的时候结束了。
下面就是一个简单的lexer的实现,支持字符串,运算符,数字,的词法分析。
首先是Token,这里用到了TokenType,type用整数表示,本来是想用enum class TokenType的,但是在继承的时候遇到了问题。虽然这个enum class定义很像class,但是不支持继承。那么我想添加type的时候,就不是很方便。然后就有了下面的一个类来定义TokenType。
class TokenType
{
public:
TokenType():_EOF(0),TEXT(1),NUMBER(2),name({"EOF","TEXT","NUMBER"}){
}
string Name(int t) {
return name.at(t);
}
public:
const int _EOF;
const int TEXT;
const int NUMBER;
vector<string> name;// = {"EOF","TEXT"};
}TokenType;
class Token
{
public:
Token(){}
Token(int tp, string tx) {
type = tp;
text = tx;
}
int Type() const {
return type;
}
string Text() const {
return text;
}
friend ostream& operator << (ostream& out,const Token& token){
out << "<" << TokenType.Name(token.Type()) << "," << token.Text() << ">" << endl;
}
protected:
int type;
string text;
};
在Lexer里面,比较重要的是如何收集Token,对应的是NextToken这个方法,而在这里面用到的判断当前字符类型的isLETTER,isWS,isDIGIT,都很容易实现,而TEXT,NUMBER则是在触发收集对应Token的时候开始执行。这几个方法如下。
/*! \brief consume the current char, and get the next char
* if it is the end of input, set c as EOF
*/
void consume(){
p++;
if (p >= text.length())
c = _eof;
else
c = text[p];
}
/*! \brief match the current char to target char
*
* @param x
*/
void match(char x){
if(c==x)
consume();
else
LexerError err(x,c,p);
}
/*! \brief get token from the text,
* here only support text word and digital number,
* integer, float or scientific number
* @return the next token
*/
Token NextToken(){
while(c!=_eof){
if(isWS())
WS();
else if(isLETTER())
return TEXT();
else if(isDIGIT())
return NUMBER();
else
LexerError err(c,p);
}
return _EOF();
}
bool isWS(){
return (c==' ' || c=='\t' || c=='\r');
}
bool isLETTER(){
return ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'));
}
bool isDIGIT(){
return (c >= '0' && c <= '9');
}
/*! \brief Get all the following digits as an integer
*
* @return return the integer in the string format
*/
string Digit() {
string buf;
do {
buf.push_back(c);
consume();
} while (isDIGIT());
return buf;
}
/*! \brief Skip all whitespace
*
*/
void WS(){
while(isWS())
consume();
}
Token _EOF(){
return Token(TokenType._EOF,toString(_eof));
}
/*! \brief Get all the following letter into a text word
*
* @return return the word as a TEXT token
*/
Token TEXT() {
string buf;
do {
buf.push_back(c);
consume();
} while (isLETTER());
return Token(TokenType.TEXT, buf);
}
/*! \brief Get the number, whether it is a integer, or a float,
* or even in scientific format
*
* @return
*/
Token NUMBER() {
string buf = Digit();
if (c == '.')
buf += Digit();
if (c == 'e' || c == 'E') {
consume();
buf.push_back(c);
if (c == '+' || c == '-') {
consume();
buf.push_back(c);
buf += Digit();
}
}
return Token(TokenType.NUMBER, buf);
}
可以看看收集NUMBER的过程,如果使用正则表达式,对应的模式为 (\d)+([.](\d)+)?([e|E][-|+]?(\d)+)?。或许正则表达式的过程就是上面的过程。
下面是测试结果,

开始使用github了,这个项目的完整代码可以参考,https://github.com/Frandy/anapig.git
希望能与大家多交流,谢谢。
来源:https://www.cnblogs.com/Frandy/archive/2012/10/06/parser_simple_lexer.html