随笔-341  评论-2670  文章-0  trackbacks-0
    上一篇文章中我们看到了可配置语法分析器使用起来的样子,在这篇文章中我将告诉大家如何通过重载操作符的方法构造文法表达式树,从而使用递归向下法进行语法分析的工作。

    在这之前我们将研究一下什么是文法表达式。我们将文法表达式看成分析器,于是复杂的文法表达式就是由简单的分析器通过各种方法组合起来的复杂分析器。一个分析器有以下几个属性:

    1:输入类型。输入类型通常是一个字符串的指针还是迭代器什么的,具体类型不重要,重要的是输入状态必须能被复制,能跳到下一个元素。当然wchar_t*也满足这种要求,但是我们为了通用性(譬如可以为你自己的容器扩展出一个输入迭代器)我们采用类似STL的迭代器的方法,也就是concept(这并没有包含其技巧,只是概念)来实现。然后库将为一些基本的东西提供默认的迭代器,譬如IEnumerable<T>(嗯嗯,这不是C#,已经被Vczh Library++实现了。容器采用了一种泛型+接口的方法,但是在不必要的情况下允许不支付虚函数的代价,不过这根本章内容无关,以后再谈),或者WString和AString。

    2:输出类型。输出类型一般包含两个方面。第一个是成功后的结果,第二个是失败后的错误信息。怎么让可配置语法分析器在恰当的地方输出类型也是一个很复杂的问题,不过这根本章内容无关,下一篇文章接着讲这个细节。在这里我们先忽略错误信息,就如同正则表达式拒绝匹配一个字符串也不会告诉你为什么一样。

    我们可以通过这两种属性来构造出一个文法表达式的基类。表达式树通常用基类+若干子类的方法来实现,有了基类等于定下了子类的基调。
1 template<typename I, typename O>
2 class Expression
3 {
4 public:
5   virtual Maybe<O> Parse(I& input)=0;
6 };

    Maybe指的是里面可以有类型O的值,或者什么都没有。这额外的信息可以添加一个bool来表达,这里就不赘叙了。到了这里我们明白一个表达式树的重点不是其内容,而是分析输入的算法。因此我们可以组合出连接、分支和循环:
 1 template<typename I, typename O1, typename O2>
 2 class Sequence : public Expression<I, ParsingPair<O1, O2>>;
 3 {
 4 public:
 5   Ptr<Expression<I, O1>> left;
 6   Ptr<Expression<I, O2>> right;
 7 
 8   Maybe<ParsingPair<O1, O2>> Parse(I& input);
 9 };
10 
11 template<typename I, typename O>
12 class Alternate : public Expression<I, O>;
13 {
14 public:
15   Ptr<Expression<I, O>> left;
16   Ptr<Expression<I, O>> right;
17 
18   Maybe<O> Parse(I& input);
19 };
20 
21 template<typename I, typename O>
22 class Loop : public Expression<I, ParsingList<O>>;
23 {
24 public:
25   Ptr<Expression<I, O>> element;
26   int min;
27   int max;
28 
29   Maybe<ParsingList<O>> Parse(I& input);
30 };

    这就是连接、分支和循环的声明了。现在我们可以很清楚的了解什么是带类型的文法了。类型主要指的是输出类型,而输入类型肯定是不能变化的。ParsingPair<A, B>就是一个带两个数据的结构,而ParsingList<T>是一个链表。做成这样主要是为了在传递他们的时候不要做太多浪费的复制工作,在这里我们只需要了解其概念就好了。

    每一种组合都对子文法的类型有着一些要求,譬如说分支要求左右文法表达式的类型是一样的。而且输出类型是通过子文法的类型计算而得到的。Ptr<T>是智能指针,在这里使用主要是为了避免复制的时候出现问题。智能指针在这种数据结构下还是十分好用的,反正构造和析构一条文法的效率都是无关紧要的,不要太慢就可以了。

    但是我们如何重载操作符来组合文法表达式呢?其实文法表达式最终产生的结果都是Ptr<Expression<I, O>>,Ptr的操作符重载是不能修改的,所以我们还要一个代理类:
 1 template<typename I, typename O>
 2 class Node
 3 {
 4 public:
 5   Ptr<Expression<I, O>> expression;
 6 };
 7 
 8 template<typename I, typename O>
 9 Node<I, O> operator|(const Node<I, O>& left, const Node<I, O>& right);
10 
11 template<typename I, typename O1, typename O2>
12 Node<I, ParsingPair<O1, O2>> operator+(const Node<I, O1>& left, const Node<I, O2>& right);
13 
14 //除了+以外,还可以继承*啊,或者干脆写个loop(node, min, max)什么的
15 template<typename I, typename O>
16 Node<I, ParsingList<O>> operator+(const Node<I, O>& element);

    我们就可以在每一个操作符重载里面将各自Node的expression成员变量拿出来,然后通过构造上面提供的Sequence、Alternate和Loop来构造更加复杂的文法表达式,最后重新装进一个Node<I, O>里面就行了。

    这就是我们可以将文法写进C++的小技巧。下一篇文章我们将会了解到表达式的每一个Parse函数内部都做了些什么。
posted on 2009-12-04 23:43 陈梓瀚(vczh) 阅读(3171) 评论(1)  编辑 收藏 引用 所属分类: VL++3.0开发纪事

评论:
# re: Vczh Library++ 3.0之可配置语法分析器(设计文法表达式) 2009-12-05 06:59 | radar
精辟啊!  回复  更多评论
  

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理