QParserGenerator代码分析一(生成LALR1 DFA)
以下所说的文法文件均为QParserGenerator的文法文件
产生式
我们将文法文件中形如
| "{String}"
;
形式的式子称为产生式,它由它的左端非终结符(strings)和右端终结符和非终结符组成。
非终结符:非终结符总是出现在产生式的左端,它表示这个条目是由右侧的一些终结符和非终结符推导而来的。
终结符:终结符总是出现在产生式的右端,一般的它总是一个常量字符串或Regex,在文法文件中由最顶端的%token定义出来,内部有一些内置的Regex比如"{Digit}"对应正则表达式为[0-9]+。
上面的文法可分解为两条产生式
strings -> "{String}";
在文法文件中遇到或的关系就可将这条产生式分为若干条左端相同的产生式,只是为了书写形式上的好看,所以在QParserGenerator中支持了|符号。
产生式的结构
首先我们定义出一种结构来描述一个终结符或非终结符
{
enum Type
{
TerminalSymbol,
NoTerminalSymbol,
}type;
Rule rule;
uint index;
#if defined(_DEBUG) && DEBUG_LEVEL == 3
string name;
#endif
Item() : type(NoTerminalSymbol), index(inc()) {}
Item(Rule::Context* pContext) : type(TerminalSymbol), rule(pContext), index(0) {}
#if defined(_DEBUG) && DEBUG_LEVEL == 3
Item(const string& name) : type(NoTerminalSymbol), index(inc()), name(name) {}
#endif
Item(const Item& i)
: type(i.type)
, rule(i.rule)
, index(i.index)
#if defined(_DEBUG) && DEBUG_LEVEL == 3
, name(i.name)
#endif
{
}
Item(const Rule& rule) : type(TerminalSymbol), rule(rule), index(inc()) {}
inline Item& operator=(const Item& i)
{
if (&i != this)
{
type = i.type;
rule = i.rule;
index = i.index;
#if defined(_DEBUG) && DEBUG_LEVEL == 3
name = i.name;
#endif
}
return *this;
}
inline const bool operator<(const Item& x)const
{
return index < x.index;
}
inline const bool operator==(const Item& x)const
{
return index == x.index && type == x.type && (type == TerminalSymbol ? rule == x.rule : true);
}
inline const bool operator!=(const Item& x)const
{
return (index != x.index || type != x.type) || (type == TerminalSymbol ? rule != x.rule : false);
}
inline const bool isNoTerminalSymbol()const
{
return type == NoTerminalSymbol;
}
inline const bool isTermainalSymbol()const
{
return type == TerminalSymbol;
}
static uint inc()
{
static uint i = 0;
return i++;
}
};
只有一个非终结符对象才会用到rule成员对象。
有了这个基本类型之后,让我们来构造出一条产生式的结构
{
public:
Production() {}
Production(const Item& left) : left(left), index(inc()) {}
Production(const Item& left, const Item& item) : left(left), index(inc()) { right.push_back(item); }
Production(const Item& left, const vector<Item>& right) : left(left), right(right), index(inc()) {}
Production(const Production& p) : left(p.left), right(p.right), index(p.index) {}
inline const bool operator<(const Production& p)const
{
return index < p.index;
}
protected:
static uint inc()
{
static uint i = 0;
return i++;
}
public:
Item left;
vector<Item> right;
uint index;
};
正如前面所说,每条产生式的左端总是一个非终结符,而右端是若干的终结符或非终结符,应此我们有了以上结构。
LALR1的产生式
在LALR1中由于每条产生式是带若干个展望符和圆点的,应此我们设计另外一个继承自Production的结构LALR1Production
{
typedef LR0Production parent;
public:
class Item
{
public:
enum { Rule, End }type;
regex::Rule rule;
Item() : type(End) {}
Item(const regex::Rule& rule) : type(Rule), rule(rule) {}
inline const bool operator==(const Item& x)const
{
return type == x.type && (type == End ? true : rule == x.rule);
}
inline const bool operator==(const Production::Item& x)const
{
return type == End ? false : rule == x.rule;
}
inline const bool operator!=(const Item& x)const
{
return type != x.type || (type == End ? true : rule != x.rule);
}
Item& operator=(const Item& x)
{
if (&x == this) return *this;
type = x.type;
if (type == Rule) rule = x.rule;
return *this;
}
};
LALR1Production() : LR0Production() {}
LALR1Production(const Production::Item& left, const vector<Production::Item>& right) : LR0Production(left, right) {}
LALR1Production(const Production::Item& left, const Production::Item& right, size_t pos) : LR0Production(left, right, pos) {}
LALR1Production(const LALR1Production& p) : LR0Production(p), wildCards(p.wildCards) {}
LALR1Production(const LR0Production& p) : LR0Production(p) {}
LALR1Production(const Production& p, size_t pos) : LR0Production(p, pos) {}
inline const bool operator==(const LALR1Production& p)const
{
return static_cast<LR0Production>(*this) == static_cast<LR0Production>(p);
}
inline LALR1Production stepUp()
{
LALR1Production x(*this);
++x.idx;
return x;
}
public:
vector<Item> wildCards;
};
由于历史上的原因我们让LALR1Production继承自LR0Production而不是Production,在LR0Production中只是增加了idx域来表示圆点的位置。而对于增广的产生式(指begin->. 开始符号)总是只带展望符$的,应此我们有了其中的Item结构来表示它是结束符$或是其他的rule。
有了上面两个结构之后,我们便可以开始实现从产生式转换到DFA的过程了。
LALR1的状态和边
LALR1的每个状态中包含有若干条LALR1的产生式应此它的结构就很简单了
{
public:
vector<LALR1Production> data;
uint idx;
Item() : idx(0) {}
void mergeWildCards(Item* pItem)
{
#if defined(_DEBUG) && DEBUG_LEVEL == 3
if (data.size() != pItem->data.size()) throw error<const char*>("compare size error", __FILE__, __LINE__);
#endif
for (size_t i = 0, m = data.size(); i < m; ++i)
{
data[i].wildCards.add_unique(pItem->data[i].wildCards);
}
}
inline const bool operator==(const Item& x)const
{
return data == x.data;
}
static uint inc()
{
static uint i = 0;
return i++;
}
};
struct Edge
{
Item* pFrom;
Item* pTo;
Production::Item item;
Edge(Item* pFrom, Item* pTo, const Production::Item& item) : pFrom(pFrom), pTo(pTo), item(item) {}
inline const bool operator==(const Edge& x)const
{
return pFrom == x.pFrom && pTo == x.pTo && item == x.item;
}
};
而LALR1的一条边是由一个状态通过一个文法符号抵达另一个状态的,所以它也非常形象。
LALR1 DFA生成算法
网上流传着非常多的LALR1 DFA生成算法,其中有比较费时的先生成LR1状态机然后合并同心集来转化到LALR1 DFA的算法,也有较快的展望符传播算法,出于性能的考虑,我们在这里选用的是第二种算法。
算法描述:
首先是自生展望符的计算过程和DFA的生成过程
2.从队列q中拿出一个项目item,并求出这个item中所有的状态转移符号s。
3.对这个item和每个状态转移符号应用go函数求出由这个item可以转换到的其他状态newItem。
4.若转移到的状态newItem不在items列表当中将其加入到队列q和items列表中,否则合并新生成状态newItem和items中原有的对应状态oldItem的展望符列表,并将原有状态oldItem加入到changes列表中。
5.添加一条从item到newItem或oldItem的边,它通过一个文法符号x来转换。
6.循环2直到队列q为空。
下面是传播展望符的部分
7.遍历changes列表,并求出每个状态的状态转移符号s。
8.遍历每个状态转移符号并应用go函数求出新产生的状态newItem,由于新计算出来的状态newItem必定在items列表中,我们只需要将它的展望符做合并即可。
LALR1的核
LALR1的核是由增广项目"begin->. 开始符号“通过某些文法所产生的一些LALR1的最小状态,比如有文法
start -> start "a"
start -> "a"
它的核为
begin -> . start
K1:
begin -> start .
start -> start . "a"
K2:
start -> "a" .
K3:
start -> start "a" .
K0通过文法符号start到达K1,K1通过其中的另外一条产生式到达K2(通过closure函数可求出这个产生式,将会在下文介绍),K1中第二条表达式通过文法符号"a"到达核K3。应此我们说LALR1的核就是增广文法通过一些文法符号所产生的一些最小状态,然后通过闭包函数closure可求出这个状态包含的所有产生式集。
closure(闭包)函数
通过闭包函数可求出LALR1最小状态中拓展出来的其他产生式,应此它有一个核作为输入和一个LALR1状态作为输出,它的算法描述如下
2.从队列q中取出一个元素p。
3.若p是一个待约项目(圆点右边是一个非终结符)那么继续执行4,否则循环到2。
4.求这个产生式的AFirst集合记作v。
5.遍历所有左侧是p圆点之后非终结符且圆点不在最左侧的产生式i。
6.若求出的AFirst集合v为空,则将p的展望符集中的所有元素插入到i中,否则将v中的每个元素插入到i中。
7.若i已存在于输出状态item则将它的展望符合并到原产生式中,否则将这个产生式i插入到输出状态item和队列q中。
8.循环2知道队列q为空为止。
通过以上函数便可求出每个核K所对应的LALR1状态item。
AFirst函数
AFirst函数其实就是求这个产生式圆点后第二个符号的First集合。
First函数
First函数返回的是一些终结符的集合,应此若输入的是一个非终结符,它会去查看所有左端是这个非终结符的产生式的右侧第一个符号,若它仍然是一个非终结符则继续递归下去,否则将这个终结符加入到输出集合中。而为了不产生死循环,它不会处理左递归的产生式。
go(状态转移)函数
状态转移函数有两个输入分别为某个状态item和一个文法符号x以及一个输出newItem,表明item状态通过文法符号x达到newItem状态。它的算法描述如下
2.若i不是一个归约项目(圆点在最后)则将其加入集合j中。
3.若集合j不为空,则求取j的闭包作为输出状态newItem。
当然通过go函数求出来的新状态是有可能已经存在的。
通过上面这些算法的描述,我们已经可以求出一个完整的LALR1 DFA了。下面我们来看看这些算法的代码会是什么样的。
{
vector<LALR1Production> v;
v.push_back(inputProductions[begin][0]);
pStart = closure(v);
pStart->idx = Item::inc();
context.states.insert(pStart);
items.push_back(pStart);
queue<Item*> q;
q.push(pStart);
vector<Item*> changes;
while (!q.empty())
{
Item* pItem = q.front();
vector<Production::Item> s;
symbols(pItem, s);
select_into(s, vts, compare_production_item_is_vt, push_back_unique_vector<Production::Item>);
select_into(s, vns, compare_production_item_is_vn, push_back_unique_vector<Production::Item>);
for (vector<Production::Item>::const_iterator i = s.begin(), m = s.end(); i != m; ++i)
{
Item* pNewItem = NULL;
if (go(pItem, *i, pNewItem))
{
long n = itemIndex(pNewItem);
if (n == -1)
{
pNewItem->idx = Item::inc();
q.push(pNewItem);
items.push_back(pNewItem);
context.states.insert(pNewItem);
}
else
{
items[n]->mergeWildCards(pNewItem);
changes.push_back_unique(items[n]);
destruct(pNewItem, has_destruct(*pNewItem));
Item_Alloc::deallocate(pNewItem);
}
edges[pItem].push_back_unique(Edge(pItem, n == -1 ? pNewItem : items[n], *i));
}
}
q.pop();
}
for (vector<Item*>::const_iterator i = changes.begin(), m = changes.end(); i != m; ++i)
{
vector<Production::Item> s;
symbols(*i, s);
for (vector<Production::Item>::const_iterator j = s.begin(), n = s.end(); j != n; ++j)
{
Item* pNewItem = NULL;
if (go(*i, *j, pNewItem))
{
long n = itemIndex(pNewItem);
if (n == -1) throw error<const char*>("unknown item", __FILE__, __LINE__);
else items[n]->mergeWildCards(pNewItem);
destruct(pNewItem, has_destruct(*pNewItem));
Item_Alloc::deallocate(pNewItem);
}
}
}
return true;
}
LALR1::Item* LALR1::closure(const vector<LALR1Production>& kernel)
{
Item* pItem = Item_Alloc::allocate();
construct(pItem);
queue<LALR1Production> q;
for (vector<LALR1Production>::const_iterator i = kernel.begin(), m = kernel.end(); i != m; ++i)
{
pItem->data.push_back(*i);
q.push(*i);
}
while (!q.empty())
{
const LALR1Production& p = q.front();
if (p.idx < p.right.size() && p.right[p.idx].isNoTerminalSymbol()) // 待约项目
{
vector<Production::Item> v;
firstX(p, v, p.idx + 1);
for (vector<LALR1Production>::iterator i = inputProductions[p.right[p.idx]].begin(), m = inputProductions[p.right[p.idx]].end(); i != m; ++i)
{
if (i->idx > 0) continue;
LALR1Production& item = *i;
if (v.empty()) item.wildCards.add_unique(p.wildCards);
else
{
for (vector<Production::Item>::const_iterator j = v.begin(), n = v.end(); j != n; ++j)
{
item.wildCards.push_back_unique(LALR1Production::Item(j->rule));
}
}
vector<LALR1Production>::iterator j = find(pItem->data.begin(), pItem->data.end(), item);
if (j == pItem->data.end())
{
q.push(item);
pItem->data.push_back(item);
}
else j->wildCards.add_unique(item.wildCards);
}
}
q.pop();
}
return pItem;
}
void LALR1::firstX(const LALR1Production& p, vector<Production::Item>& v, size_t idx)
{
if (idx >= p.right.size()) return;
first(p, v, idx);
}
void LALR1::first(const LALR1Production& p, vector<Production::Item>& v, size_t idx)
{
#ifdef _DEBUG
if (idx >= p.right.size())
{
throw error<const char*>("position out of right size", __FILE__, __LINE__);
return;
}
#endif
if (p.right[idx].isTermainalSymbol())
{
v.push_back_unique(p.right[idx]);
return;
}
for (vector<LALR1Production>::const_iterator i = inputProductions[p.right[idx]].begin(), m = inputProductions[p.right[idx]].end(); i != m; ++i)
{
if (i->left == i->right[0]) continue;
if (i->right[0].isTermainalSymbol())
{
v.push_back_unique(i->right[0]);
continue;
}
else
{
first(*i, v, 0);
}
}
}
void LALR1::symbols(Item* pItem, vector<Production::Item>& v)
{
for (vector<LALR1Production>::const_iterator i = pItem->data.begin(), m = pItem->data.end(); i != m; ++i)
{
if (i->idx < i->right.size()) v.push_back_unique(i->right[i->idx]);
}
}
bool LALR1::go(Item* pItem, const Production::Item& x, Item*& newItem)
{
vector<LALR1Production> j;
for (vector<LALR1Production>::iterator i = pItem->data.begin(), m = pItem->data.end(); i != m; ++i)
{
if (i->idx < i->right.size() && i->right[i->idx] == x) j.push_back_unique(i->stepUp());// fromItoJ(*i, j);
}
if (j.empty()) return false;
newItem = closure(j);
return true;
}
其实代码并不算多,只是描述起来有些麻烦罢了。
QParserGenerator就先介绍到这里,接下来一篇文章将会介绍一个例子来说明某个文法是如何变成LALR1 DFA的。最后完整的代码可到http://code.google.com/p/qlanguage/下载。