岁月流转,往昔空明

C++博客 首页 新随笔 联系 聚合 管理
  118 Posts :: 3 Stories :: 413 Comments :: 0 Trackbacks

Exodus:语法分析(一)

在一个魔法世界里,你是一个会魔法的法师。我的意思是,作为一个法师,你什么都会了,也什么都有了,施法材料,法袍,魔杖,法术书。甚至你连成功后的庆祝动作都想好了。你以为你会“魔法”了。只可惜,这里还缺少了一样东西,那就是,魔法的口诀。

而在这里,我们什么都有了。用来分析的Token,语法树到OP CODE的翻译,虚拟机,什么都有了。但是我们还是缺一样口诀,那就是,如何从Token到语法树的口诀。

在我们进行词法分析的时候,遵从的是Spirit这本颇有难度的《圣经》。不过,我们只浏览了如《使徒行传》般流畅而松散的Spirit.Lex。在这里,我们依然沿用Spirit,这是我们编译器前端的原旨。不过现在,我们要讲解的是环环相扣、荡气回肠的《Exodus》——Spirit.Qi。

嘛,这段神叨叨的引子,只是为了强调语法分析的地位而已。在继续阅读本章之前,需要你看的明白BNF。有关于BNF方面的信息,你可以在任何一本讲述编译原理的书籍上找到。

仍然是以一个简单的A+B为例。由于我们已经有了词法“literal_int”和“literal_add”,因此A+B这样一个表达式,用BNF来表示,就是:

Expr ::= literal_int literal_add literal_int

在Spirit.Qi里,语法的表达也类似于BNF的形式。只要你设计出语言的BNF,就很容易的翻译成Spirit.Qi支持的语法定义。我们这里,就可以写成:

template <typename IteratorT>

struct binary_expression: qi::grammar<IteratorT>{

    template <typename TokenDefT> binary_expression(const TokenDefT& tok): binary_expression::base_type(start)

    {

        start = (  literal_int >> literal_op >> literal_int );

        literal_int = tok.littok_int;

        literal_op = tok.optok_add;

    }

    qi::rule<IteratorT> literal_op, literal_int, start;

};

在Spirit.Qi中,一个Rule就等于EBNF的一个非终结符。一个Grammar相当于一组Rule的集合,并且可以拥有一个或者多个的起始Rule作为入口。本质上我们可以把Grammar看成一个Rule(准确的说,是Parser,若要了解相关概念,请参阅Spirit的自定义Parser部分)。等号用于连接非终结符(Rule)及其推导式;使用“>>”(输入流/右位移运算符)连接语法要素之间的连接符号。更多的符号请参阅Spirit.Qi文档。

至于为什么不将Rule合并到一起,而提供一个Grammar的中间层,主要有两方面的考虑,一个是提供了一个抽象层,例如我们可以把Statement和Expression分开来写,使得层次上更加清晰;还有一个方面在于节省编译时间。因为Spirit使用了大量的源编程技术,如果把所有的Rule合并到一起编译,会占用大量的编译时间。在使用了Grammar之后,可以运用C++编译器在一个编译过程里对相同的模板特化只进行一次的Tricks,大大节省了编译时间。

在上一章里,咱们最后还留了一个问题,就是空白符号的处理方法。这里,我们将于空白符号一起,来走一下Spirit的语法和词法分析的流程。

首先,我们建立好词法,将源代码字符流组织成更加容易被语法分析识别的Token流。

template <typename BaseLexerT>

struct sasl_tokens : public boost::spirit::lex::lexer< BaseLexerT > {

    sasl_tokens(){

        this->self.add_pattern("SPACE", "[ \\t\\v\\f]+");

 

        littok_int = "[0-9]+";

        optok_add = "[\\+]");

        whitetok_space = "{SPACE}";

       

        this->self = littok_int | optok_add;

        this->self("SKIPPED") = whitetok_space;

    }

    boost::spirit::lex::token_def<>

        littok_int, optok_add, whitetok_space;

};

这里,我们将词法分为两组,对语法分析有效的Tokens组和无效的空白组,空白组用”Skipped”作为状态以示区别。这里我们需要说明一下,Spirit.LEX的词法分析的“状态”与词法分析工具“Lex/Flex”中的状态概念是相同的。

在Lex类的词法分析工具里,有一个专门的状态。一般而言,这些状态都用字符串表示。Lex的默认是“INITIAL”,Spirit.Lex的默认状态是空(如果我没记错的话)。在指定词法的时候,可以告诉词法分析器,此文法在什么状态下,这条词法才发挥作用。词法分析器的状态可以由外部程序自由指定。

我们将表示空白的词法都放在Skipped状态下后,我们就可以对每个单词,用Skipped状态去匹配。如果发现是在Skipped状态下匹配成功的单词,在进入语法分析前就可以先丢弃,进而实现过滤空白符的目的。

考虑表达式“55_+38”(‘_’代表空格),在分析成Token流之后,会变成以下的形式:

State

INITIAL

SKIPPED

INITIAL

INITIAL

Token

Literal_int

Literal_ws

Literal_op

Literal_int

Literal

55

_

+

38

然后撰写我们的Grammar。由于我们需要指定Skipper来跳过我们不需要的Token,因此我们的Grammar在模板参数里,也要加入这个Skipper的类型参数。

template <typename IteratorT, typename LexerT>

struct binary_expression:

qi::grammar<IteratorT, qi::in_state_skipper<LexerT> >

{

    template <typename TokenDefT>

binary_expression(const TokenDefT& tok):

    binary_expression::base_type(start)

    {

        start = (  literal_int >> literal_op >> literal_int );

        literal_int = tok.littok_int;

        literal_op = tok.optok_add;

    }

boost::spirit::qi::in_state_skipper<LexerT> skipper_type;

    qi::rule<IteratorT, skipper_type> literal_op, literal_int, start;

};

并在咱们的驱动代码里面,这样写:

typedef sasl_tokenizer::iterator_type sasl_token_iterator;

typedef sasl_tokenizer::lexer_def sasl_skipper;

 

sasl_tokenizer sasl_tok;

binary_expression<sasl_token_iterator, sasl_skipper> g( sasl_tok );

 

lex::tokenize_and_phrase_parse(

first,

last,

sasl_tok,

g, qi::in_state("SKIPPED")[sasl_tok.self]

);

喏,看到了指定skipper的代码了不?这就提示parser,遇到了skipped状态解析出来的token,就自己吃了吧,不要拿去匹配了。这样就达到了过滤掉空白符的目的。

不过呢,尽管我们parse通过了,但是仍然没有提取出我们想要的信息来。到目前为止,我们还没能让parser构造出咱们之前手工构建并传递给Code Generator的语法树来。这仍然是横亘在出埃及的我们面前的红海。

下一次,我们将仍然相信Spirit这本Bible,相信它给我们的一章叫 “Semantic Action”的启示录。它将告诉我们,如何把Parser分析出的结果转化为我们要的语法树,以引领我们走向流OP CODE之地。

God bless programmers and p2p sites except gfw’s developers and Cisco. Amen

posted on 2009-12-15 23:35 空明流转 阅读(2364) 评论(1)  编辑 收藏 引用

评论

# re: 实用编译器构建指南(四) 2010-01-07 11:09 正心
It looks like you've read so many God's books! :)~~~  回复  更多评论
  


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