在本文开始之前,我假定你已经了解了以下几个很简单的概念:
文法符号(token)
产生式(production)
终结符(terminator)
非终结符(non-terminator)
文法(grammar)
自动机
语法分析的含义
符号约定
我在编译原理的学习过程中发现最大的困难是有时无法精确地分辨龙书上的α(阿尔法)和a(A的小写字母),很多时候因为这样搞的一头雾水。为了让你们不会和我受到一样的困扰,我决定同你们约定一下符号含义:
· 当我使用大写的英文字母,如A、B、C等,它精确地表示一个非终结符;
· 当我使用小写的英文字母,如a、b、c等,它精确地表示一个终结符;
· 当我使用下划线加某个数字时,如_1,它表示一个非终结符或一个终结符;
· 不像很多编译原理书,我不会使用拉丁字母表示“任意符号序列”和“空串”,
反而,当我想表达任意序列的时候,我直白的使用中文“任意”;当我想表达空串的时候,我直白的使用中文“空”;如果在叙述性的文字里面出现了这个两个概念,那么我会用引号括起来,但在产生式里不会。
· 我会将文法表示成G或G1、G2等;将增广文法表示成G`;将增广开始符表示成S`;将增广结束符表示成$。
比如,A -> 任意 B c 任意,在我这里,它表示一个非终结符A可以推导出任意符号序列开头,后面跟一个非终结符B以及一个终结符c,再后面又是任意符号序列。
记住符号约定是重要的,因为它会让你清楚的明白我要表达的意思。
FIRST集的引入
直观的讲,一个符号的FIRST集包含这个符号能推导出的所有串的第一个非终结符。
比如,有以下文法G:
A -> a
A -> B
B -> b
B -> 空
A能推导出a开头的串,因为存在A -> a,所以a属于A的FIRST集;也可能推导出b开头的串,因为虽然不直接存在A -> b,但存在A -> B和B -> b,也就是说,我们可以间接的从A推导出b开头的串,因此,b也属于A的FIRST集;最后,因为A -> B 而 B -> 空,这个两个产生式的含义是A能推导出B而B可能为(推导出)空,那么A就可能推导出空,我们可以并不负任何责任地说“空串也是A的FIRST集”,这并没有什么用,因为空串代表一个符号都没有。除此之外,A确实不能推出其他符号开头的串了。
注意,我们在刚开始说“这个符号能推导出的所有串”,而非终结符只能推出它自己,因此,非终结符的FIRST集便是它自己。
接下来,我们给出某符号的First集的普遍求法。
输入:文法符号X(这里我们违背了符号约定,这个X表示任何文法符号)
输出:X的FIRST集
算法:
1. 如果X是终结符,那么FIRST(X)只包含一个元素X,算法结束;
2. 否则,对每一个X为左部的产生式X -> _1 _2 … ,求出_1的FIRST集,如果_1能推导出空串,那么继续求_2的FIRST集,依次类推。
FOLLOW集的引入
FIRST集是很有意义的,回顾一下,你可以这样简单的理解它:我的语法分析进行到某个阶段,开始识别非终结符A,这时候我看下一个输入符号,如果这个输入符号不在FIRST(A)中,那么这就不是A能推导的序列,那么识别错误。
接下来我们来引入另外一个同样很有意义的概念,FOLLOW集。
不过,在这之前,我们先来看另外一个概念,增广文法,不要被名字吓到,其实它很简单。
如果一个文法的开始符号是S,那么它的增广文法是给原有的文法中加上这个产生式:
S` -> S $,
并将S`作为新的开始符号,其中S`称为增广开始符号,$称为增广结束符号。加入S`是因为原来的文法里可能并没有说明怎么样才算推导结束。这个产生式的含义是:输入串存在一个非终结符号$作为结束的标志,我们从S`开始推导输入串,如果推导完$,就算推导结束了。
是时候引入FOLLOW集了。
某个符号的FOLLOW集表示:我们识别了这个符号后,这个符号后面可能出现哪些非终结符?
来看增广文法G:
S` -> S $
S -> A a
A -> b
A -> c
S -> A B
B -> d
B -> 空
我们先来求A的FOLLOW集:产生式S -> A a中,我们推导完这个A后,如果是正确的输入串,那我们会看到a,那么a就属于A的FOLLOW集;产生式S -> A B中,我们推导完这个A后,如果是正确的输入串,那我们会看到“B”,然而,B是一个非终结符,我们真正看到的应该是B能推导出的串的第一个非终结符,那么这个非终结符是什么?聪明的你肯定想到了,它不就是B的FIRST集?BINGO!因此,我们把B的FIRST集都加入A的FOLLOW集中。但是问题来了,B可能是空集啊,如果输入串对应的这部分B是空集,那我们下一个看到的是什么?想想看,我们在B是空的情况下将A B按S -> A B 规约到了S,那么S推导出的串的末尾不就是A的末尾?再进一步,S后面出现的,不正是A也会出现的?于是,我们将FOLLOW(S)加入FOLLOW(A)中。请注意,加入的前提是A后面的B可能为空!A没有其他FOLLOW了,可我们还有一个遗留的问题,FOLLOW(S)是什么?这个问题就交给你们了:)
接下来,我们给出某符号的FOLLOW集的普遍求法:
输入:文法符号X(这里我们违背了符号约定,这个X表示任何文法符号)
输出:X的FOLLOW集
算法:
对于每一个右部出现了X的产生式A -> 任意 X _1 _2 … _n
对于X右边的每一个符号_i:
1. 如果_i是终结符,那么把_i加入X的FOLLOW集,结束内层循环;
2. 否则,如果i < n,那么
把_(i+1)的FIRST加入X的FOLLOW集,如果_(i+1)能推导出空串,那么置i = i +1,重新做1;否则,结束内层循环。
3. 否则(i等于n,即_i是最右边的符号),那么
把FOLLOW(A)加入X的FOLLOW集中。
文章的最后,我给出一个定义良好的文法类,囊括了FIRST集和FOLLOW集的求法。
产生式应该准确的属于一个文法,而产生式都是由文法符号构成,因此,设计思路即是文法管理所有文法符号和产生式。
由于在产生式的描述中,同一符号可能出现多次,因为,文法类将符号实体(private: _TokenEntity)和符号(token)区分开来。
如果是类的使用者,只需阅读// *注释的部分,如果要修改、理解类,还需要阅读// $ 注释的部分。
// * 文法
// * 文法是一组符号组成的产生式的集合。
// * 本文法至少包含2个文法符号以及0个产生式:
// 一个称为增广开始符的非终结符(记为$extstart$),由extstart_token()返回,它不会出现在产生式的右部。
// 一个称为增广结束符的终结符(记为$extend$),由extend_token()返回,它只会出现在左部为增广开始符的产生式的最右边。
class grammar
{
private:
// $ 文法符号实体类型
struct _TokenEntity
{
public:
// $ 符号字符串
std::string _str;
// $ 是否是终结符
bool _ister;
// $ 两个符号实体是否相同?
bool operator==(const _TokenEntity &that) const { return _str == that._str; }
bool operator!=(const _TokenEntity &that) const { return !(*this == that); }
// $ 为使用std::set而定义
bool operator<(const _TokenEntity &that) const { return _str < that._str; }
};
// $ 文法中所有符号实体的集合。
std::set<_TokenEntity> _toks;
public:
// * 文法符号类型,对应一个文法符号实体
class token
{
friend class grammar;
private:
// $ 到符号实体集合的迭代器。
decltype(_toks)::iterator _p;
public:
// * 返回这个符号的C风格字符串。
const char* c_str() const { return _p->_str.c_str(); }
// * 是否代表相同的符号。
bool operator==(const token &that) const { return _p == that._p; }
bool operator!=(const token &that) const { return !(*this == that); }
// * 返回本符号是否是终结符。
bool is_ter() const { return _p->_ister; }
};
private:
// $ 快速定位增广开始符号。
token _extstart;
// $ 快速定位增广结束符号。
token _extend;
public:
// * 产生式类型
class production
{
friend class grammar;
private:
// $ 左部
token _left;
// $ 右部
std::vector _right;
public:
// * 右部的类型。right_type.size() == 0代表左部推导出空串。
// * 与std::SequenceContainer语义相同,每个元素按序对应右部的符号。
typedef decltype(_right) right_type;
// * 返回左部符号。
const token left() const { return _left; }
// * 将右部以right_type形式返回。
const right_type& right() const { return _right; }
// * 返回两个产生式是否相等。
bool operator==(const production &that) const { return _left == that._left && _right == that._right; }
bool operator!=(const production &that) const { return _left != that._left || _right != that._right; }
};
private:
// $ 维护一个产生式集合。
std::vector _prods;
// $ first使用。
inline void _pushback_if_no(std::vector &result, const token tok) const
{
for (auto t : result)
if (t == tok)
return;
result.push_back(tok);
}
public:
// $ 构造一个文法,这个文法只含增广开始符、增广结束符,不含任何产生式。
grammar()
{
// 建立增广开始符号实体并初始化_extstart
_TokenEntity extstart;
extstart._str = "$start$";
extstart._ister = false;
auto result = _toks.insert(extstart);
if (result.second == false)
throw std::string("can't insert $start$ to grammar::_toks");
_extstart._p = result.first;
// 建立增广结束符号实体并初始化_extend
_TokenEntity extend;
extend._str = "$eof$";
extend._ister = true;
result = _toks.insert(extend);
if (result.second == false)
throw std::string("can't insert $eof$ to grammar::_toks");
_extend._p = result.first;
}
// * 产生式的pointer类型(唯一标识该产生式,和指针语义相同)
typedef decltype(_prods)::const_iterator pointer_type;
// * 获取左部为left的产生式的pointer集合并将它们push_back到result中。
inline void find(std::vector &result, const token left) const
{
for (auto i = _prods.begin(); i != _prods.end(); ++i)
if (i->left() == left)
result.push_back(i);
}
// * 将符号t的FIRST集push_back到result中,但不包括可能存在的空串;
// * 如果t的FIRST集存在空串,则返回true,否则返回false。
bool first(std::vector &result, const token t) const
{
if (t.is_ter()) // 终结符的FIRST集便是它自己
{
_pushback_if_no(result, t);
return false;
}
bool haveEmpty = false;
for (auto prod : _prods)
{
if (prod.left() != t)
continue;
// t -> 空串
if (prod.right().size() == 0)
haveEmpty = true;
// t -> 终结符 ...
else if (prod.right()[0].is_ter())
_pushback_if_no(result, prod.right()[0]);
// t -> 非终结符 ...
else
{
typedef production::right_type::size_type szt;
szt i = 0;
szt end = prod.right().size();
while (i < prod.right().size())
{
bool e = first(result, prod.right()[i]);
if (e) // 需要看下一个符号
++i;
else
break;
}
}
}
return haveEmpty;
}
// * 将符号t的FOLLOW集push_back到result中,t不应该是增广结束符号。
void follow(std::vector &result, const token tok) const
{
assert(!is_extend(tok));
if (is_extstart(tok)) // 扩展开始符号的FOLLOW就是结束符号
_pushback_if_no(result, extend_token());
for (auto prod : _prods)
{
typedef production::right_type::size_type Sz;
for (Sz i = 0; i < prod.right().size(); ++i)
{ // 对于每次出现在产生式右端的tok
if (prod.right()[i] != tok)
continue;
// A -> ...tok
if (i == prod.right().size() - 1)
follow(result, prod.left());
// A -> ...tok...
else for (Sz j = i + 1; j < prod.right().size(); ++j)
{
bool e = first(result, prod.right()[j]);
if (!e) // 后面无需推导了
break;
// 最后一个符号可能推导出空
else if (j == prod.right().size() - 1)
follow(result, prod.left());
}
}
}
}
// * 获取增广开始符。
token extstart_token() const
{
return _extstart;
}
// * 获取增广结束符。
token extend_token() const
{
return _extend;
}
// * 返回该符号是否是增广开始符。
bool is_extstart(const token t) const { return t == _extstart; }
// * 返回该符号是否是增广结束符。
bool is_extend(const token t) const { return t == _extend; }
};