??xml version="1.0" encoding="utf-8" standalone="yes"?>亚洲阿v天堂在线,亚洲成Av人片乱码色午夜,精品亚洲成α人无码成α在线观看 http://m.tkk7.com/landy/category/10830.html像狼一样凶?/description>zh-cnThu, 01 Mar 2007 02:45:18 GMTThu, 01 Mar 2007 02:45:18 GMT60JavaCC、解析树(wi)?XQuery 语法Q第 2 部分http://m.tkk7.com/landy/archive/2006/05/06/44767.html独孤q客独孤q客Sat, 06 May 2006 08:23:00 GMThttp://m.tkk7.com/landy/archive/2006/05/06/44767.htmlhttp://m.tkk7.com/landy/comments/44767.htmlhttp://m.tkk7.com/landy/archive/2006/05/06/44767.html#Feedback0http://m.tkk7.com/landy/comments/commentRss/44767.htmlhttp://m.tkk7.com/landy/services/trackbacks/44767.html本文的第 1 部分要讨Z(jin)语法、解析器?BNF。然后它介绍?JavaCCQ一个流行的解析器生成器。第 2 部分演示?jin)如何修改?1 部分中的h代码Q这样就可以使用附加工具 JJTree 来构建相同解析的解析?wi)表C。?zhn)探索这U方法的优点Qƈ研究如何~写 Java 代码在运行时遍历该解析树(wi)以便恢复其状态信息,q对正在解析的表辑ּ求倹{本文结ְ演示如何开发通用例程Q用于遍历从一部?XQuery 语法生成的解析树(wi)Qƈ对其求倹{?/blockquote>

使用 JavaCC 解析器生成器有一个严重缺点:(x)许多或大多数客户机端 Java 代码需要嵌入到 .jj 语法脚本中,该脚本编码了(jin)(zhn)的 BNFQ巴U斯-诺尔范式QBackus-Naur FormQ。这意味着(zhn)失M(jin)在开发周期中合适的 Java IDE 可以向?zhn)提供的许多优炏V?/p>

开始?JJTree 吧,它是 JavaCC 的伙伴工兗JJTree 被设|成提供一个解析器Q该解析器在q行时的主要工作不是执行嵌入?Java 操作Q而是构徏正在解析的表辑ּ的独立解析树(wi)表示。这P(zhn)就可以独立于生成该解析?wi)的解析代码Q捕捉在q行时易于遍历和查询的单个树(wi)中的解析?x)话的状态。用解析树(wi)表示q会(x)使调试变得更Ҏ(gu)Qƈ~短开发时间。JJTree 是作?JavaCC 分发版(distributionQ的一部分发布的(请参?参考资?/font>Q?

在我们l之前,我要特别提一下,术语 解析?/b>?抽象语法?/b>Q或 ASTQ描qC(jin)非常怼的语法结构。严格地Ԍ对于我在下面提到的解析树(wi)Q语a理论家更_地把它称?AST?

要?JJTreeQ?zhn)需要能够:(x)

  1. 创徏 JJTree 作ؓ(f)输入获取?.jjt 脚本
  2. ~写客户机端代码以遍历在q行时生成的解析?wi)ƈ对其求?

本文演示?jin)如何执行这两种操作。它q没有涵盖所有内容,但肯定能带?zhn)入门?/p>

JJTree 基础知识

JJTree 是一个预处理器,为特?BNF 生成解析器只需要简单的两步Q?/p>

  1. Ҏ(gu)谓的 .jjt 文gq行 JJTreeQ它?x)生一个中间的 .jj 文g
  2. ?JavaCC ~译该文Ӟ W?1 部分中讨Z(jin)q个q程Q?

q运的是Q?jjt 文g的结构只是我在第 1 部分中向(zhn)显C的 .jj 格式的较?yu)扩展。主要区别是 JJTree d?jin)一个新的语?node-constructor构造,该构造可以让(zhn)指定在解析期间在哪里以?qing)在什么条件下生成解析?wi)节炏V换句话_(d)该构造管理由解析器构造的解析?wi)的形状和内宏V?

清单 1 昄?jin)一个简单的 JavaCC .jj 脚本Q它cM于?zhn)在?1 部分中看到的脚本。ؓ(f)便v见,我只昄?jin)结果?/p>
清单 1. simpleLang ?JavaCC 语法
												
														void simpleLang()   : {}    { addExpr() <EOF> }

void addExpr()      : {}    { integerLiteral() ( "+" integerLiteral() )? }

void integerLiteral()   : {}    { <INT> }

SKIP  : { " " | "\t" | "\n" | "\r" }

TOKEN : { < INT : ( ["0" - "9"] )+ > }


												
										

该语法说明了(jin)该语a中的合法表达式包含:(x)

  1. 单个整数文字Q或
  2. 一个整数文字,后面跟一个加P再跟另一个整数文字?

对应?JJTree .jjt 脚本Q再ơ声明,略有化)(j)看上d能如下:(x)


清单 2. {h(hun)于清?1 中的 JavaCC 语法?JJTree
												
														SimpleNode simpleLang() : #Root       {}  { addExpr() <EOF> { return jjtThis; }}

void addExpr()          :             {}  { integerLiteral()

                                          ( "+" integerLiteral() #Add(2) )? }

void integerLiteral()   : #IntLiteral {}  { <INT> }

SKIP  : { " " | "\t" | "\n" | "\r" }

TOKEN : { < INT : ( ["0" - "9"] )+ > }


												
										

该脚本对(zhn)已l看到的脚本d?jin)一些新的语法特性。现在,我们只讨论突出显C的部分。以后,我会(x)详细说明?/p>

回页?/font>


逐句说明 JJTree 语法

首先h意,最层?simpleLang() l果?JavaCC 的过E性语法现在指定了(jin)一个返回类型:(x) SimpleNode 。它与嵌入的 Java 操作 return jjtThis Q有一点ؓ(f) JJTree 虚张声势Q一h定了(jin)从应用程序代码调用解析器?simpleLang() Ҏ(gu)返回解析树(wi)的根Q然后这个根用于树(wi)遍历?

?JavaCC 中,解析器调用看上去如下Q?/p>
												
														   SimpleParser parser = new SimpleParser(new StringReader( expression ));

    parser.simpleLang();


												
										

而现在看上去象下面这P(x)

												
														   SimpleParser parser = new SimpleParser(new StringReader( expression ));

    SimpleNode rootNode = parser.simpleLang();


												
										

注:(x)所抓取的根节点q不仅仅?SimpleNode cd。它其实?Root cdQ正?清单 2 中的 #Root 伪指令所指定的(虽然(zhn)不?x)在上述调用代码中那样用?j)?Root ?SimpleNode 子代Q就?JJTree 生成的解析器构造的每个节点一栗我在下面向?zhn)昄?SimpleNode 的一些内|方法?

addExpr() l果中的 #Add(2) 构造与上述?#Root 伪指令不同,体现在以下几斚wQ?

  • 它是参数化的。树(wi)构徏器在构造树(wi)期间使用节点堆栈Q没有参数的节点构徏器的~省行ؓ(f)是将自己攑֜正在构造的解析?wi)的剙Q将所有节点弹出在同一?节点作用?/i> 中创建的节点堆栈Qƈ把自己提升到那些节点父代的位|。参?2 告诉新的父节点(在此CZ中是一?Add 节点Q要恰好采用 两个子节点,它们?下一D|?/font> 中描q的两个 IntLiteral 子节炏VJJTree 文档更详l地描述?jin)这个过E。用好的调试器在运行时遍历解析?wi)是另一个宝늚辅助Ҏ(gu)Q它有助于理解树(wi)构徏?JJTree 中是如何工作的?
  • ?#Root 伪指令放在其l果的主体之外表C?每次 遍历该结果时都会(x)生成一?Root 节点Q而在此特定示例中Q只允许发生一ơ)(j)Q这一点具有同{的重要?。但是,?#Add(2) 伪指令放在可选的“零或一”项中表CZ当在解析期间遍历包含它的选择子句??换句话说Q当该结果表CZ个真的加法操作时 ??有条件地 生成一?Add 节点。当发生q种情况Ӟ?x)遍?integerLiteral() 两次Q每ơ调用时都将一?IntLiteral 节点d到树(wi)上。这两个 IntLiteral 节点都成用它们的 Add 节点的子节点。但是,如果正在解析的表辑ּ是单个整敎ͼ那么作ؓ(f)l果?IntLiteral 节点直接成?Root 的一个子节点?

一图胜千言Q引用一句古老的谚语Q。以下是׃q语法生成的两种cd的解析树(wi)的图形表C:(x)


?1Q单个整数表辑ּ的解析树(wi)


?2Q加法操作的解析?

让我们更详细地研I?SimpleNode 的类层次l构?



回页?/font>


使用解析?/font>

?.jjt 脚本中声明的每个节点都指C析器生成 JJTree SimpleNode 的一个子cR接下来Q?SimpleNode 又实现名?Node ?Java 接口。这两个cȝ源文仉是由 JJTree 脚本和定?.jj 文g一赯动生成的?清单 1 昄?jin)定?.jj 文g的当前示例。在当前CZ中,JJTree q提供了(jin)(zhn)自q Root ?Add ?IntLiteral cM?qing)没有在q里看到的一些附加的助手cȝ源文件?

所?SimpleNode 子类都承了(jin)有用的行为?SimpleNode Ҏ(gu) dump() 是q样一个例子。它q充当了(jin)我以前的论点Q用解析树(wi)使调试更Ҏ(gu)Q从而羃短开发时_(d)(j)的示例。以下三行客h端代码的代码片段实例化了(jin)解析器、调用解析器、抓取所q回的解析树(wi)Qƈ且将一个简单的解析?wi)的文本表示转储到控制台Q?

												
														   SimpleParser parser = new SimpleParser(new StringReader( expression ));

    SimpleNode rootNode = parser.simpleLang();

    rootNode.dump();


												
										

?2 中的?wi)的调试输出是?x)

												
														   Root

        Add

            IntLiteral

            IntLiteral


												
										



回页?/font>


辅助D

另一个有用的内置 SimpleNode Ҏ(gu)?jjtGetChild(int) 。当(zhn)在客户机端向下览解析?wi),q且遇到 Add 节点Ӟ(zhn)会(x)要抓取它?IntLiteral 子节炏V抽取它们表C的整数|q将q些数字加到一??毕竟Q那是用来练?fn)的。假设下一D代码中昄?addNode 是表C我们感兴趣?Add cd节点的变量,那我们就可以讉K addNode 的两个子节点。( lhs ?rhs 分别?左边Qleft-hand sideQ?/i>?双Qright-hand sideQ?/i>的常用羃写。)(j)

												
														    SimpleNode lhs = addNode.jjtGetChild( 0 );

     SimpleNode rhs = addNode.jjtGetChild( 1 );


												
										

即到目前ؓ(f)止?zhn)已经执行了(jin)所有操作,但?zhn)仍然没有_的信息来计算该解析树(wi)表示的算术运的l果。?zhn)的当前脚本已l省略了(jin)一个重要的l节Q树(wi)中的两个 IntLiteral 节点实际上不包含它们声称要表C的整数。那是因为当记号赋予器(tokenizerQ在输入中遇到它们Ӟ(zhn)没有将它们的g存到?wi)中Q?zhn)需要修?integerLiteral() l果来执行该操作。?zhn)q需要将一些简单的存取器方法添加到 SimpleNode ?



回页?/font>


保存和恢复状?/font>

要将所扫描的记L(fng)值存储到适当的节点中Q将以下修改d?SimpleNode Q?

												
														   public class SimpleNode extends Node

    {

        String m_text;

        public void   setText( String text ) { m_text = text; }

        public String getText()              { return m_text; }

        ...

    }


												
										

?JJTree 脚本中的以下l果Q?/p>
												
														   void integerLiteral() : #IntLiteral {} <INT> }


												
										

更改成:(x)

												
														   void integerLiteral() : #IntLiteral { Token t; }

                                        { t=<INT> { jjtThis.setText( t.image );} }


												
										

该结果抓取它刚在 t.image 中遇到的整数的原始文本|q用?zhn)?setText() setter Ҏ(gu)该字符串存储到当前节点中?清单 5 中的客户机端 eval() 代码昄?jin)如何用相应?getText() getter Ҏ(gu)?

可以很容易地修改 SimpleNode.dump() Q以提供M节点?m_text g其在解析期间存储 ?我把q作Z个众所周知的练?fn)留l?zhn)来完成。这让(zhn)更形象地理解在q行调试时解析树(wi)看v来是什么样子。例如,如果(zhn)解析了(jin)?2 + 1”,略经修改?dump() 例程可以生成以下有用的输出:(x)

												
														   Root

        Add

            IntLiteral[42]

            IntLiteral[1]


												
										



回页?/font>


l合QXQuery ?BNF 代码片段

让我们通过研究实际语法的一个代码片D|q行l合和ȝ。我向(zhn)演CZD非常小?XQuery ?BNF 子集Q这?XML 的查询语a?W3C 规范Q请参阅 参考资?/font>Q。我在这里所说的大多数内容也适用?XPathQ因两者共享了(jin)许多相同的语法。我q将要地研究q算W优先的问题,q将?wi)遍历代码推q到成熟的递归例程中,该例E可以处理Q意复杂的解析?wi)?

清单 3 昄?jin)(zhn)要用?XQuery 语法片段。这D?BNF 摘自 2002 q?11 ?15 日的工作草案Q?


清单 3Q一部分 XQuery 语法
												
														[21]  Query              ::= QueryProlog QueryBody

    ...

[23]  QueryBody          ::= ExprSequence?

[24]  ExprSequence       ::= Expr ( "," Expr )*

[25]  Expr               ::= OrExpr

    ...

[35]  RangeExpr          ::= AdditiveExpr ( "to"  AdditiveExpr )*

[36]  AdditiveExpr       ::= MultiplicativeExpr (("+" | "-") MultiplicativeExpr )*

[37]  MultiplicativeExpr ::= UnionExpr (("*" | "div" | "idiv" | "mod") UnaryExpr )*

      ...


												
										

(zhn)将要构Z个刚好适合?JJTree 语法脚本来处理结?[36] ?[37] 中的 + ?- ?* ?div q算W,而且单地假设该语法所知道的唯一数据cd是整数。该h语法 非常,q不能妥善处?XQuery 支持的丰富的表达式和数据cd。但是,如果(zhn)要为更大、更复杂的语法构析器Q它应该能给(zhn)?JavaCC ?JJTree 的入门知识?

清单 4 昄?.jjt 脚本。请注意该文仉部的 options{} 块。这些选项Q还有许多其它可用选项开养I(j)指定?jin)其中?wi)构徏在本例中是以 多重 方式q行的,卌Ҏ(gu)造器用于昑ּ地命名所生成节点的类型。备用方法(不在q里研究Q是l果只将 SimpleNode 节点提供l解析树(wi)Q而不是它的子cR如果?zhn)惌避免扩散节点c,那么该选项很有用?

q请注意原始?XQuery BNF l常多个运符l合到同一个结果中。在 清单 4中,我已l将q些q算W分d JJTree 脚本中的单独l果中,因ؓ(f)q让客户机端的代码更单。要q行l合Q只需存储已扫描的q算W的|pҎ(gu)数所q行的操作一栗?


清单 4Q清?3 中的 XQuery 语法?JJTree 脚本
												
														options {

   MULTI=true;

   NODE_DEFAULT_VOID=true;

   NODE_PREFIX="";

}

PARSER_BEGIN( XQueryParser )

package examples.example_2;

public class XQueryParser{}

PARSER_END( XQueryParser )

SimpleNode query()     #Root      : {} { additiveExpr() <EOF> { return jjtThis; }}

void additiveExpr()               : {} { subtractiveExpr()

                                       ( "+" subtractiveExpr() #Add(2) )* }

void subtractiveExpr()            : {} { multiplicativeExpr()

                                       ( "-" multiplicativeExpr() #Subtract(2) )* }

void multiplicativeExpr()         : {} { divExpr() ( "*" divExpr() #Mult(2) )* }

void divExpr()                    : {} { integerLiteral()

                                       ( "div" integerLiteral() #Div(2) )* }

void integerLiteral() #IntLiteral :    { Token t; }

                                       { t=<INT> { jjtThis.setText(t.image); }}

SKIP  : { " " | "\t" | "\n" | "\r" }

TOKEN : { < INT : ( ["0" - "9"] )+ > }


												
										

?.jjt 文g引入?jin)几个新的特性。例如,该语法中的运结果现在是 q代?/i> Q通过使用 * Q零ơ或多次Q发生指C符来表C它们的可选的W二,q与 清单 2 中的 ? Q零ơ或一ơ)(j)表示法相反。该脚本所提供的解析器可以解析L长的表达式,如? + 2 * 3 div 4 + 5”?

实现优先U?/font>

该语法还知道 q算W优先。例如,(zhn)期望乘法的优先U比加法高。在实际例子中,q表C如? + 2 * 3”这L(fng)表达式将作ؓ(f)? + ( 2 * 3 )”进行求|而不是? 1 + 2 ) * 3”?

优先U是通过使用U联样式实现的,其中每个l果?x)调用紧随其后的较高优先U的l果。联样式和节点构造的位置和格式保证了(jin)以正的l构生成解析?wi),q样?wi)遍历可以正执行。用一些直观图形也许更易于理解q一炏V?/p>

?3 昄?jin)由此语法生成的解析树(wi),它可以让?zhn)正地? + 2 * 3”当作? + ( 2 * 3 )”进行求倹{请注意Q?Mult q算W与它的之间的联系?Plus 更紧密,而这正是(zhn)希望的Q?


?3. l构正确的树(wi)

??4昄的树(wi)Q该语法 不会(x)生成q样的树(wi)Q表C?zhn)Q错误地Q想要将该表辑ּ当作?1 + 2) * 3”求倹{?


?4. l构不正的?



回页?/font>


遍历解析?wi)客h?/font>

p我曾{应的,我将用客h端代码的清单作ؓ(f)ȝQ该清单调用该解析器ƈ遍历它生成的解析?wi),它用简单而功能强大的递归 eval() 函数Ҏ(gu)(wi)遍历旉到的每个节点执行正确操作?清单 5中的注释提供?jin)关于内?JJTree 工作的附加详l信息?


清单 5. 可容易泛化的 eval() 例程
												
														   // return the arithmetic result of evaluating 'query'

    public int parse( String query )

    //------------------------------

    {

        SimpleNode root = null;

     // instantiate the parser

        XQueryParser parser = new XQueryParser( new StringReader( query ));

            try {
            // invoke it via its topmost production

            // and get a parse tree back

            root = parser.query();

            root.dump("");

        }

        catch( ParseException pe ) {

            System.out.println( "parse(): an invalid expression!" );

        }

        catch( TokenMgrError e )  {

            System.out.println( "a Token Manager error!" );

        }

        // the topmost root node is just a placeholder; ignore it.

        return eval( (SimpleNode) root.jjtGetChild(0) );

    }

    int eval( SimpleNode node )

    //-------------------------

    {

        // each node contains an id field identifying its type.

        // we switch on these. we could use instanceof, but that's less efficient

        // enum values such as JJTINTLITERAL come from the interface file

        // SimpleParserTreeConstants, which SimpleParser implements.

        // This interface file is one of several auxilliary Java sources

        // generated by JJTree. JavaCC contributes several others.

        int id = node.id;

        // eventually the buck stops here and we unwind the stack

        if ( node.id == JJTINTLITERAL )

            return Integer.parseInt( node.getText() );

        SimpleNode lhs =  (SimpleNode) node.jjtGetChild(0);

        SimpleNode rhs =  (SimpleNode) node.jjtGetChild(1);

        switch( id )

        {

            case JJTADD :       return eval( lhs ) + eval( rhs );

            case JJTSUBTRACT :  return eval( lhs ) - eval( rhs );

            case JJTMULT :      return eval( lhs ) * eval( rhs );

            case JJTDIV :       return eval( lhs ) / eval( rhs );

            default :

                throw new java.lang.IllegalArgumentException(

                                  "eval(): invalid operator!" );

        }

    }


												
										



回页?/font>


l束?/font>

如果(zhn)想要查看可以处理许多实?XQuery 语法的功能更丰富?eval() 函数版本Q欢q下载我的开放源?XQuery 实现QXQEngineQ的副本Q请参阅 参考资?/font> Q。它?TreeWalker.eval() 例程 例D?30 多种 XQuery 节点cd。还提供?jin)一?.jjt 脚本?



回页?/font>


参考资?



回页?/font>


关于作?/font>

Howard Katz 居住在加拿大温哥华,他是 Fatdog Software 的唯一业主Q该公司专门致力于开发搜?XML 文档的Y件。在q去的大U?35 q里Q他一直是z跃的程序员Q一直业l良好)(j)Qƈ且长期ؓ(f)计算N易出版机构撰写技术文章。Howard ?Vancouver XML Developer's Association 的共同主持hQ还?Addison Wesley 卛_出版的书c?The Experts on XQuery的编辑,该书?W3C ?Query 工作l成员合著,概述?jin)有?XQuery 的技术前景。他和他的妻子夏天去划船Q冬天去边远地区滑雪。可以通过 howardk@fatdog.com?Howard 联系?



独孤q客 2006-05-06 16:23 发表评论
]]>
JavaCC、解析树(wi)?XQuery 语法Q第 1 部分http://m.tkk7.com/landy/archive/2006/05/06/44766.html独孤q客独孤q客Sat, 06 May 2006 08:22:00 GMThttp://m.tkk7.com/landy/archive/2006/05/06/44766.htmlhttp://m.tkk7.com/landy/comments/44766.htmlhttp://m.tkk7.com/landy/archive/2006/05/06/44766.html#Feedback0http://m.tkk7.com/landy/comments/commentRss/44766.htmlhttp://m.tkk7.com/landy/services/trackbacks/44766.html在简要讨Z(jin)语法、解析器?BNF 后,本文介l?JavaCCQ这是一个流行的解析器生成器工具。?zhn)开发?JavaCC 的样本代码来构徏定制的解析器Q先从语法的 BNF 描述开始。第 2 部分接着演C如何用辅助工??JJTree 来构建同一解析的解析树(wi)表示Q以?qing)如何在q行旉历该?wi),以发现其状态信息。文章将以开发构建和遍历解析?wi)的h代码作ؓ(f)l束Q该解析?wi)是?zhn)ؓ(f)一部?XQuery 语法生成的?/blockquote>

要完成最单的日常解析dQ?zhn)不需要用象自动化解析器生成器那样复杂的M东西。例如,同“梳理”CSVQ逗号分割的|Comma-Separated-ValueQ文件的各部分同L(fng)单的~程l习(fn)需要了(jin)解文件的l构Q可能还需要了(jin)解如何?Java ?BNF 开?/font>

复杂语言的语法通常都是使用 BNFQ巴U斯-诺尔范式QBackus-Naur FormQ表C法或者其“近亜y(c)?EBNFQ扩展的 BNFQ描q的。自动化工具可以使用那些描述Q我用通用的术?BNF来指代这两种变体Q或与它们近似的描述来ؓ(f)(zhn)生成解析代码。本文就描述?jin)这L(fng)一U解析器-生成器工PUCؓ(f) JavaCC。我简要地研究一?JavaCC 的基本知识,q在l束的时候花些时间研I一下它的一个辅助工??JJTreeQ但是在讨论中不?x)介l太多的理论知识Q以免偏M题。本文力N明我的理念:(x)(zhn)ƈ不需要了(jin)解很多有x规的解析理论pq行解析Q?

Z么?JavaCC 呢?有几个原因:(x)我对 XQuery 有着强烈的兴,?W3C ?XML Query 工作l恰好?JavaCC 来构建ƈ试 XQuery 语法的版本,q且构徏和测试它?XSL l共享的 XPath 语法。我q?JavaCC 来提?XQEngine 中的查询-解析代码QXQEngine 是我自己的开放源?XQuery 实现Q请参阅 参考资?/font>Q?

最后一点(但不是最不重要的Q,h是完全合适的Q尽?JavaCC 不是开放源码,但它是完全免费的。(请参?参考资?/font>以了(jin)解有兛_何获?JavaCC 的信息)(j)?



回页?/font>


解析 101

在我开始介l一些实际的 XQuery 语法之前Q让我们先从一个非常简单的 BNF 开始,它描qC(jin)一U语aQ该语言仅由两个只对整数q行q算的算术运符构成。我U这U语a?

												
														simpleLang     ::=   integerLiteral ( ( "+" | "-" ) integerLiteral )?
integerLiteral ::=   [ 0-9 ]+

												
										

该语法中的每个规则都是一?l果QproductionQ?/b> Q其中左边的(l果的名Uͼ(j)是依据语法中的其它结果描q的。最上面的结?simpleLang ?simpleLang ?非终端(non-terminalQ?/i> W号的一个示例,它对其它l果q行引用Q另一斚wQ规?

回页?/font>


?BNF ?JavaCC

让我们看看如何?JavaCC 实现该语法。JavaCC 使用UCؓ(f) .jj 的文件。该文g中的语法描述是用非常类g BNF 的表C法~写的,q样从一UŞ式{换到另一UŞ式通常q当容易。(该表C法有自q语法Q从而其在 JavaCC 中是可表辄。)(j)

JavaCC .jj 文g语法和标准的 BNF 之间的主要区别在于:(x)利用 JavaCC 版本Q?zhn)可以在语法中嵌入操作。一旦成功遍历了(jin)语法中的那些部分Q则执行q些操作。操作都?Java 语句Q它们是解析?Java 源代码的一部分Q该部分作ؓ(f)解析器生成过E的一部分产生?/p>

Q注Q除?jin)一?Java 清单 1q不包含(zhn)需要用来对该语a中的表达式实际求值的嵌入?Java 代码。当(zhn)研I过 JJTree ?qing)其解析树(wi)表C后Q我对此做更详l的研究。)(j)


清单 1. ~码 SimpleLang 语法的完?.jj 脚本
												
														PARSER_BEGIN( Parser_1 )
package examples.example_1;
public class Parser_1 {}
PARSER_END( Parser_1 )

void simpleLang() : {}             { integerLiteral() 
                                   ( ( "+" | "-" ) integerLiteral() )? <EOF> }
void integerLiteral() : {Token t;} { t=<INT> 
                                   { System.out.println("integer = "+t.image); }}

SKIP 	: { " " | "\t" | "\n" | "\r" }
TOKEN	: { < INT : ( ["0" - "9"] )+ > }

												
										

h意有兌文g的下q情形:(x)

  • PARSER_END 伪指令指定了(jin)要生成的 Java 解析器的名称QParser_1.javaQ,q提供一个位|以便将 Java 语句插入该类。在q个案例中,(zhn)正一?Java package 语句也放|在 Java 助手cL件的剙Q该文g是作为生成过E一部分产生的(请参?JavaCC ~译q程 Q。尽我在本CZ中没有这样做Q但是这也是一个声明实例变量的好场所Q该实例变量由(zhn)结果中?Java 语句引用。如果?zhn)喜欢Q甚臛_以在q里插入 Java 遍历解析代码中ؓ(f)(zhn)演C那是如何工作的。(q里的术语“编译”也不是偶然??解析器生成器通常也称为“编译器的编译器”。)(j)
  • 花括P{ ?}) 内描qC(jin)l果的主体,q且排除?jin)Q何?zhn)正在嵌入?Java 操作。请注意 System.out.println() 语句。该操作作ؓ(f)Ҏ(gu) SKIP 语句表明Q在记号之间可以出现I白Q空根{蟩根{回车和换行Q,I白被忽略?
  • integerLiteral() 声明?jin)类?t 。当在输入流中遇到整数时?触发 该规则,该整敎ͼ象文本一P(j)的D赋给实例变量 Token 字段 System.out.println() 代码可以在解析时在那个记L(fng)内部使用

    回页?/font>


    遍历解析器代?/font>

    让我们非常简要地?jin)解一下?zhn)生成的解析器的内部原理。出于下面两个原因,E微?jin)解qD的 .jj 语法生成的方法以?qing)其在运行时的执行顺序是很有用的Q?/p>

    • 有时候(特别是当(zhn)第一ơ做的时候)(j)Q解析器gq回?jin)不同的l果Q而?zhn)认?f)?.jj 文g指示它这样做的。?zhn)可以在运行时单步遍历产生的解析器代码Q以便查看它到底在做什么,q相应地调整语法文g。我现在q经常这样做?
    • 如果(zhn)知道结果/Ҏ(gu)的执行顺序,那么(zhn)将对在脚本中的什么地方嵌?Java 操作以获得特定的l果有更好的理解。在W?2 部分谈论有关 JJTree 工具和解析树(wi)表示Ӟ我将回过头来更详l地讨论q一内容?

    管深入研究已生成解析器的详l信息超了(jin)本文的范_(d)但是 清单 2 q是昄?jin)?f)Ҏ(gu) System.out.println( "integer = "+t.image) 。该语句作ؓ(f)嵌入 .jj 脚本?Java 操作发挥作用?


    清单 2. Parser_1 中生成的Ҏ(gu)
    												
    														  static final public void integerLiteral() throws ParseException {
                                Token t;
        t = jj_consume_token(INT);
        System.out.println( "integer = "+t.image);
      }
    
    												
    										

    以下高、详的描述说明?jin)这个解析器做什么:(x)

    1. 最上面的方?integerLiteral() ?
    2. <INT> 记号值“包”v来;如果是后者,则当?integerLiteral() 做进一步处理。如果记可予器未遇到这两个记号Q则q回词法错误?
    3. 如果记号赋予器返回的记号不是整数记号?integerLiteral() 抛出 integerLiteral() 再次调用记号赋予器以q回下一个记受如果返?integerLiteral() 最后一ơ调用记可予器Q以L另一个整数。如果遇C个整敎ͼ则表辑ּ是有效的Q解析器完成工作。如果下一个记号不是整敎ͼ则解析器抛出异常?

    注:(x)如果解析器失败了(jin)Q则抛出 TokenMgrError 。Q何一U异帔R表明(zhn)的表达式是无效的?

    q里?要点 是,只有当解析器成功地遍历了(jin)嵌入 Java 操作的那部分l果后,才能执行嵌入到这两个l果中的M Java 操作。如果将表达式?2 + 1”传递给该解析器Q则语句 integer = 1 。如果运行无效的表达式?2 + abc”,则生消?a Token Manager error! 。在后一U情形中Q解析器只成功地遍历?integerLiteral() ,而未成功遍历W二:(x)

    												
    														void simpleLang() : {} { integerLiteral() ( ("+" | "-") integerLiteral() )? <EOF> }
    												
    										

    换而言之,W二?

    回页?/font>


    JavaCC ~译q程

    当?zhn)?.jj 文gq行 JavaCC Ӟ它会(x)生成许多 Java 源文件。其中一个是主解析代?Parser_1.javaQ当(zhn)有一个要解析的表辑ּӞ(zhn)将从?zhn)的应用程序调用该代码。JavaCC q创Z(jin)其它六个p析器使用的辅助文件。JavaCC d生成?jin)以下七?Java 文g。前三个是特定于q个Ҏ(gu)语法的;后四个是通用的助手类 Java 文gQ无法是怎么L(fng)Q都?x)生成这几个文g?/p>

    • Parser_1.java
    • Parser_1Constants.java
    • Parser_1TokenManager.java
    • ParseException.java
    • SimpleCharStream.java
    • Token.java
    • TokenMgrError.java

    一?JavaCC 生成?jin)这七?Java 源文Ӟ则可以编译它们ƈ它们链接到(zhn)的 Java 应用E序中。然后可以从(zhn)的应用E序代码调用新的解析器,从而将表达式传递给它进行求倹{下面是一个样本应用程序,它实例化(zhn)的解析器,q且为它提供?jin)一个硬q接在应用程序顶部的表达式?/p>
    清单 3Q调用第一个解析器
    												
    														package examples.example_1;
    
    import examples.example_1.Parser_1;
    import examples.example_1.ParseException;
    import java.io.StringReader;
    
    public class Example_1
    {
        static String expression = "1 + 42";
        
        public static void main( String[] args )
        //--------------------------------------
        {
            new Example_1().parse( expression );
        }
    
        void parse( String expression )
        //-----------------------------
        {
            Parser_1 parser = new Parser_1( new StringReader( expression ));
            try 
            {
                parser.simpleLang();
            }
            catch( ParseException pe )  {
                System.out.println( "not a valid expression" ); 
            }
            catch( TokenMgrError e ) {
                System.out.println( "a Token Manager error!" );
            }
        }
    }
    
    												
    										

    q里有什么是值得注意的呢Q?/p>

    1. 要调?simpleLang() ?jj 文g中的l果序通常是无关的Q而本案例除外Q在本案例中Q语法中最剙的结果名U用于调用解析器?
    2. 如果正在传递给解析器代码的表达式不能根据语法合法地构造,则将抛出 LexicalError ?
    3. 如果表达式是有效的,则执行嵌入语法各部分的Q?Java 操作Q这些语法部分都被成功遍历,p 遍历解析器代?/font>l尾描述的一栗?


    回页?/font>


    l束?/font>

    q篇文章l束后还有第 2 部分。?zhn)从cM的样本代码开始着手,学习(fn)如何使用 JavaCC 的“伙伴”工?JJTree 来创建在q行时构析的解析?wi)表C的解析器,而不是执行嵌?.jj 脚本的操作。正如?zhn)看到的Q这有很多优炏V?/p>

    回页?/font>


    参考资?



    回页?/font>


    关于作?/font>

    Howard Katz 居住在加拿大温哥华,他是 Fatdog Software 的唯一业主Q该公司专门致力于开发搜?XML 文档的Y件。在q去的大U?35 q里Q他一直是z跃的程序员Q一直业l良好)(j)Qƈ且长期ؓ(f)计算N易出版机构撰写技术文章。Howard ?Vancouver XML Developer's Association 的共同主持hQ还?Addison Wesley 卛_出版的书c?The Experts on XQuery的编辑,该书?W3C ?Query 工作l成员合著,概述?jin)有?XQuery 的技术前景。他和他的妻子夏天去划船Q冬天去边远地区滑雪。可以通过 howardk@fatdog.com?Howard 联系?



    独孤q客 2006-05-06 16:22 发表评论
    ]]>JavaCC 用于支持l端用户?DB2 UDB 数据库编制简?/title><link>http://m.tkk7.com/landy/archive/2006/05/06/44765.html</link><dc:creator>独孤q客</dc:creator><author>独孤q客</author><pubDate>Sat, 06 May 2006 08:21:00 GMT</pubDate><guid>http://m.tkk7.com/landy/archive/2006/05/06/44765.html</guid><wfw:comment>http://m.tkk7.com/landy/comments/44765.html</wfw:comment><comments>http://m.tkk7.com/landy/archive/2006/05/06/44765.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://m.tkk7.com/landy/comments/commentRss/44765.html</wfw:commentRss><trackback:ping>http://m.tkk7.com/landy/services/trackbacks/44765.html</trackback:ping><description><![CDATA[ <p>U别: 初</p> <p> <a > <font color="#996699">JoAnn P. Brereton</font> </a>, 高软g工程师,IBM<br /></p> <p>2004 q?5 ?01 ?/p> <blockquote>JavaCC 是一个功能极其强大的‘编译器的编译器’工P可用于编制上下文无关的语法。本文演CZ(jin)如何?JavaCC 用于支持l端用户?DB2 UDB 数据库编制简单的布尔查询?/blockquote> <p> <a name="0"> <span id="6166116" class="atitle"> <strong> <font size="4">JavaCC ?/font> </strong> </span> </a> </p> <p>许多Z Web 的项目都包含卛_Qad-hocQ查询系l以允许l端用户搜烦(ch)信息。因此,l端用户?x)需要某U语a来表达他们所希望搜烦(ch)的内宏V以前,用户查询语言的定义极其简单。如果终端用h于使用与最典型?Google 搜烦(ch)一般简单的语言Q那?Java ?StringTokenizer 对于解析dqԒl有余了(jin)。然而,如果用户希望有一U更健壮的语aQ比如要d括号和“AND?“OR”逻辑Q那么我们很快就?x)发现我们需要更强大的工兗我们需要一U方法,用以首先定义用户要使用的语aQ然后用该定义解析相应的条目q且对各U后端数据库制定正确的查询?/p> <p>q就是工?JavaCC 出现的原因。JavaCC 代表“Java] Compiler Compiler”,是对 YACCQ“Yet Another Compiler Compiler”)(j)的承(YACC ?AT&T Z(jin)构徏 C 和其他高U语a解析器而开发的一个基?C 的工P(j)。YACC 和其伙伴词法记号赋予器(tokenizerQ——“Lex”——接收由常用的巴U斯-诺尔范式QBackus-Nauer formQ又U?Bacchus Normal FormQBNFQŞ式的语言定义的输入,q生成一个“C”程序,用以解析该语a的输入以?qing)执行其中的功能。JavaCC ?YACC 一P是ؓ(f)加快语言解析器逻辑的开发过E而设计的。但是,YACC 生成 C 代码Q?JavaCC 呢,正如(zhn)想像的那样QJavaCC 生成的是 Java 代码?/p> <p>JavaCC 的历史极具传奇色彩。它h?Sun 公司的“Jack”。Jack 后来辗{?jin)几家拥有者,比如著名?Metamata ?WebGainQ最后变成了(jin) JavaCCQ然后又回到?Sun。Sun 公司最后在 BSD 的许可下它作ؓ(f)开放源代码的代码发布。JavaCC 目前?Web 主页?<a ><font color="#5c81a7">http://javacc.net.java.net</font></a>?</p> <p>JavaCC 的长处在于它的简单性和可扩展性。要~译?JavaCC 生成?Java 代码Q无需M外部 JAR 文g或目录。仅仅用基本?Java 1.2 版编译器可以进行编译。而该语言的布局也得它易于d产生式规则和行ؓ(f)。该 Web 站点甚至描述?jin)如何编制异总便给出用户合适的语法提示?/p> <br /> <table cellspacing="0" cellpadding="0" width="100%" border="0"> <tbody> <tr> <td> <img style="WIDTH: 500px; CURSOR: pointer" onclick="javascript:window.open(this.src);" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" onload="javascript:if(this.width>500)this.style.width=500;" /> </td> </tr> </tbody> </table> <table class="no-print" cellspacing="0" cellpadding="0" align="right"> <tbody> <tr align="right"> <td> <table cellspacing="0" cellpadding="0" border="0"> <tbody> <tr> <td valign="center"> <img style="CURSOR: pointer" onclick="javascript:window.open(this.src);" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" onload="javascript:if(this.width>500)this.style.width=500;" border="0" /> <br /> </td> <td valign="top" align="right"> <a class="fbox" > <b> <font face="Verdana" color="#996699">回页?/font> </b> </a> </td> </tr> </tbody> </table> </td> </tr> </tbody> </table> <br /> <br /> <p> <a name="1"> <span id="6111111" class="atitle"> <strong> <font size="4">问题定义</font> </strong> </span> </a> </p> <p>让我们假设?zhn)有一位客户在一个出U视频节目的商店里,该商店拥有一个简单的?sh)?jing)数据库。该数据库包含表 MOVIES、ACTORS ?KEYWORDS。MOVIES 表列举他商店中每部电(sh)q相关数据Q即如每部电(sh)q名称和导演等内容。ACTORS 表列举每部电(sh)׃的演员姓名。?KEYWORDS 表则列D描述?sh)?jing)的词语,例如“action”、“drama”、“adventure”等{?/p> <p>客户希望能够对该数据库发出稍微复杂的查询。例如,他想输入以下形式的查?/p> <p> </p> <table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"> <tbody> <tr> <td> <pre> <code class="section"> <font face="Lucida Console"> actor = "Christopher Reeve" and keyword=action and keyword=adventure</font> </code> </pre> </td> </tr> </tbody> </table> <br /> <p>q且希望q回?Christopher Reeve L?Superman pd?sh)?jing)。他q希望像下面q样用括h说明求值次序以区分查询</p> <p> </p> <table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"> <tbody> <tr> <td> <pre> <code class="section"> <font face="Lucida Console"> (actor = "Christopher Reeve" and keyword=action) or keyword=romance</font> </code> </pre> </td> </tr> </tbody> </table> <br /> <p>q样可能q回不是?Christopher Reeve L的电(sh)?/p> <p> </p> <table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"> <tbody> <tr> <td> <pre> <code class="section"> <font face="Lucida Console">actor = "Christopher Reeve" and (keyword=action or keyword=romance)</font> </code> </pre> </td> </tr> </tbody> </table> <br /> <p>q样则M(x)q回 Christopher Reeve L的电(sh)影?/p> <br /> <table cellspacing="0" cellpadding="0" width="100%" border="0"> <tbody> <tr> <td> <img style="WIDTH: 500px; CURSOR: pointer" onclick="javascript:window.open(this.src);" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" onload="javascript:if(this.width>500)this.style.width=500;" /> </td> </tr> </tbody> </table> <table class="no-print" cellspacing="0" cellpadding="0" align="right"> <tbody> <tr align="right"> <td> <table cellspacing="0" cellpadding="0" border="0"> <tbody> <tr> <td valign="center"> <img style="CURSOR: pointer" onclick="javascript:window.open(this.src);" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" onload="javascript:if(this.width>500)this.style.width=500;" border="0" /> <br /> </td> <td valign="top" align="right"> <a class="fbox" > <b> <font face="Verdana" color="#996699">回页?/font> </b> </a> </td> </tr> </tbody> </table> </td> </tr> </tbody> </table> <br /> <br /> <p> <a name="2"> <span id="1111611" class="atitle"> <strong> <font size="4">解决Ҏ(gu)</font> </strong> </span> </a> </p> <p>对于该Q务,(zhn)将分两个阶D|定义解决Ҏ(gu)。在W?1 阶段中,(zhn)将?JavaCC 定义语言Q要保能够正确解析l端用户的查询。在W?2 阶段中,(zhn)将?JavaCC 代码d行ؓ(f)以?DB2] SQL 代码Q从而确保返回正的?sh)?jing)来响应终端用L(fng)查询?/p> <p> <a name="IDA1LUNB"> <span id="1161111" class="smalltitle"> <strong>阶段 1 - 定义用户的查询语a</strong> </span> </a> </p> <p>在名ؓ(f) UQLParser.jj 的文仉定义该语a。该文g被 JavaCC 工具~译成ؓ(f)一l?.java cd?Java cL件。要?JJ 文g中定义语aQ?zhn)需要做以下 5 件事Q?/p> <ol> <li>定义解析环境 </li> <li>定义“空白? </li> <li>定义“标讎ͼtokenQ? </li> <li>按照标记定义语言本n的语? </li> <li>定义每个解析阶段中将发生的行?</li> </ol> <p>(zhn)可以通过所展示的代码段定义自己?UQLParser.jj 文gQ也可以通过本文的相关代码进行效ѝ对于步?1 ?4Q在 JavaCCPaper/stage1/src 中?UQLParser.jj 的副本。而步?5 则在 JavaCCPaper/stage2/src 中进行。样本数据库?DDL 可以?JavaCCPaper/moviedb.sql 中找到。如果用相同的用户标识创徏数据库和q行解析器,该实例将q行得最好。Ant 文gQbuild.xmlQ可用于加快~译q程?/p> <p> <b>步骤 1. 定义解析环境</b> </p> <p>JavaCC .jj 文g通过执行 JavaCC 被转换?.java 文g。JavaCC 获?.jj 文g?PARSER_BEGIN ?PARSER_END 的中间部分ƈ之复制?Java l果文g中。作析器设计者,(zhn)可以将解析前后所有与解析器有关的动作|于该文件中。?zhn)q可以在其中?Java 代码链接到步?4 ?5 中将?x)定义的解析器动作上?/p> <p>在以下所C的实例中,解析器相Ҏ(gu)较简单。构造函?UQLParser 接收一个字W串输入Q通过 Java ?java.io.StringReader cd其读入,然后调用另一个不可见的构造函数将 StringReader 强制转换?Reader。这里定义的惟一其他Ҏ(gu)是 static main Ҏ(gu)Q该Ҏ(gu)在调用构造函C后再调用q今q未定义的名?parse() 的方法?/p> <p>正如(zhn)可能已猜到的,JavaCC 已经提供?jin)一?Java Reader cȝ构造函数。而我们添加了(jin)Z字符串的构造函敎ͼ以便易于使用和测试?/p> <a name="IDAQMUNB"> <b>清单 1. 解析器的 Java 环境</b> </a> <br /> <table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"> <tbody> <tr> <td> <pre> <code class="section"> <font face="Lucida Console">PARSER_BEGIN(UQLParser) package com.demo.stage1; import java.io.StringReader; import java.io.Reader; public class UQLParser { /** A String based constructor for ease of use. **/ public UQLParser(String s) { this((Reader)(new StringReader(s))); } public static void main(String args[]) { try { String query = args[0]; UQLParser parser = new UQLParser(query); parser.parse(); } catch(Exception e) { e.printStackTrace(); } } } PARSER_END(UQLParser) </font> </code> </pre> </td> </tr> </tbody> </table> <br /> <p> <b>步骤 2. 定义I白</b> </p> <p>在该语言中,(zhn)希望将I格、蟩根{回车和换行作ؓ(f)分隔W处理,而不是将其忽略。这些字W都被称?<b><i>I白</i></b>。在 JavaCC 中,我们?SKIP 区域中定义这些字W,如清?2 中所C?</p> <a name="IDACNUNB"> <b>清单 2. ?SKIP 区域中定义空?/b> </a> <br /> <table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"> <tbody> <tr> <td> <pre> <code class="section"> <font face="Lucida Console">/** Skip these characters, they are considered "white space" **/ SKIP : { " " | "\\t" | "\\r" | "\\n" } </font> </code> </pre> </td> </tr> </tbody> </table> <br /> <p> <b>步骤 3. 定义标记</b> </p> <p>接下来,(zhn)将定义该语a所识别的标记?<b><i>标记</i></b>是将对解析程序有意义的解析字W串的最单位。扫描输入字W串以及(qing)判断是何标记的过E称?<b><i>记号赋予器(tokenizerQ?/i></b>。在以下查询中, </p> <code> <font face="Courier" size="1">actor = "Christopher Reeve"</font> </code> <p>其标Cؓ(f) </p> <ul> <li>actor </li> <li>= </li> <li>"Christopher Reeve" </li> </ul> <p>在?zhn)的语a中,(zhn)要?actor 和等P=Q作语言中的保留标记Q尽字 <b><i>if</i></b>?<b><i>instanceof</i></b>?Java 语言中都是带有特D意义的保留标记。通过保留字和其他Ҏ(gu)标记Q程序员希望解析器会(x)逐字地识别这些字qؓ(f)其指z定的意义。如果?zhn)正在保留q些字,L(fng)l进行下dƈ且保留不{号Q?lt;>Q和左右括号。还要保留名U、导演和关键字以表示用于用户搜烦(ch)的特定字Dc(din)?</p> <p>要定义所有这些内容,请?JavaCC ?TOKEN 指o(h)。每个标记的定义都用括P< ?>Q括h。在冒号Q?Q的左边l出标记的名Uͼq在双l出正则表达式。正则表辑ּ是定义将要匹配的文本部分的方式。在其最单的形式中,正则表达式可以匹配精的字符序列。用下列代码来定义六个匚w_字的标记和四个匹配符L(fng)标记。当分析器看CQ何一个字Ӟ会(x)用符?AND、OR、TITLE、ACTOR、DIRECTOR ?KEYWORD 来加以匹配。在匚wW号之后Q解析器相应地q回 LPAREN、RPAREN、EQUALS ?NOTEQUAL。清?3 展示?JavaCC 保留标记的定义?/p> <a name="IDAIOUNB"> <b>清单 3. 定义保留标记</b> </a> <br /> <table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"> <tbody> <tr> <td> <pre> <code class="section"> <font face="Lucida Console">TOKEN: /*RESERVED TOKENS FOR UQL */ { <AND: "and"> | <OR: "or"> | <TITLE: "title"> | <ACTOR: "actor"> | <DIRECTOR: "director"> | <KEYWORD: "keyword"> | <LPAREN: "("> | <RPAREN: ")"> | <EQUALS: "="> | <NOTEQUAL: "<>"> } </font> </code> </pre> </td> </tr> </tbody> </table> <br /> <p>对于像“Christopher Reeve”一L(fng)字符Ԍ(zhn)或许无法在我们的语a中将所有的演员姓名存储Z留字。但是,(zhn)可以通过使用正则表达式定义的字符模式识别 STRING ?QUOTED_STRING cd的标记?<b><i>正则表达?/i></b>是定义匹配模式的字符丌Ӏ定义匹配所有字W串或引用字W串的正则表辑ּ要比定义_的字匚w更具技巧性?</p> <p>(zhn)将定义一个由一个或更多字符pd构成的字W串QSTRINGQ,其中的有效字Wؓ(f)大小写的 A ?Z 以及(qing)数字 0 ?9。ؓ(f)?jin)简单v见,不考虑定媄(jing)明星或电(sh)影名U的重音字符或其他不规则体。?zhn)可以按下列方式将该模式写Z个正则表辑ּ?/p> <p> </p> <table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"> <tbody> <tr> <td> <pre> <code class="section"> <font face="Lucida Console"> <STRING : (["A"-"Z", "a"-"z", "0"-"9"])+ ></font> </code> </pre> </td> </tr> </tbody> </table> <br /> <p>加号表示围在括号中的模式Q从 A ?Z、a ?z ?0 ?9 中的M字符Q可依次出现一ơ或多次。在 JavaCC 中,(zhn)还可以用星P*Q来表示模式的零ơ或多次出现以及(qing)用问PQ)(j)来表C?0 ?1 此重复?/p> <p>QUOTED_STRING 更h巧性了(jin)。如果?zhn)定义一个以引号开_(d)以引L(fng)ƈ在其中包含Q何其他字W的字符Ԍ那么该字W串是一?QUOTED_STRING。其正则表达式ؓ(f) <code><font face="Courier" size="1">"\\"" (~["\\""])+ "\\""</font></code> Q这肯定有些D~ؕ的。简单一点理解就是,׃引用字符本n对于 JavaCC 是有意义的,因此我们需要将对它的引用{换ؓ(f)Ҏ(gu)们的语言而非 JavaCC 是有意义的。ؓ(f)?jin){换该引用Q我们在它之前用了(jin)一个反斜杠。字W颚化符P~Q意味着q是针?JavaCC 记号赋予器的?<code><font face="Courier" size="1">(~["\\""])+</font></code> 是对于一个或更多非引用字W的速写。合在一P <code><font face="Courier" size="1">"\\"" (~["\\""])+ "\\""</font></code> 意味着“一个引用加上一个或更多非引用再加上一个引用”?</p> <p>(zhn)必d保留字规则之后添?STRING ?QUOTED_STRING 的记可予器规则。保持该ơ序是极光要的Q因可予器规则在文件中出现的次序就是应用标记规则的ơ序。?zhn)需要确定“title”是被视作保留字而非字符串的。清?4 中显CZ(jin) STRING ?QUOTED_STRING 标记的完整定义?/p> <a name="IDAMPUNB"> <b>清单 4. 定义 STRING ?QUOTED_STRING</b> </a> <br /> <table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"> <tbody> <tr> <td> <pre> <code class="section"> <font face="Lucida Console">TOKEN : { <STRING : (["A"-"Z", "0"-"9"])+ > <QUOTED_STRING: "\\"" (~["\\""])+ "\\"" > } </font> </code> </pre> </td> </tr> </tbody> </table> <br /> <p> <b>步骤 4. 按照标记定义语言</b> </p> <p>既然已经定义?jin)标讎ͼ那么现在是时候按照标记来定义解析规则?jin)。用戯入表辑ּ形式的查询。一?<b><i>表达?/i></b>是一pd?<b><i>布尔q算W?/i></b><b><i>and</i></b>?<b><i>or</i></b>q接的一个或更多查询V?</p> <p>Z(jin)表达q一点,我们需要编写一个解析规则,也称?<b><i>产生?/i></b>。将清单 5 中的产生式写?JavaCC UQLParser.JJ 文g?</p> <a name="IDANQUNB"> <b>清单 5. expression() 产生?/b> </a> <br /> <table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"> <tbody> <tr> <td> <pre> <code class="section"> <font face="Lucida Console">void expression() : { } { queryTerm() ( ( <AND> | <OR> ) queryTerm() )* } </font> </code> </pre> </td> </tr> </tbody> </table> <br /> <p>当对 .jj 文gq行 Javacc Ӟ产生式将被{换ؓ(f)Ҏ(gu)。所有的 JavaCC 产生式方法的q回都必Mؓ(f)I。第一l花括号包含产生式方法所需的所有声明。这里暂时ؓ(f)I。第二组花括号包含以 JavaCC 理解的方式所写的产生式规则。请注意先前所定义?AND ?OR 标记的用法。还h意,queryTerm() 是作为方法调用而写的。实际上QqueryTerm() 是另一个生式Ҏ(gu)?/p> <p>现在Q就让我们定?queryTerm() 产生式。queryTerm() 要么是一个单独的判别式(例如 title="The Matrix"Q,要么是一个由括号括v来的表达式。JavaCC 中通过 expression() 递归地定义了(jin) queryTerm()Q这使得(zhn)可以通过清单 6 中所C的代码明地ȝ该语a?/p> <a name="IDAZQUNB"> <b>清单 6. JavaCC 中的 queryTerm() 产生式方法(UQLParser.jjQ?/b> </a> <br /> <table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"> <tbody> <tr> <td> <pre> <code class="section"> <font face="Lucida Console">void queryTerm() : { } { (<TITLE> | <ACTOR> | <DIRECTOR> | <KEYWORD>) ( <EQUALS> | <NOTEQUAL>) ( <STRING> | <QUOTED_STRING> ) | <LPAREN> expression() <RPAREN> } </font> </code> </pre> </td> </tr> </tbody> </table> <br /> <p>q就是我们所需的所有规则。两个生式中ȝ?jin)全部的语言解析器?/p> <p> <a name="IDAERUNB"> <span id="6161166" class="smalltitle"> <strong>?JavaCC 当作试驱动?/strong> </span> </a> </p> <p>在这个时候,(zhn)应该已l有?jin)一个有效的 JavaCC 文g。在q行到步?5 之前Q?zhn)可以~译q“运行”该E序以查看?zhn)的解析器q作是否正确?/p> <p>随本文一h供的 ZIP 文g应包含了(jin)阶段 1 ?JavaCC CZ文g UQLParser.jj。将整个 ZIP 文g解压C个空目录下。要~译 stage1/UQLParser.jjQ?zhn)首先需要下?JavaCC q根?<a ><font color="#5c81a7">JavaCC Web ?/font></a> 上的指导q行安装。ؓ(f)?jin)简单v见,请务必将 Javacc.bat 的执行\径填?PATH 环境变量中。编译十分容易,目录更改ؓ(f)卸蝲 UQLParser.jj 的位|ƈ输入下列命o(h)?</p> <code> <font face="Courier" size="1">javacc "debug_parser " output_directory=.\\com\\demo\\stage1 UQLParser.jj </font> </code> <p>如果(zhn)愿意,也可以用附带的 <a ><font color="#5c81a7">Ant</font></a> 文g build.xml。?zhn)必须上方的属性文件调整ؓ(f)指向 JavaCC 安装。在(zhn)第一ơ运行它ӞJavaCC 生成如清单 7 中所C的消息?</p> <a name="IDAZRUNB"> <b>清单 7. UQLParser.jj 的编译输?/b> </a> <br /> <table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"> <tbody> <tr> <td> <pre> <code class="section"> <font face="Lucida Console">Java Compiler Compiler Version 3.2 (Parser Generator) (type "javacc" with no arguments for help) Reading from file UQLParser.jj . . . File "TokenMgrError.java" does not exist. Will create one. File "ParseException.java" does not exist. Will create one. File "Token.java" does not exist. Will create one. File "SimpleCharStream.java" does not exist. Will create one. Parser generated successfully. </font> </code> </pre> </td> </tr> </tbody> </table> <br /> <p>除了(jin)已提到的四个文gQJavaCC q将产生 UQLParser.java、UQLParserConstants.java ?UQLParserTokenManager.java。所有这些文仉被写入了(jin) com\\demo\\stage1 目录。此时vQ?zhn)p够编译这些文件且无需向默认的q行时类路径做Q何添加了(jin)。如?JavaCC 步骤q行成功QAnt 文g的默认目标将自动执行 Java ~译。如果没有成功,(zhn)可以用以下命o(h)~译层目录QJavaCCPaper/stage1Q的文gQ?/p> <code> <font face="Courier" size="1">javac "d bin src\\com\\demo\\stage1\\*.java </font> </code> <p>Java cL件一旦就位,(zhn)就可以通过向?zhn)所定义?"main" java Ҏ(gu)输入下列用户CZ查询来测试新的解析器?jin)。如果?zhn)正用同一代码Q请?JavaCCPaper/stage1 目录开始ƈ在命令行中输入下列命令?/p> <code> <font face="Courier" size="1">java "cp bin com.demo.stage1.UQLParser "actor = \\"Tom Cruise\\"" </font> </code> <p>我们?JavaCC 步骤中所使用?<code><font face="Courier" size="1">-debug_parser</font></code> 选项保输Z列有用的跟踪消息Q以昄用户查询是如何被解析的。其输出应该如清?8 中所C?</p> <a name="IDARSUNB"> <b>清单 8. 查询 actor="Tom Cruise" ?UQLParser 输出</b> </a> <br /> <table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"> <tbody> <tr> <td> <pre> <code class="section"> <font face="Lucida Console">Call: parse Call: expression Call: queryTerm Consumed token: <"actor"> Consumed token: <"="> Consumed token: <<QUOTED_STRING>: ""Tom Cruise""> Return: queryTerm Return: expression Consumed token: <<EOF>> Return: parse </font> </code> </pre> </td> </tr> </tbody> </table> <br /> <p>要测试带括号表达式的递归路径Q请试以下试?/p> <code> <font face="Courier" size="1">java "cp bin com.demo.stage1.UQLParser "(actor=\\"Tom Cruise\\" or actor=\\"Kelly McGillis\\") and keyword=drama" </font> </code> <p>q将产生清单 9 中的输出?/p> <a name="IDAATUNB"> <b>清单 9. 查询 (actor="Tom Cruise" or actor="Kelly McGillis") and keyword=drama ?UQL1Parser 输出</b> </a> <br /> <table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"> <tbody> <tr> <td> <pre> <code class="section"> <font face="Lucida Console">Call: parse Call: expression Call: queryTerm Consumed token: <"("> Call: expression Call: queryTerm Consumed token: <"actor"> Consumed token: <"="> Consumed token: <<QUOTED_STRING>: ""Tom Cruise""> Return: queryTerm Consumed token: <"OR"> Call: queryTerm Consumed token: <"actor"> Consumed token: <"="> Consumed token: <<QUOTED_STRING>: ""Kelly McGillis""> Return: queryTerm Return: expression Consumed token: <")"> Return: queryTerm Consumed token: <"AND"> Call: queryTerm Consumed token: <"keyword"> Consumed token: <"="> Consumed token: <<STRING>: "drama"> Return: queryTerm Return: expression Consumed token: <<EOF>> Return: parse </font> </code> </pre> </td> </tr> </tbody> </table> <br /> <p>该输出十分有用,因ؓ(f)它演CZ(jin)通过 queryTerm ?expression 的递归。queryTerm 的第一个实例实际上是一个由两个 queryTerm l成的表辑ּ?<a ><font color="#996699">?1</font></a>展示?jin)该解析路径的图形视图?</p> <br /> <a name="fig1"> <b>?1. 解析用户查询的图形表C?/b> </a> <br /> <img style="WIDTH: 500px; CURSOR: pointer" onclick="javascript:window.open(this.src);" src="http://www-128.ibm.com/developerworks/cn/db2/library/techarticles/dm-0401brereton/javacc-paper-figure1.gif" width="579" onload="javascript:if(this.width>500)this.style.width=500;" /> <br />如果(zhn)对?JavaCC 生成怎样?Java 代码感到好奇Q就惛_Ҏ(gu)看一看(但不要试图进行Q何更改!Q。?zhn)找C下内宏V? <p><b>UQLParser.java</b> —?在这一文g中,(zhn)将扑ֈ(zhn)在 UQLParser.jj 文g里的 PARSER_BEGIN ?PARSER_END 之间所攄的代码。?zhn)q会(x)发现 JJ 产生式方法已l被改变?Java Ҏ(gu)?jin)?</p><p>例如Qexpression() 规则已将被扩展ؓ(f)清单 10 中的代码?jin)?/p><a name="IDAEUUNB"><b>清单 10. UQLParser.java</b></a><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">static final public void expression() throws ParseException { trace_call("expression"); try { queryTerm(); label_1: while (true) { switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { case AND: case OR: ; break; default: jj_la1[0] = jj_gen; break label_1; } switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { case AND: jj_consume_token(AND); break; case OR: jj_consume_token(OR); break; default: jj_la1[1] = jj_gen; jj_consume_token(-1); throw new ParseException(); } queryTerm(); } } finally { trace_return("expression"); } } </font></code></pre></td></tr></tbody></table><br /><p>它与(zhn)最初在其中写入 queryTerm()、AND ?OR 所呈现的样子有些相像,但其余的是 JavaCC 所d的解析细节?/p><p><b>UQLParserConstants.java</b> —?该文件易于得到。?zhn)所定义的所有标记都在这里。JavaCC 只不q将它们记录在数l中q提供整型常数来引用该数l。清?11 展示?UQLParserConstants.java 的内宏V?</p><a name="IDASUUNB"><b>清单 11. UQLParserConstants.java</b></a><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">/* Generated By:JavaCC: Do not edit this line. UQLParserConstants.java */ package com.demo.stage1; public interface UQLParserConstants { int EOF = 0; int AND = 5; int OR = 6; int TITLE = 7; int ACTOR = 8; int DIRECTOR = 9; int KEYWORD = 10; int LPAREN = 11; int RPAREN = 12; int EQUALS = 13; int NOTEQUAL = 14; int STRING = 15; int QUOTED_STRING = 16; int DEFAULT = 0; String[] tokenImage = { "<EOF>", "\\" \\"", "\\"\\\\t\\"", "\\"\\\\r\\"", "\\"\\\\n\\"", "\\"and\\"", "\\"or\\"", "\\"title\\"", "\\"actor\\"", "\\"director\\"", "\\"keyword\\"", "\\"(\\"", "\\")\\"", "\\"=\\"", "\\"<>\\"", "<STRING>", "<QUOTED_STRING>", }; } </font></code></pre></td></tr></tbody></table><br /><p><b>UQLParserTokenManager.java</b> —?q是一个嵌接文件。JavaCC 该cȝ作记可予器。这是一D늡定标Cؓ(f)什么的代码。这里让人感兴趣的首要例E是 GetNextToken。解析器产生式方法将用该例程来判断采用哪条\l?</p><p><b>SimpleCharStream.java</b> —?UQLParserTokenManager 用该文g来表C将被解析的字符?ASCII ?</p><p><b>Token.java</b> —?其中提供?Token cL表示标记本n。本文的下一部分演C?Token cȝ用途?</p><p><b>TokenMgrError.java and ParseException</b>—?q些cd别表C可予器和分析器中的异常状况?</p><p><a name="IDAIVUNB"><span id="1161116" class="smalltitle"><strong>阶段 2 - l?JavaCC 代码d行ؓ(f)</strong></span></a></p><p><strong>注意Q?/strong>关于教程的这一部分Q请查阅代码?stage2 子目录。从q里开始所展示?JJ 文g是 JavaCCPaper/stage2/UQLParser.jj。ؓ(f)?jin)运行示?SQL 查询Q?zhn)q应该通过附带?moviedb.sql 文g创徏 MOVIEDB 数据库。请通过 <code><font face="Courier" size="1">db2 -tf moviedb.sql</font></code> 执行 DDL?</p><p>既然已经q行?jin)解析,我们需要对单个表达式采取行动了(jin)。这一阶段的目标是生成可运行的 DB2 SQL 查询q将q回用户期望的结果?/p><p>该过E应该首先从一个包含空白处的模?SELECTQ解析器填写此I白处?<a ><font color="#996699">清单 12</font></a>中显CZ(jin) SELECT 模板。解析器所生成的查询或怸像hc?DBA 所写的那样为最佳的Q但是它?yu)返回终端用h期望的正结果?</p><a name="listing12"><b>清单 12. SELECT 语句</b></a><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">SELECT TITLE, DIRECTOR FROM MOVIE WHERE MOVIE_ID IN ( -- parser will fill in here-- ); </font></code></pre></td></tr></tbody></table><br /><p>解析器填入的内容取决于它通过记号赋予器所采用的\径。例如,如果用户从上面输入查询:(x)</p><code><font face="Courier" size="1">(actor="Tom Cruise" or actor="Kelly McGillis") and keyword=drama" </font></code><p>那么解析器将Ҏ(gu) <a ><font color="#996699">?2</font></a> ?SQL 查询的遗漏部分中发出文本。它?yu)回送括P输入l端 queryTerm 的子查询q且?INTERSECT 代替 AND 以及(qing) UNION 代替 OR?</p><br /><a name="fig2"><b>?2. SQL 查询的解析器输出</b></a><br /><img style="WIDTH: 500px; CURSOR: pointer" onclick="javascript:window.open(this.src);" src="http://www-128.ibm.com/developerworks/cn/db2/library/techarticles/dm-0401brereton/javacc-paper-figure2.gif" width="597" onload="javascript:if(this.width>500)this.style.width=500;" /><br /><p>q将保 SQL 查询发出</p><code><font face="Courier" size="1">(actor = "Tom Cruise" or actor = "Kelly McGillis") and keyword=drama </font></code><p>如清单 13 中所C?/p><a name="IDADXUNB"><b>清单 13. 完整?SELECT 语句</b></a><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">SELECT TITLE, DIRECTOR FROM MOVIE WHERE MOVIE_ID IN ( ( SELECT MOVIE_ID FROM ACTOR WHERE NAME='Tom Cruise' UNION SELECT MOVIE_ID FROM ACTOR WHERE NAME='kelly McGillis' ) INTERSECT SELECT MOVIE_ID FROM KEYWORD WHERE KEYWORD='drama' ); </font></code></pre></td></tr></tbody></table><br /><p>正如前面提到的,很可能存在更快、更优的Ҏ(gu)来编写这个特定的查询Q但是此 SQL 生成正的l果。DB2 优化器通常可以解决性能斚w的不?/p><p>因此Q需要向 JavaCC 源代码添加什么来生成该查询呢Q?zhn)必须d动作和其他支持所定义语法的代码?<b><i>动作</i></b>是指为响应特定生式而执行的 Java 代码。在d动作之前Q首先要d向调用E序q回完整 SQL 的方法。ؓ(f)此,要在 JavaCC 文g的最上部分添加一个名?<code><font face="Courier" size="1">getSQL()</font></code> 的方法。?zhn)q应该给解析器的内部成员d private StringBuffer sqlSB。该变量表CZQ何解析阶D늚当前 SQL 字符丌Ӏ?<a ><font color="#996699">清单 14</font></a> 展示?UQLParser.jj ?PARSER_BEGIN/PARSER_END 部分。最后,?main() 试Ҏ(gu)中添加一些代码,用以输出和执行所生成?SQL 查询?</p><a name="listing14"><b>清单 14. PARSER_BEGIN/PARSER_END 部分</b></a><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">PARSER_BEGIN(UQLParser) package com.demo.stage2; import java.sql.DriverManager; import java.sql.Connection; import java.sql.Statement; import java.sql.ResultSet; import java.sql.Statement; import java.lang.StringBuffer; import java.io.StringReader; import java.io.Reader; public class UQLParser { private static StringBuffer sqlSB; // internal SQL representation. public UQLParser(String s) { this((Reader)(new StringReader(s))); sqlSB = new StringBuffer(); } public String getSQL() { return sqlSB.toString(); } public static void main(String args[]) { try { String query = args[0]; UQLParser parser = new UQLParser(query); parser.parse(); System.out.println("\\nSQL Query: " + parser.getSQL()); // Note: This code assumes a // default connection // (current userid and password). System.out.println("\\nResults of Query"); Class.forName( "COM.ibm.db2.jdbc.app.DB2Driver" ).newInstance(); Connection con = DriverManager.getConnection( "jdbc:db2:moviedb"); Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery(parser.getSQL()); while(rs.next()) { System.out.println("Movie Title = " + rs.getString("title") + " Director = " + rs.getString("director")); } rs.close(); stmt.close(); con.close(); } catch(Exception e) { e.printStackTrace(); } } } PARSER_END(UQLParser) </font></code></pre></td></tr></tbody></table><br /><p>现在Q填入由解析器来完成的动作。我们将先从一个容易的开始。在解析一个表辑ּ的时候,解析器每当解析“AND”时发出字“INTERSECT”,而解析“OR”时发出“UNION”。ؓ(f)此,要在 expression 产生式中?<AND> ?<OR> 标记之后插入自包含的 Java 代码块。该代码应向 sqlSB StringBuffer q加 INTERSECT ?UNION。清?15 中显CZ(jin)q些代码?/p><a name="IDAJYUNB"><b>清单 15. expression 所执行的动?/b></a><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">void expression() : { } { queryTerm() ( ( <AND> { sqlSB.append("\\nINTERSECT\\n"); } | <OR> { sqlSB.append("\\nUNION\\n"); } ) queryTerm() )* } </font></code></pre></td></tr></tbody></table><br /><p></p><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">queryTerm()</font></code></pre></td></tr></tbody></table><br />产生式内需要执行多个动作。这些Q务如下:(x) <ol><li>搜索名U映到它们各自?DB2 表和列上 </li><li>保存比较器(comparatorQ标? </li><li>比较字QcomparandQ{换ؓ(f) DB2 可以理解的Ş式,例如Q除?QUOTED_STRING 标记的双引号 </li><li>?sqlSB 发送合适的子查? </li><li>对于递归表达式的情况Q随之发出括受?</li></ol><p>对于所有这些Q务,(zhn)将需要一些局部变量。如清单 16 中所C,q些变量是在产生式中W一对花括号之间定义的?/p><a name="IDAAZUNB"><b>清单 16. queryTerm() 的局部变?/b></a><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">void queryTerm() : { Token tSearchName, tComparator, tComparand; String sComparand, table, columnName; } </font></code></pre></td></tr></tbody></table><br /><p>W一个Q务可用清?17 中的代码来完成。设|与所遇标记关联的合适的 DB2 表和列?/p><a name="IDALZUNB"><b>清单 17. 搜索名U映到 DB2</b></a><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console"> ( <TITLE> {table = "movie"; columnName = "title"; } | <DIRECTOR> {table = "movie"; columnName = "director"; } | <KEYWORD> {table = "keyword"; columnName = "keyword"; } | <ACTOR> {table = "actor"; columnName = "name"; } ) </font></code></pre></td></tr></tbody></table><br /><p>W二个Q务可用清?18 中的代码来完成。保存标C便可用于 SQL ~冲区?/p><a name="IDAWZUNB"><b>清单 18. 保存比较?/b></a><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console"> ( tComparator=<EQUALS> | tComparator=<NOTEQUAL> ) </font></code></pre></td></tr></tbody></table><br /><p>W三个Q务可用清?19 中的代码来完成。相应地讄比较字的|如果有必要,׃ QUOTED_STRING 标记中除d引号?/p><a name="IDAB0UNB"><b>清单 19. 准备比较?/b></a><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console"> tComparand=<STRING> { sComparand = tComparand.image; } | tComparand=<QUOTED_STRING> { // need to get rid of quotes. sComparand = tComparand.image.substring(1, tComparand.image.length() - 1); } </font></code></pre></td></tr></tbody></table><br /><p>W四个Q务可用清?20 中的代码来完成。完整的查询被q加C(jin) sql ~冲区?/p><a name="IDAM0UNB"><b>清单 20. ~写 SQL 表达?/b></a><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console"> { sqlSB.append("SELECT MOVIE_ID FROM ").append(table); sqlSB.append("\\nWHERE ").append(columnName); sqlSB.append(" ").append(tComparator.image); sqlSB.append(" '").append(sComparand).append("'"); } </font></code></pre></td></tr></tbody></table><br /><p>最后对于递归表达式的情况Q当解析器在表达式递归中看到括hQ就应该单地q行回送,如清?21 中所C?/p><a name="IDAX0UNB"><b>清单 21. 回送括?/b></a><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console"> <LPAREN> { sqlSB.append("("); } expression() <RPAREN> { sqlSB.append(")"); } </font></code></pre></td></tr></tbody></table><br /><p>清单 22 展示?jin)完整?queryTerm() 产生式?/p><a name="IDAC1UNB"><b>清单 22. 完整?queryTerms() 产生?/b></a><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">/** * Query terms may consist of a parenthetically * separated expression or may be a query criteria * of the form queryName = something or * queryName <> something. * */ void queryTerm() : { Token tSearchName, tComparator, tComparand; String sComparand, table, columnName; } { ( <TITLE> {table = "movie"; columnName = "title"; } | <DIRECTOR> {table = "movie"; columnName = "director"; } | <KEYWORD> {table = "keyword"; columnName = "keyword"; } | <ACTOR> {table = "actor"; columnName = "name"; } ) ( tComparator=<EQUALS> | tComparator=<NOTEQUAL> ) ( tComparand=<STRING> { sComparand = tComparand.image; } | tComparand=<QUOTED_STRING> { // need to get rid of quotes. sComparand = tComparand.image.substring(1, tComparand.image.length() - 1); } ) { sqlSB.append("SELECT MOVIE_ID FROM ").append(table); sqlSB.append("\\nWHERE ").append(columnName); sqlSB.append(" ").append(tComparator.image); sqlSB.append(" '").append(sComparand).append("'"); } | <LPAREN> { sqlSB.append("("); } expression() <RPAREN> { sqlSB.append(")"); } } </font></code></pre></td></tr></tbody></table><br /><p>像前面一L(fng)译ƈq行 UQLParser.jj。访?UQLParser.java q注意生式规则是如何被整齐地插入生成代码中的。清?23 中展CZ(jin)一?expression() Ҏ(gu)的扩展实例。请注意 jj_consume_token 调用之后的代码?/p><a name="IDAN1UNB"><b>清单 23. UQLParser.java 中的 expression() Ҏ(gu)</b></a><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">/** * An expression is defined to be a queryTerm followed by zero * or more query terms joined by either an AND or an OR. If two * query terms are joined with * AND then both conditions must * be met. If two query terms are joined with an OR, then * one of the two conditions must be met. */ static final public void expression() throws ParseException { trace_call("expression"); try { queryTerm(); label_1: while (true) { switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { case AND: case OR: ; break; default: jj_la1[0] = jj_gen; break label_1; } switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { case AND: jj_consume_token(AND); sqlSB.append("\\nINTERSECT\\n"); break; case OR: jj_consume_token(OR); sqlSB.append("\\nUNION\\n"); break; default: jj_la1[1] = jj_gen; jj_consume_token(-1); throw new ParseException(); } queryTerm(); } } finally { trace_return("expression"); } } </font></code></pre></td></tr></tbody></table><br /><p>按前面一栯行该代码。?zhn)必须?CLASSPATH 中包?db2java.zip。这ơ,当?zhn)q行</p><code><font face="Courier" size="1">java 'cp bin;c:/sqllib/db2java.zip com.demo.stage2.UQLParser "(actor=\\"Tom Cruise\\" or actor=\\"Kelly McGillis\\") and keyword=drama" </font></code><p>Ӟ它将生成清单 24 中的输出?/p><a name="IDA21UNB"><b>清单 24. 查询 (actor="Tom Cruise" or actor="Kelly McGillis") and keyword=drama ?UQL2Parser 输出</b></a><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">Call: parse Call: expression Call: queryTerm Consumed token: <"("> Call: expression Call: queryTerm Consumed token: <"actor"> Consumed token: <"="> Consumed token: <<QUOTED_STRING>: ""Tom Cruise""> Return: queryTerm Consumed token: <"or"> Call: queryTerm Consumed token: <"actor"> Consumed token: <"="> Consumed token: <<QUOTED_STRING>: ""Kelly McGillis""> Return: queryTerm Return: expression Consumed token: <")"> Return: queryTerm Consumed token: <"and"> Call: queryTerm Consumed token: <"keyword"> Consumed token: <"="> Consumed token: <<STRING>: "drama"> Return: queryTerm Return: expression Consumed token: <<EOF>> Return: parse SQL Query: SELECT TITLE,DIRECTOR FROM MOVIE WHERE MOVIE_ID IN ( (SELECT MOVIE_ID FROM actor WHERE name = 'Tom Cruise' UNION SELECT MOVIE_ID FROM actor WHERE name = 'Kelly McGillis') INTERSECT SELECT MOVIE_ID FROM keyword WHERE keyword = 'drama') Results of Query Movie Title = Top Gun Director = Tony Scott Movie Title = Witness Director = Peter Weir </font></code></pre></td></tr></tbody></table><br /><p>试更多使用(zhn)的解析器的查询。试一试?NOTEQUAL 标记的查询,比如 actor<>"Harrison Ford"。尝试一些像“title=”一L(fng)非法查询Q看看将发生什么情c(din)通过非常的几行 JavaCC 代码Q?zhn)q成了(jin)非常有效的终端用h询语a?/p><br /><table cellspacing="0" cellpadding="0" width="100%" border="0"><tbody><tr><td><img style="WIDTH: 500px; CURSOR: pointer" onclick="javascript:window.open(this.src);" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" onload="javascript:if(this.width>500)this.style.width=500;" /></td></tr></tbody></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tbody><tr align="right"><td><table cellspacing="0" cellpadding="0" border="0"><tbody><tr><td valign="center"><img style="CURSOR: pointer" onclick="javascript:window.open(this.src);" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" onload="javascript:if(this.width>500)this.style.width=500;" border="0" /><br /></td><td valign="top" align="right"><a class="fbox" ><b><font face="Verdana" color="#996699">回页?/font></b></a></td></tr></tbody></table></td></tr></tbody></table><br /><br /><p><a name="3"><span id="1111666" class="atitle"><strong><font size="4">最后要考虑的问?/font></strong></span></a></p><p>JavaCC 除了(jin)提供解析器生成器之外Q还提供 JJDOC 工具Q用以编制巴U斯-诺尔范式QBacchus-Nauer FormQ表C的语法。JJDOC 可以使?zhn)易于向终端用h供他们所使用语言的描q。例如,在附带代码中提供?ant 文g有一个“bnfdoc”目标?/p><p>JavaCC q提供名?JJTree 的工兗该工具提供?wi)和节点c,使?zhn)易于代码分成单ȝ解析和动作类。l该实例Q?zhn)可以考虑为查询编写一个简单优化器Q以消除不必要的 INTERSECT ?UNION。?zhn)可以通过讉K解析?wi)的节点以?qing)合ƈ怼的相邻节点(例如Qactor="Tom Cruise" ?actor="Kelly McGillis"Q来完成该工作?/p><p>JavaCC 拥有一个丰富的语法库。?zhn)在自q写解析器之前Q一定要查看 JavaCC ?examples 目录Q以便可能获取已构徏好的解决Ҏ(gu)?/p><p>请务必阅?JavaCC Web 上?Frequently Asked Questions q访?<a ><font color="#5c81a7">comp.compilers.tools.javacc</font></a> 上的 javacc 新闻l以便更好地理解 JavaCC 的所有功能和Ҏ(gu)?</p><br /><table cellspacing="0" cellpadding="0" width="100%" border="0"><tbody><tr><td><img style="WIDTH: 500px; CURSOR: pointer" onclick="javascript:window.open(this.src);" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" onload="javascript:if(this.width>500)this.style.width=500;" /></td></tr></tbody></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tbody><tr align="right"><td><table cellspacing="0" cellpadding="0" border="0"><tbody><tr><td valign="center"><img style="CURSOR: pointer" onclick="javascript:window.open(this.src);" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" onload="javascript:if(this.width>500)this.style.width=500;" border="0" /><br /></td><td valign="top" align="right"><a class="fbox" ><b><font face="Verdana" color="#996699">回页?/font></b></a></td></tr></tbody></table></td></tr></tbody></table><br /><br /><p><a name="4"><span id="6111666" class="atitle"><strong><font size="4">l束?/font></strong></span></a></p><p>JavaCC 是一个健壮的工具Q可用于定义语法q且易于?Java 商业应用E序中包含该语法的解析和异常处理。通过本文Q我们说明了(jin) JavaCC 可用于ؓ(f)数据库系l的l端用户提供一U功能强大却很简单的查询语言?/p><br /><table cellspacing="0" cellpadding="0" width="100%" border="0"><tbody><tr><td><img style="WIDTH: 500px; CURSOR: pointer" onclick="javascript:window.open(this.src);" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" onload="javascript:if(this.width>500)this.style.width=500;" /></td></tr></tbody></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tbody><tr align="right"><td><table cellspacing="0" cellpadding="0" border="0"><tbody><tr><td valign="center"><img style="CURSOR: pointer" onclick="javascript:window.open(this.src);" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" onload="javascript:if(this.width>500)this.style.width=500;" border="0" /><br /></td><td valign="top" align="right"><a class="fbox" ><b><font face="Verdana" color="#996699">回页?/font></b></a></td></tr></tbody></table></td></tr></tbody></table><br /><br /><p><a name="resources"><span id="1666661" class="atitle"><strong><font size="4">参考资?</font></strong></span></a></p><ul><li>(zhn)可以参阅本文在 developerWorks 全球站点上的 <a ><font color="#5c81a7">英文原文</font></a>. <br /><br /></li><li>可在 <a ><font color="#5c81a7">JavaCC |站</font></a>上找?JavaCC E序包?<br /><br /><br /><br /></li><li>?JohnsonQStephen CQAT&T Bell LaboratoriesQMurray HillQNew Jersey 07974Q所写论?YACCQ?<a ><font color="#5c81a7">Yet Another Compiler Compiler</font></a>中第一ơ描qC(jin)q个可广泛获得的“编译器的编译器”?<br /><br /><br /><br /></li><li>一由 Oliver Ensileng 撰写?JavaWorld 的好文章 <a ><font color="#5c81a7">Build your own Languages with JavaCC</font></a>?<br /><br /><br /><br /></li><li>Jocelyn Paine ?<a ><font color="#5c81a7">Introduction to JJTree</font></a>?<br /><br /><br /><br /></li><li>可在 <a ><font color="#5c81a7">Wikipedia</font></a> 中找到对于上下文无关语法的很好解释?<br /><br /><br /></li></ul><br /><table cellspacing="0" cellpadding="0" width="100%" border="0"><tbody><tr><td><img style="WIDTH: 500px; CURSOR: pointer" onclick="javascript:window.open(this.src);" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" onload="javascript:if(this.width>500)this.style.width=500;" /></td></tr></tbody></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tbody><tr align="right"><td><table cellspacing="0" cellpadding="0" border="0"><tbody><tr><td valign="center"><img style="CURSOR: pointer" onclick="javascript:window.open(this.src);" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" onload="javascript:if(this.width>500)this.style.width=500;" border="0" /><br /></td><td valign="top" align="right"><a class="fbox" ><b><font face="Verdana" color="#996699">回页?/font></b></a></td></tr></tbody></table></td></tr></tbody></table><br /><br /><p><a name="author"><span id="1661611" class="atitle"><strong><font size="4">关于作?/font></strong></span></a></p><table cellspacing="0" cellpadding="0" width="100%" border="0"><tbody><tr><td colspan="2"><strong><font size="4"><img style="WIDTH: 500px; CURSOR: pointer" onclick="javascript:window.open(this.src);" src="http://www.ibm.com/i/c.gif" width="100%" onload="javascript:if(this.width>500)this.style.width=500;" /></font></strong></td></tr><tr valign="top" align="left"><td><p><strong><font size="4"></font></strong></p></td><td><p>JoAnn Brereton ?IBM ?Software GroupQFederal Software Services 的一位高UY件工E师。她已ؓ(f) IBM ~程q?20 q了(jin)。她最q的目包括?CBS、Warner Brothers ?CNN ?sh)视|构档案搜烦(ch)引擎?/p></td></tr></tbody></table><img src ="http://m.tkk7.com/landy/aggbug/44765.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://m.tkk7.com/landy/" target="_blank">独孤q客</a> 2006-05-06 16:21 <a href="http://m.tkk7.com/landy/archive/2006/05/06/44765.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>从lex&yacc说到~译?6.数学表达?http://m.tkk7.com/landy/archive/2006/05/06/44764.html独孤q客独孤q客Sat, 06 May 2006 08:20:00 GMThttp://m.tkk7.com/landy/archive/2006/05/06/44764.htmlhttp://m.tkk7.com/landy/comments/44764.htmlhttp://m.tkk7.com/landy/archive/2006/05/06/44764.html#Feedback0http://m.tkk7.com/landy/comments/commentRss/44764.htmlhttp://m.tkk7.com/landy/services/trackbacks/44764.html阅读全文

    独孤q客 2006-05-06 16:20 发表评论
    ]]>
    从lex&yacc说到~译?5.实用javacc)http://m.tkk7.com/landy/archive/2006/05/06/44763.html独孤q客独孤q客Sat, 06 May 2006 08:18:00 GMThttp://m.tkk7.com/landy/archive/2006/05/06/44763.htmlhttp://m.tkk7.com/landy/comments/44763.htmlhttp://m.tkk7.com/landy/archive/2006/05/06/44763.html#Feedback1http://m.tkk7.com/landy/comments/commentRss/44763.htmlhttp://m.tkk7.com/landy/services/trackbacks/44763.html阅读全文

    独孤q客 2006-05-06 16:18 发表评论
    ]]>
    从lex&yacc说到~译?4.文法识别(一))http://m.tkk7.com/landy/archive/2006/05/06/44762.html独孤q客独孤q客Sat, 06 May 2006 08:17:00 GMThttp://m.tkk7.com/landy/archive/2006/05/06/44762.htmlhttp://m.tkk7.com/landy/comments/44762.htmlhttp://m.tkk7.com/landy/archive/2006/05/06/44762.html#Feedback0http://m.tkk7.com/landy/comments/commentRss/44762.htmlhttp://m.tkk7.com/landy/services/trackbacks/44762.html

    没想到这一pd文g能得?span lang="EN-US">csdn和大家的q么看好,首先要感谢大家的赏识和csdn的推?那么我就更没有理׃写好q下面的几篇文章?本来我的计划是简单把lex和yacc介绍完后q接进入编译器的构造的技术细节问题讨?但是最q看?jin)一些国外经典教材后,发现文法的识别问题在~译原理和技术中是个l不能忽视的问题.即现在有了(jin)yacc工具来帮助我来识别文?但是有些时候还是需要我们自己来写简单的语法分析?

    1.什么是文法识别(语法分析)

    首先要告诉大家的?span lang="EN-US">,q里的文法识别是指的上下文无关的文法,也就是上一节我们一直在讨论的那?BNF?

    比如?span lang="EN-US">,我写?jin)一?

    if (a>6+5) printf(“OK!?; else printf(“No!?;

    那么它匹配的文法也就?span lang="EN-US">

    if-stmt -> if expr stmt

             | if expr stmt else stmt

    我们通常要ؓ(f)一个程序语a写出很多BNF式的文法,怎么知道q句话是匚w的哪个文?q就是语法分析器(或者叫文法分析器要做的工作).知道?jin)是那句文法?我们才能对这句话做出正确的解?所以文法识别是个不可忽视的工作.下面我来看看我们怋用的文法识别的算?

    2.自顶向下的算?LL法)

    自顶向下的语法分析算法是十分单的.自顶向下的算法也叫LL法.LL(k)是向前预测k个符L(fng)自顶向下的算?不过无论是我们国内的~译教程q是国外的经典教E都是只讨论?jin)LL(1)法.因ؓ(f)一般的E序语言,只用LL(1)法已l够了(jin).q里我们同样也只是讨论LL(1)法.

    其中有种Ҏ(gu)的算法叫做递归下降的算?span lang="EN-US">,在C语言?׃函数本n是可以递归?所以实现这U算法就只需要写单的几个函数的递归q程是?

    Z么叫自顶向下?span lang="EN-US">?因ؓ(f)在分析过E中,我们是从语法?wi)的树(wi)顶逐步向树(wi)底分析的,所以叫自顶向下的算?

    Z(jin)方便说明自顶向下法的简单?span lang="EN-US">,我们来看一?lt;<Compilers Principles,Techniques,and Tools>>中的一个例?(本系列文章经常要引用国外l典著作的范?希望大家不要告我抄袭,我实在找不到比大师的范例更经典的范例?

    ?span lang="EN-US">4.1

    考虑一?span lang="EN-US">Pascal中定义变量的文法.

    特别说明,q里?b>dotdot表示?.?

    type -> simple | id | array [ simple ] oftype

    simple -> integer |char| num dotdot num

    在ؓ(f)array[ num dotdot num] of integer构造一个分析数的时?span lang="EN-US">,该算法就是从根结点开?

    下面我们通过其中主要的三个步骤来看看法的实现原?span lang="EN-US">.

    W一步分?span lang="EN-US">:

    __________________________________________________________________________________

    首先分析的是输入的字W串W一个串”array?判断出它属于type?b>First集合.所以在图中的分析树(wi)部分,我们的当前分析就是树(wi)根结点type.(图中标上头,pC是当前正在分析的部?.

    q里遇到一个新名词:First集合.在大学里的编译课E肯定是讲过First集合的吧.不过我还是要在这里重复一ơ了(jin).

    名词解释First集合:

    在对文法产生式进行判断的时?span lang="EN-US">,每个产生式都是由好几个终l符和非l结W构?比如

    本例中的文法

    type -> simple

    | id

    | array [ simple ] oftype

    simple -> integer

    |char

    | num dotdot num

    判断type的生式的时?如果我们把每个生式里面的simple,id,array, [ ,simple ,] , of , typeq些l结W和非终l符都进行判断的?那么׃(x)涉及(qing)到”试验和错误”的问题.当一个文法生式分析到最?发现q不匚w,必然会(x)产生回溯的问?p回到?从新开始对W二个生式逐步q行判断分析.我们知道,回溯的算法效率肯定是十分低效率的.但是实际上我们完全可以避免这U回溯算?而完成同样工作的文法分析.q就产生?jin)计?b>First集合的理论和以及(qing)后面?b>左提公因?/b>的问?

    First集合单地?是一个非l结W的最开头的字符?l结W号)的集?比如?

    First(simple) = { integer, char, num }

    First(type) = First(simple) U { id, array }

    q里?span lang="EN-US">type的一个生式中有个simple非终l符在其开?那么simple的开头字W串同时也可以是simple,所以First(simple)也是First(type)的一部分.

    Z么我们只计算每个非终l符的最开头的l结W?span lang="EN-US">? 因ؓ(f)我们q里是考虑的LL(1)法,LL(1)法只向前预一个字W号,所以我们只考虑一个First集合可以判断出是哪个文法生式?

    q里听v来似乎有些不太可?span lang="EN-US">,一个生式有那么千百万?如果单单只看W一个非l结W号,如果p说明一个输入串到底是哪个生式? 如果有两个生式的最开头一h么?比如像if语句,那怎么? 但其实我们几乎所有的E序语言的文法都可以通过LL(1)来分析出?原因是我们可以通过左提公因?/b>来把最开头的相同的生式的公ql符h取出?留下两个子生式,而他们的最开头的非终l符号不相同.

    左提公因?span lang="EN-US">:

    ?span lang="EN-US">4.2

    考虑文法

    A -> ab

    |ac

    q里,A的两个生式中最开头的l结W号都是?b>a?那么无法通过q个最开头的l结W号来判断一个输入串到底该是哪个产生式了(jin).那么我们可以修改文法?

    A -> aA?/i>

    A?/span> -> b | c

    q样一?span lang="EN-US">,一个文法变成两?但是无论Aq是A?它们的每个生式?b>First集合都是不相交的.所?他们能够只通过最开头的l结W号来判断是哪个产生?

    q个变化q程有点x们的代数里面?span lang="EN-US"> ab + ac = a(b+c),所以叫它左提公因式.

    q只是个单的左提公因式的例子,实际当中q会(x)遇到一些复杂的问题.但是无论是哪个编译教?都会(x)l出针对一切文法的左提公因式的法.同样,计算First集合的算法也在教材中详细讲解?我就不在q里再描qC(jin).

    W二步分?span lang="EN-US">:

    __________________________________________________________________________________

    l过W一步的考察输入串中的第一个串?span lang="EN-US">”array”属于非l结W号typeW三个生式的First集合,那么可以确定这个串实为type文法W三个生式的串.所以在W二步中,展开出type的第三个产生式出? type -> array[ simple ]of integer

    那么接下来就是l分析构造出来的type -> array[ simple] of integer产生式中的每个结?

    所以箭头又攑ֈ?jin)分析?wi)?span lang="EN-US">type的第一个孩l点array?因ؓ(f)array是终l符?如果它和输入中的当前头所指的l结W号相同,那么头都往下移动一l点到?b>[‘符?同样?׃分析?wi)中的?b>[?/b>是终l符?那么只要看输入中的串是否是?b>[‘就可以?如果?那么l箋往下分?分析到分析数中的simple的时?׃simple是非l结W号,那么需要考虑simple的生式?

    W三步分?span lang="EN-US">:

    __________________________________________________________________________________

    在第二步?span lang="EN-US">,分析到分析数中的simple子结点的时?׃simple是非l结W号,那么需要考虑simple的生式.simple一共有三个产生?通过输入串当前的串是?b>num?是属于simple产生式中W?个生式的First集合,所以simple在分析数中就按第三个产生?i>simple -> num dotdot num 来展开.那么分析头同样,也自动移动到simple的第一个子l点num上l分?

    M说来,q中自顶向下的分析原理就基本上是上面的过E?通过计算产生式的First集合,来逐步产生非终l符的生式.最后的分析?wi)都会(x)划归到l结W来q行判断(非终l符h无法q行直接判断?一定要展开q后才行).

    看了(jin)原理,我们再看实现的伪代码.代码很简?

    ________________________________________________________________________

    void match(char token)

    {

        if lookahead == token)

    lookahead = token;

        else

         error(0);

    }

    void type()

    {

        if( lookahead == integer || lookeahead == char || lookahead == num)

            simple();

        else if( lookahead == id)

            match(id);

        else if( lookahead == array)

        {

            match(array); match(')'); simple(); match(')'); match(of); type();

        }

        else

            error(0);

    }

    void simple()

    {

        if( lookahead == integar) match(integer);

        else if( lookahead == char) match(char);

        else if( lookahead == num)

        {

            match(num); match(dotdot); match(num);

        }

        else

            error(0);

    }

    _________________________________________________________________________

    注意:q里的代码都是纯的语法分析代?实际执行q程中ƈ没有什么用?但是我们构造语法树(wi)parse-tree的代码就是镶嵌在q些U的语法分析代码?



    独孤q客 2006-05-06 16:17 发表评论
    ]]>从lex&yacc说到~译?3.范式文法)http://m.tkk7.com/landy/archive/2006/05/06/44761.html独孤q客独孤q客Sat, 06 May 2006 08:16:00 GMThttp://m.tkk7.com/landy/archive/2006/05/06/44761.htmlhttp://m.tkk7.com/landy/comments/44761.htmlhttp://m.tkk7.com/landy/archive/2006/05/06/44761.html#Feedback0http://m.tkk7.com/landy/comments/commentRss/44761.htmlhttp://m.tkk7.com/landy/services/trackbacks/44761.html

    从这一节开?span lang="EN-US">,我们qq入~译器构造的正题?不得不说,前面的词法扫描器在整个编译器部分只是个很很的l成,而这两节讲述的语a构造器才能真正为我们的~译工作起到重要的作?q些东西怿大家在大学的~译原理的课E已l学?jin)不?那么本文我也只是大致地带q?让大家回忆v大学的知?重要的yacc使用技巧等{?我将在后面的内容讲出.

    ?span lang="EN-US">3.1

    exp -> exp op exp | (exp) | number

    op -> + | - | *

    q里是一个定义的带有加法,减法,乘法的简单整数算术表辑ּ的文?其中_体表示的是l结W号,也就是不能有产生式生成的W号.而exp,op是非终l符,它们都是׃个?>”符h产生?

    比如100 + 222 *123123 ?888+11)是W合上述文法的具体的表达?

    注意,在文法定义中,是可以递归?所以exp产生式右边的式子中可以再ơ出现exp.

    q里?span lang="EN-US">|和正则表辑ּ一?表示的选择的意?也就是说,exp可以是exp op exp或?exp)再或者number.

    下面让我们看?span lang="EN-US"><<~译原理?qing)实?gt;>书中的一个关于BNF文法的介l?

    比如说我们有个数学表辑ּ(34-3)*42,然后我们来看看上面的exp文法怎么来推D别它.

    (1) exp => exp op exp               [exp ->exp op exp]

    (2)     => exp op number            [exp ->number    ]

    (3)     => exp * number             [op -> *         ]

    (4)     => (exp) * number           [exp ->(exp)     ]

    (5)     => (exp op exp) * number    [exp ->exp op exp]

    (6)     => (exp op number)* number  [exp -> number   ]

    (7)     => (exp ?number) * number [op -> -         ]

    (8)     => (number–number)* number [exp -> number   ]

    最l?span lang="EN-US">,exp里面全部的非l结W号全部变成?jin)终l符?那么推导完成.

    q种推导十分像我们在L数学中讲到的命题推理.其实形式语言的推导的数学基础是我们L数学的命题推?

    在推DE中,其实是把原来的文法中的递归展开.那么我们在推导的q程,也就很容易实现分析树(wi)的生?而分析树(wi)是我们~译E序中十分重要的信息?我们之所以前面又做词法分?又做语法分析的目标就是ؓ(f)?jin)生成分析?wi).有了(jin)?我们~译E序在后面的代码生成q程中将变得Ҏ(gu)癑ր?

     L(fng):

    ?span lang="EN-US">3.2

    同样?span lang="EN-US"><<~译原理?qing)实?gt;>书上的例?

    ?span lang="EN-US">E -> E+a | a 表示的文法ؓ(f)G,那么考虑它生成的表达L(G)

    如果由标准的数学定义,那么我们用公式L(G)={s | exp =>* s }表示一U文法G.

    s代表记号W号的Q意数l串,也就是我们的l结W号.而exp代表非终l符?=>*表示一pd的从非终l符到终l符L(fng)推导q程.q里*有点像我们在讲述正则表达式中?W号一?它表C?到无限次的重?所?>*是表示0ơ到无限ơ的推导q程.

    L(G) = {a,a+a,a+a+a,a+a+a+a,…}

    E => E+a => E+a+a => E+a+a+a

    同时,在我们的~译课本?又经常讲q另一U数学表达方式来阐述文法的定?

    G=(T,N,P,S)

    注意,q里的T,N,P,S都是集合.

    T表示l结W号(terminal),也就是这里{a,+}

    N表示非终l符?nonterminal),也就是这里{E},但是N不能与T怺.

    P表示产生?production)或者文法规?grammar rule)的集?q里它只有一个元? E -> E+a

    S表示集合N的开始符?start symbol).关于S,本h也搞不清楚它的用?所以很抱歉!

    ?span lang="EN-US">3.3

    q是我们CE序语言中经怋用if else文法

    statement -> if-stmt | other

    if-stmt -> if(exp) statement | if (exp) statement else statement

    exp -> 0|1

    statement是我们C语言中用语?它的产生式包括了(jin)两种可能,一是if-stmt语句,二是other.然后我们又另外定义if-stmt语句的生式.q里有两U情?一是没有else?另外是有else?里面我们又用了(jin)递归.if-stmt本来是包含在statement里面?可是我们又在if-stmt的生式中用statement.正是因ؓ(f)文法中允?dng)R归,所以它比v我们前面讲的正则表达式有更广泛的表示能力,但同?文法的推D别也更加法复?按照~译原理的书c?一般讲完BNF文法?p重点讲解文法的推导算?一共有两种,一是LL法,自顶向下的算?二是LR法,自底向上的算?LL法比较?其中q有一U特D的情况,是我们下一节要讲的递归下降的算?׃C语言中的函数本来可以递归,那么实现q中递归下降的算法是十分单的,而且对于我们一般的E序设计语言来说,虽然它的法能力很弱,但是已经是够用?而关于LR的算?那么是一个大N?它的法能力最?但是实现h十分困难,q好,已经有科学家为我们提供了(jin)yacc(或者叫bison)q个工具,可以来自动生成LR的文法推导算?q就是我们一直在提到的yacc工具?

    回过头来,我们看看下面的程?

    if(0) other else other

    的分析树(wi)

    思?span lang="EN-US">: Z么要把文法最l分析成?wi)结?

    因ؓ(f)文法本n是递归?span lang="EN-US">,而表C的递归的最好数据结构就是树(wi),所以我们把文法弄成?wi)结构?后面在处理代码生成等问题?也可以用递归来很Ҏ(gu)地完?

    ?span lang="EN-US">3.4

    q里我给?span lang="EN-US">microsoft在msdn中对于C语言的statement的文?

    注意,q里?W号替代?jin)我们前面生式?>

    statement :

    labeled-statement
    compound-statement
    expression-statement
    selection-statement
    iteration-statement
    jump-statement
    try-except-statement   /* Microsoft Specific */
    try-finally-statement   /* Microsoft Specific */

    jump-statement :

    goto identifier ;
    continue;
    break;
    return expression opt ;

    compound-statement :

    { declaration-list opt statement-list opt }

    declaration-list :

    declaration
    declaration-list declaration

    statement-list :

    statement
    statement-list statement

    expression-statement :

    expression opt ;

    iteration-statement :

    while ( expression ) statement
    do statement while ( expression );
    for ( expression opt ; expression opt ; expression opt ) statement

    selection-statement :

    if ( expression ) statement
    if ( expression ) statement else statement
    switch ( expression ) statement

    labeled-statement :

    identifier : statement
    case constant-expression : statement
    default : statement

    try-except-statement :   /* Microsoft Specific */

    __try compound-statement
    __except ( expression ) compound-statement

    try-finally-statement :   /* Microsoft Specific */

    __try compound-statement
    __finally compound-statement



    独孤q客 2006-05-06 16:16 发表评论
    ]]>
    从lex&yacc说到~译?2.flex的?http://m.tkk7.com/landy/archive/2006/05/06/44760.html独孤q客独孤q客Sat, 06 May 2006 08:15:00 GMThttp://m.tkk7.com/landy/archive/2006/05/06/44760.htmlhttp://m.tkk7.com/landy/comments/44760.htmlhttp://m.tkk7.com/landy/archive/2006/05/06/44760.html#Feedback0http://m.tkk7.com/landy/comments/commentRss/44760.htmlhttp://m.tkk7.com/landy/services/trackbacks/44760.html阅读全文

    独孤q客 2006-05-06 16:15 发表评论
    ]]>
    从lex&yacc说到~译?1.正则表达?http://m.tkk7.com/landy/archive/2006/05/06/44759.html独孤q客独孤q客Sat, 06 May 2006 08:12:00 GMThttp://m.tkk7.com/landy/archive/2006/05/06/44759.htmlhttp://m.tkk7.com/landy/comments/44759.htmlhttp://m.tkk7.com/landy/archive/2006/05/06/44759.html#Feedback0http://m.tkk7.com/landy/comments/commentRss/44759.htmlhttp://m.tkk7.com/landy/services/trackbacks/44759.html阅读全文

    独孤q客 2006-05-06 16:12 发表评论
    ]]>
    վ֩ģ壺 ĻӰԺƵ| Ƶվ߹ۿ| ŮɫëƬѿ| þֻƷ99re| Ůžž| avһ | AVרպ| ƹƵӰԺ߹ۿ| ɫɫۺվ| ۲ӰԺ߲wwwѹۿ| ߹͵Ʒ| Ʒѿþþ| ѵӰ| ޹ҹ߲| ۺҹ | ͵͵ͼƬ| ձ| ޹˾þþƷ| þþþƷҹѲ| ĻѸ| Ƶ| ɫavѹۿ| þþþŷղAV| ۺһ| ߾Ʒһ| һëƬaѲɫӰ | þþþþþþþùƷ| Ѹ߹ۿ| ޹ۺ| ˴ֳսŮ2021ƷƵ | ߲| ޾ƷƵþ| ߳ˮ| 㽶һ| ޹ƷþþþþԻ| ޹˾Ʒ| ƷþþþþӰ| ձѹۿ| ڵֻˬƵ | Ƶۿ| ޳˶|