﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>IT博客-Welcome To Oliver_Yin's  Blog!-文章分类-正则表达式</title><link>http://www.cnitblog.com/oliver_yin/category/502.html</link><description>打字--&gt; 死打字--&gt;抄写程序--&gt;研究程序--&gt;分析需求 
--&gt;分析架构--&gt;项目管理--&gt;企业信息化--&gt;.........</description><language>zh-cn</language><lastBuildDate>Sat, 01 Oct 2011 08:50:18 GMT</lastBuildDate><pubDate>Sat, 01 Oct 2011 08:50:18 GMT</pubDate><ttl>60</ttl><item><title>软件架构（转并收藏）</title><link>http://www.cnitblog.com/oliver_yin/articles/3460.html</link><dc:creator>生活像一团麻</dc:creator><author>生活像一团麻</author><pubDate>Fri, 21 Oct 2005 01:40:00 GMT</pubDate><guid>http://www.cnitblog.com/oliver_yin/articles/3460.html</guid><wfw:comment>http://www.cnitblog.com/oliver_yin/comments/3460.html</wfw:comment><comments>http://www.cnitblog.com/oliver_yin/articles/3460.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/oliver_yin/comments/commentRss/3460.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/oliver_yin/services/trackbacks/3460.html</trackback:ping><description><![CDATA[根据<A class=bluekey href="http://www.yesky.com/key/4747/159747.html" target=_blank>Linda</A> Rising的《Pattern Almanac》一书，已知的架构模式有七十多种。这是一个只多不少的统计，其中包括了很多通常认为是设计模式的模式，比如Bridge，Facade，Interpreter，Mediator等模式通常认为是设计模式，但是在许多情况下，也可以作为架构模式出现，因此也常常被当作架构模式。<BR><BR>　　<B>Layers架构模式</B><BR><BR>　　在<A class=bluekey href="http://www.yesky.com/key/4340/174340.html" target=_blank>收集</A>到用户对软件的要求之后，架构设计就开始了。架构设计一个主要的目的，就是把系统划分成为很多"板块"。划分的方式通常有两种，一种是横向的划分，一种是纵向划分。<BR><BR>　　横向划分将系统按照商业目的划分。比如一个<A class=bluekey href="http://www.yesky.com/key/1561/136561.html" target=_blank>书店</A>的管理系统可以划分成为进货、销售、<A class=bluekey href="http://www.yesky.com/key/633/165633.html" target=_blank>库存</A>管理、员工管理等等。<BR><BR>　　纵向划分则不同，它按照<A class=bluekey href="http://www.yesky.com/key/4805/289805.html" target=_blank>抽象</A>层次的高低，将系统划分成"层"，或叫Layer。比如一个公司的内网管理系统通常可以划分成为下面的几个Layer:<BR><BR>　　一、网页，也就是用户界面，负责显示数据、接受用户输入；<BR><BR>　　二、领域层，包括JavaBean或者COM对象、<A class=bluekey href="http://www.yesky.com/key/4672/134672.html" target=_blank>B2B</A>服务等，封装了必要的商业<A class=bluekey href="http://www.yesky.com/key/3446/223446.html" target=_blank>逻辑</A>，负责根据商业逻辑决定显示什么数据、以及如何根据用户输入的数据进行计算；<BR><BR>　　三、数据库，负责存储数据，按照查询要求提供所存储的数据。<BR><BR>　　四、操作系统层，比如Windows NT或者Solaris等<BR><BR>　　五、硬件层，比如SUN E450服务器等<BR><BR>　　有人把这种Layer叫做Tier，但是Tier多带有<A class=bluekey href="http://www.yesky.com/key/365/180365.html" target=_blank>物理</A>含义，不同的Tier往往位于不同的计算机上，由网络连接起来，而Layer是纯粹逻辑的概念，与物理划分无关。 <BR><BR>　　Layers架构模式的好处是：<BR><BR>　　第一、任何一层的变化都可以很好地局限于这一层，而不会影响到其他各层。<BR><BR>　　第二、更容易容纳新的技术和变化。Layers架构模式容许任何一层变更所使用的技术<BR><BR>　　<B>Fa?ade架构模式</B><BR><BR>　　外部与一个子系统的通讯必须通过一个统一的门面（Facade）对象进行，这就是Facade模式。<BR><BR>　　现代的软件系统都是比较复杂的，设计模式的任务就是协助设计师处理复杂系统的设计。<BR><BR>　　设计师处理复杂系统的一个常见方法便是将其"分而治之"，把一个系统划分为几个较小的子系统。但是这样做了以后，设计师往往仍然会发现一个子系统内仍然有太多的类型要处理。而使用一个子系统的使用端往往只关注一些特定的功能，却要同时与子系统内部的许多对象打交道后才能达到目的，请见下面的对象图。<BR><BR>
<TABLE width="90%" align=center border=0>
<TBODY>
<TR>
<TD>
<DIV align=center><IMG src="http://dev.yesky.com/imagelist/05/06/a87695fj2vm9.gif" border=0><BR>图4、Facade架构模式的结构图。</DIV></TD></TR></TBODY></TABLE><BR>　　这就是一种不便，它使得系统的逻辑变得不必要的复杂，维护成本提高，复用率降低。<BR><BR>　　用一个范例说明，中国<A class=bluekey href="http://www.yesky.com/key/3161/168161.html" target=_blank>大陆</A>的<A class=bluekey href="http://www.yesky.com/key/3390/138390.html" target=_blank>医院</A>便是一个子系统，按照部门职能，这个系统可以划分为挂号、门诊、划价、化验、收银、取药等。看病的病人要与这些部门打交道，就如同一个子系统的使用端与一个子系统的各个类型打交道一样，不是一件容易的事情。<BR><BR>　　首先病人必须先挂号，然后门诊。如果医生要求化验，病人必须首先划价，然后缴款，才能到化验部门做化验。化验后，再回到门诊室，请见下面的对象图。<BR><BR>
<TABLE width="90%" align=center border=0>
<TBODY>
<TR>
<TD>
<DIV align=center><IMG src="http://dev.yesky.com/imagelist/05/06/ho315ggk8hk0.jpg" border=0><BR>图5、描述病人在医院里的体验。图中的方框代表医院。 </DIV></TD></TR></TBODY></TABLE><BR>　　解决这种不便的方法便是引进Facade模式。仍然通过医院的范例说明，可以设置一个接待员的位置，由接待员负责代为挂号、划价、缴费、取药等。这个接待员就是Facade模式的体现，病人只接触接待员，由接待员负责与医院的各个部门打交道，请见下面的对象图。<BR><BR>
<TABLE width="90%" align=center border=0>
<TBODY>
<TR>
<TD>
<DIV align=center><IMG src="http://dev.yesky.com/imagelist/05/06/631z3q347q41.jpg" border=0><BR>图6、描述经过Facade模式的<A class=bluekey href="http://www.yesky.com/key/3989/148989.html" target=_blank>改装</A>后，病人在医院里的体验。图中的方框代表医院。</DIV></TD></TR></TBODY></TABLE><BR>　　Facade模式要求一个子系统的外部与其内部的通讯必须通过一个统一的门面（Facade）对象进行。Facade模式提供一个<A class=bluekey href="http://www.yesky.com/key/4920/184920.html" target=_blank>高等</A>级的接口，使得子系统更易于使用。<BR><BR>　　使用了Facade模式之后，本章的第一个图中所描述的一个子系统的使用端对象所面对的复杂关系就可以简化为下面这个样子。 <BR><BR>
<TABLE width="90%" align=center border=0>
<TBODY>
<TR>
<TD>
<DIV align=center><IMG src="http://dev.yesky.com/imagelist/05/06/4j1k41qbnf7h.gif" border=0><BR>图7、Facade架构模式的结构图</DIV></TD></TR></TBODY></TABLE><BR>　　描述经过Facade模式的改装后，一个子系统的使用端与子系统的关系。图中的大方框代表一个子系统。<BR><BR>　　就如同医院的接待员一样，Facade模式的门面类型将使用端与子系统的内部复杂性分隔开，使得使用端只需要与门面对象打交道，而不需要与子系统内部的很多对象打交道。<BR><BR>　　<B>Mediator架构模式</B><BR><BR>　　Mediator模式包装了一系列对象相互作用的方式，使得这些对象不必互相明显参照；从而使它们可以较松散地耦合。当这些对象中的某些对象之间的相互作用发生改变时，不会立即影响到其它的一些对象之间的相互作用；从而可以保证这些相互作用可以彼此独立地变化。 <BR><BR>　　在下面的示意图中有大量的对象，这些对象既会影响别的对象，又会被别的对象所影响，因此常常叫做同事（Colleague）对象。这些同事对象通过彼此的相互作用形成系统的行为。从图中可以看出，几乎每一个对象都需要与其它的对象发生相互作用，而这种相互作用表现为一个对象与另一个对象的直接耦合。<BR><BR>
<TABLE width="90%" align=center border=0>
<TBODY>
<TR>
<TD>
<DIV align=center><IMG src="http://dev.yesky.com/imagelist/05/06/u3xb76t989n9.gif" border=0><BR>图8、这是一个过度耦合的系统</DIV></TD></TR></TBODY></TABLE><BR>　　通过引入调停者对象（Mediator），可以将系统的网状结构变成以中介者为中心的星形结构，如下图所示。在这个星形结构中，同事对象不再通过直接的联系与另一个对象发生相互作用；相反地，它通过调停者对象与另一个对象发生相互作用。调停者对象的存在保证了对象结构上的稳定，也就是说，系统的结构不会因为新对象的引入造成大量的修改工作。 <BR><BR>
<TABLE width="90%" align=center border=0>
<TBODY>
<TR>
<TD>
<DIV align=center><IMG src="http://dev.yesky.com/imagelist/05/06/ty1x3qa272mo.gif" border=0><BR>图9、这是一个使用了Mediator架构模式之后的结构图</DIV></TD></TR></TBODY></TABLE><BR>　　比较传统的设计方法，面向对象的技术可以更好地协助设计师管理更为复杂的系统。一个好的面向对象的设计可以使对象之间增加协作性（Collaboration），减少耦合度（<A class=bluekey href="http://www.yesky.com/key/2395/162395.html" target=_blank>Coupling</A>）。一个深思熟虑的设计会把一个系统分解为一群相互协作的同事对象，然后给每一个同事对象以独特的责任，恰当的配置它们之间的协作关系，使它们可以在一起工作。<BR><BR>　　在Mediator模式中，所有的成员对象都可以协调工作，但是又不直接相互管理。这些对象都与一个处于中心地位的调停者对象发生紧密的关系，由这个调停者对象进行协调工作。这个协调者对象叫做调停者（Mediator），而调停者所协调的成员对象称做同事（Colleague）对象。<BR><BR>　　<A class=bluekey href="http://www.yesky.com/key/193/200193.html" target=_blank>在C</A>olleague对象内部发生的事件会影响到所有的同事，但是这种影响不是以直接管理的方式直接传到其它的对象上的。记住在小组的成员增加时，这样的相互作用关系是以比指数更快的方式增加的。相反，这种影响仅仅直接影响到调停者对象，而调停者对象反过来会协调其它的同事，形成整个系统的行为。<BR><BR>　　如果小组的成员增加时，调停者对象可能会面临修改，而其它的同事则可以装做不知道这个新的成员一样，不必修改。反过来，如果小组的成员之一被从系统中删除的话，调停者对象需要对此做出修改，而小组中其它的同事则不必改动。<BR><BR>　　<B>Interpreter架构模式</B><BR><BR>　　给定一个语言之后，Interpreter模式可以定义出其文法的一种表示，并同时提供一个直译器；使用端可以使用这个直译器来解释这个语言中的句子。<BR><BR>　　如果某一类型问题一再地发生的话，那么一个有意义的做法就是将此类型问题的各个实例表达为一个简单语言中的语句。这样就可以建造一个直译器，通过解释这些语句达到解决问题的目的。<BR><BR>　　例如，依照一个匹配模式搜寻字符串便是一个常见的问题。与其为每一个匹配模式建造一个特定的算法，不如建造一个一般性的算法处理各种常规表达式。当接到一个指定的常规表达式时，系统使用一个直译器解释这个常规表达式，从而对字符串进行匹配。<BR><BR>　　再比如<A class=bluekey href="http://www.yesky.com/key/1069/136069.html" target=_blank>VBA</A>（Visual <A class=bluekey href="http://www.yesky.com/key/2906/137906.html" target=_blank>Basic</A> for Applications）就不仅仅出现在微软的Office系列软件中，并且可以供第三<A class=bluekey href="http://www.yesky.com/key/2945/157945.html" target=_blank>厂家</A>出产的软件嵌入使用；Crystal <A class=bluekey href="http://www.yesky.com/key/920/165920.html" target=_blank>Reports</A>报表生成软件也包括了一个便于使用的宏语言，使用户可以执行较为复杂的命令操作。一般而言，将VBA或者其它的语言软件嵌入到自己的<A class=bluekey href="http://www.yesky.com/key/3887/173887.html" target=_blank>软件产品</A>中，可以使产品定制化（Customization）能力大大增强，但是这些宏语言引擎往往都很昂贵。<BR><BR>　　现在要介绍的Interpreter模式将描述怎样在有了一个简单的文法后，使用模式设计解释这些语句。熟悉了这个模式以后，一个没有接收过形式语言和编译器的正规训练的设计师也可以自行设计一个简单的直译器，以便为使用端提供一个简单语言，或者在系统内部使用一个简单语言描述一个合适的问题。<BR><BR>　　<B>语言、直译器和剖析器</B><BR><BR>　　Interpreter模式只描述直译器是怎样工作的，并不指明怎样在执行时创建新的直译器。虽然广义地讲直译器不一定要有一个剖析器（Parser），但是使用剖析器仍然是最常见的建立直译器的办法。一个剖析器可以从一个档或命令行读入文字性命令，并创建直译器。<BR>剖析器是一种能够识别文字并将文字按照一定规则进行分解以便进一步处理的对象。剖析器能够识别的字符串叫做语言。通常建立的小型计算机语言是与环境无关的语言，也就是遵循一定的文法的文字模式，所谓文法，便是决定怎样将语言的元素组合起来的规则的集合。剖析器便是根据组合规则将字符串分解的。<BR><BR>　　抽象地讲，语言并不一定是以字符串的形式表达的。在Interpreter模式里面所提到的语言是指任何直译器对象能够解释的任何组合。在Interpreter模式中，需要定义一个代表文法的命令类型的等级结构，也就是一系列的组合规则；每一个命令对象都有一个解释方法，代表对命令对象的解释。<BR><BR>　　命令对象的等级结构中的对象的任何排列组合都是一个语言，而剖析器的工作便是将一个文字性语言翻译成为等效的直译器语言。因此，直译器往往需要剖析器。<BR><BR>　　<B>认识Jack吗</B>？<BR><BR>　　剖析器生成器（Parser Generator），常常称为编译器的编译器（Compiler Complier）。Sun Microsystem提供一个专为Java程序员发明的强大的剖析器生成器，最初叫做Jack，后来改名为JavaCC。<BR><BR>　　要使用JavaCC，必须使用它提供的脚本语言<A class=bluekey href="http://www.yesky.com/key/4285/189285.html" target=_blank>编写</A>一个脚本，然后执行JavaCC生成Java源代码。这生成的源代码就是所需的剖析器。现在Sun已经不再负责JavaCC的研发，对JavaCC感兴趣的读者可以从http://www.experimentalstuff.com/Technologies/JavaCC得到免费的JavaCC和相关数据。<BR><BR>　　JavaCC最早命名为Jack是为了与一个早就广泛使用的剖析器生成器YACC谐音。如果读者已经熟悉了YACC，可以使用YACC达到同样的目的；只是相比之下JavaCC更容易得到Java程序员的喜爱。<BR><img src ="http://www.cnitblog.com/oliver_yin/aggbug/3460.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/oliver_yin/" target="_blank">生活像一团麻</a> 2005-10-21 09:40 <a href="http://www.cnitblog.com/oliver_yin/articles/3460.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>算术表达式的自上而下语法分析及其实现 [转]</title><link>http://www.cnitblog.com/oliver_yin/articles/2212.html</link><dc:creator>生活像一团麻</dc:creator><author>生活像一团麻</author><pubDate>Sat, 20 Aug 2005 05:10:00 GMT</pubDate><guid>http://www.cnitblog.com/oliver_yin/articles/2212.html</guid><wfw:comment>http://www.cnitblog.com/oliver_yin/comments/2212.html</wfw:comment><comments>http://www.cnitblog.com/oliver_yin/articles/2212.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/oliver_yin/comments/commentRss/2212.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/oliver_yin/services/trackbacks/2212.html</trackback:ping><description><![CDATA[算术表达式的自上而下语法分析及其实现<BR>&nbsp;hifrog（原作）&nbsp;<BR><BR>关键字&nbsp;算术表达式&nbsp;编译原理&nbsp;语法分析&nbsp;文法&nbsp;产生式&nbsp;终结符&nbsp;非终结符&nbsp;开始符&nbsp;<BR><BR><BR><BR>学过编译原理的同学大概都知道对一个句子进行自上而下语法分析的方法。我参考了陈火旺院士的《高级程序设计语言编译原理》，在这篇文章里我主要是站在编译原理的角度讲述一种语法分析程序的实现的方法，通过对一个典型的例子——算术表达式的分析，从而使大家了解构造一个实用的语法分析程序的方法，同时，也为广大程序员提供一种解决实际问题的思路。&nbsp;<BR><BR>本文包括以下内容：&nbsp;<BR>1．&nbsp;算术表达式的产生式；&nbsp;<BR>2．&nbsp;自上而下语法分析的算法和的产生式函数的构造；&nbsp;<BR>3．&nbsp;产生式函数的改进；&nbsp;<BR>4．&nbsp;语法分析中的出错处理；&nbsp;<BR>5．&nbsp;自上而下语法分析程序的实现。&nbsp;<BR><BR><BR>1．&nbsp;算术表达式的产生式&nbsp;<BR><BR>我在这里要实现的算术表达式要实现5种运算：加、减、乘、除和括号。比如一个简单的算术表达式的文法G1中包含以下产生式：&nbsp;<BR>G1:&nbsp;<BR>E&nbsp;-&gt;&nbsp;E+E&nbsp;|&nbsp;E-E&nbsp;|&nbsp;E*E&nbsp;|&nbsp;E/E&nbsp;|&nbsp;(E)&nbsp;|&nbsp;i&nbsp;<BR>为了明确运算符的优先权（括号的优先权高于乘除法，乘除法的优先权高于加减法），可改写文法G1如下：&nbsp;<BR>改写后的文法G2:&nbsp;<BR>E&nbsp;-&gt;&nbsp;T+E&nbsp;|&nbsp;T-E&nbsp;|&nbsp;T&nbsp;<BR>T&nbsp;-&gt;&nbsp;F*T&nbsp;|&nbsp;F/T&nbsp;|&nbsp;F&nbsp;<BR>F&nbsp;-&gt;&nbsp;(E)&nbsp;|&nbsp;i&nbsp;<BR>任何具有加、减、乘、除和括号运算优先权的算术表达式都可以通过上述文法中的产生式推导出来，比如对于行如i-i*(i+i)的算术表达式，有如下推导过程(其中i是数字或变量标示符，推导需要从开始符E开始推导，以下是最左推导)：&nbsp;<BR><BR>E=&gt;&nbsp;T-E&nbsp;=&gt;&nbsp;F-E&nbsp;=&gt;&nbsp;i-E&nbsp;=&gt;&nbsp;i-T&nbsp;=&gt;&nbsp;i-F*T&nbsp;=&gt;&nbsp;i-i*T&nbsp;=&gt;&nbsp;i-i*F&nbsp;=&gt;&nbsp;i-i*(E)&nbsp;=&gt;&nbsp;i-i*(T+E)&nbsp;=&gt;i-i*(F+E)&nbsp;=&gt;&nbsp;i-i*(i+E)&nbsp;=&gt;&nbsp;i-i*(i+T)&nbsp;=&gt;&nbsp;i-i*(i+F)&nbsp;=&gt;&nbsp;i-i*(i+i)&nbsp;<BR><BR>在本文中，我们就使用文法G2中的产生式构造语法分析程序。&nbsp;<BR><BR>2．自上而下语法分析的算法和的产生式函数的构造&nbsp;<BR><BR>我们可以把一个对句子从开始符E到终结符的推导过程转化为一棵语法树，根节点（即开始符）在上、叶节点（即终结符）在下，自上而下的语法分析就是对这样一棵语法树“自上而下”地遍历过程。即，每次遍历从根节点（开始符）开始，通过各个中间节点（除开始符外非终结符）到达叶节点（终结符）。如果把每一个产生式做成一个函数，那么我们可以方便地通过对这些函数的递归调用和回溯来实现对语法树的遍历。那么对于文法G2中的3个产生式，我们需要3个函数：&nbsp;<BR>void&nbsp;E_AddSub();&nbsp;//对应于非终结符E的产生式&nbsp;<BR>void&nbsp;T_MulDiv();&nbsp;//对应于非终结符T的产生式&nbsp;<BR>void&nbsp;F_Number();&nbsp;//对应于非终结符F的产生式&nbsp;<BR><BR>我们通过对输入字符流的分析来实现自上而下的语法分析。在语法分析的过程中，我们需要一个输入字符缓冲区，用来存放输入的算术表达式字符串，需要一个字符指示器来指示当前正在分析的字符，还需要一个出错处理模块。在算法设计实现中，我们用到了3个全局成员：ch、advance和error，它们的含义如下：&nbsp;<BR><BR>ch&nbsp;当前指示器所指的字符&nbsp;<BR>advance()&nbsp;使指示器指向输入字符缓冲器中的下一个字符的函数&nbsp;<BR>error()&nbsp;出错处理程序函数&nbsp;<BR><BR>由此可以构造自上而下语法分析算法，首先分析产生式E&nbsp;-&gt;&nbsp;T+E&nbsp;|&nbsp;T-E&nbsp;|&nbsp;T，不妨先把它分解成以下3个产生式：&nbsp;<BR>E&nbsp;-&gt;&nbsp;T+E&nbsp;<BR>E&nbsp;-&gt;&nbsp;T-E&nbsp;<BR>E&nbsp;-&gt;&nbsp;T&nbsp;<BR>下面首先写出E&nbsp;-&gt;&nbsp;T+E个语法分析函数：&nbsp;<BR><BR>//清单1：产生式E&nbsp;-&gt;&nbsp;T+E的语法分析函数&nbsp;<BR>void&nbsp;E_AddSub()&nbsp;<BR>{&nbsp;<BR>T_MulDiv();&nbsp;//调用非终结符T的产生式函数分析T&nbsp;<BR>If(ch==’+’)&nbsp;//如果当前字符是’+’，&nbsp;<BR>{&nbsp;<BR>advance();&nbsp;//则取下一个字符&nbsp;<BR>E_AddSub();&nbsp;//调用非终结符E的产生式函数分析E&nbsp;<BR>}&nbsp;<BR>else&nbsp;//如果不是’+’号&nbsp;<BR>error();&nbsp;//则进行出错处理&nbsp;<BR>}&nbsp;<BR><BR>看到上面函数中的算法，你大概已经可以想到产生式E&nbsp;-&gt;&nbsp;T-E&nbsp;的自上而下语法分析算法了，即把If(ch==’+’)&nbsp;一句中的’+’改成’-‘号即可。下面是产生式E&nbsp;-&gt;&nbsp;T&nbsp;的算法，很简单：&nbsp;<BR><BR>//清单2：产生式E&nbsp;-&gt;&nbsp;T的语法分析函数&nbsp;<BR>void&nbsp;E_AddSub()&nbsp;<BR>{&nbsp;<BR>T_MulDiv();&nbsp;///调用非终结符T的产生式函数分析T&nbsp;<BR>}&nbsp;<BR><BR>大家可以看到，为每一个产生式写一个分析函数，通过它们之间的相互调用，即可实现对语法树的遍历，从而实现对句子的推导。由于E&nbsp;-&gt;&nbsp;T+E、E&nbsp;-&gt;&nbsp;T-E、E&nbsp;-&gt;&nbsp;T三个产生式可以合并成E&nbsp;-&gt;&nbsp;T+E&nbsp;|&nbsp;T-E&nbsp;|&nbsp;T，我们也可以把对应的三个产生式的函数合并成一个函数，由于有产生式E&nbsp;-&gt;&nbsp;T&nbsp;，所以在E的产生式函数中只调用非终结符T的分析函数就可以了，即使下一个字符不是’+’或’-‘也不必做错误处理，而E&nbsp;-&gt;&nbsp;T+E&nbsp;|&nbsp;T-E的合并用一句分支语句if(ch==’+’||ch==’-‘)判断即可，这样，合并后E产生式的函数如下：&nbsp;<BR><BR>//清单3：产生式E&nbsp;-&gt;&nbsp;T+E&nbsp;|&nbsp;T-E&nbsp;|&nbsp;T的分析函数&nbsp;<BR>void&nbsp;E_AddSub()&nbsp;<BR>{&nbsp;<BR>T_MulDiv();&nbsp;//调用非终结符T的产生式函数分析T&nbsp;<BR>If(ch==’+’&nbsp;||ch==’-‘)&nbsp;//如果当前字符是’+’或’-‘，&nbsp;<BR>//如果是’+’，则用产生式E&nbsp;-&gt;&nbsp;T+E推导，&nbsp;<BR>//如果是’-‘，则用产生式E&nbsp;-&gt;&nbsp;T-E推导。&nbsp;<BR>{&nbsp;<BR>advance();&nbsp;//则取下一个字符&nbsp;<BR>E_AddSub();&nbsp;//调用非终结符E的产生式函数分析E&nbsp;<BR>}&nbsp;//此时产生式E&nbsp;-&gt;&nbsp;T+E&nbsp;|&nbsp;T-E的推导算法结束&nbsp;<BR>//如果下一个字符不是不是’+’或’-‘，&nbsp;<BR>//则本函数是根据产生式E&nbsp;-&gt;&nbsp;T来进行推导的，不必进行错误处理。&nbsp;<BR>}&nbsp;<BR><BR>同理，你也可以容易地写出产生式T&nbsp;-&gt;&nbsp;F*T&nbsp;|&nbsp;F/T&nbsp;|&nbsp;F和F&nbsp;-&gt;&nbsp;(E)&nbsp;|&nbsp;i的自上而下语法分析函数：&nbsp;<BR><BR>//清单4：产生式T&nbsp;-&gt;&nbsp;F*T&nbsp;|&nbsp;F/T&nbsp;|&nbsp;F的分析函数&nbsp;<BR>void&nbsp;T_MulDiv()&nbsp;<BR>{&nbsp;<BR>F_Number();&nbsp;//调用非终结符F的产生式函数分析F&nbsp;<BR>If(ch==’*’&nbsp;||ch==’/‘)&nbsp;//如果当前字符是’*’或’\‘，&nbsp;<BR>//如果是’*’，则用产生式T&nbsp;-&gt;&nbsp;F*T推导，&nbsp;<BR>//如果是’\‘，则用产生式T&nbsp;-&gt;&nbsp;F/T推导。&nbsp;<BR>{&nbsp;<BR>advance();&nbsp;//则取下一个字符&nbsp;<BR>T_MulDiv();&nbsp;//调用非终结符T的产生式函数分析T&nbsp;<BR>}&nbsp;//此时产生式T&nbsp;-&gt;&nbsp;F*T&nbsp;|&nbsp;F/T&nbsp;的推导算法结束&nbsp;<BR>//如果下一个字符不是不是’*’或’/‘，&nbsp;<BR>//则本函数是根据产生式T&nbsp;-&gt;&nbsp;F&nbsp;来进行推导的，不必进行错误处理。&nbsp;<BR>}&nbsp;<BR><BR>//清单5：产生式F&nbsp;-&gt;&nbsp;(E)&nbsp;|&nbsp;i的分析函数&nbsp;<BR>void&nbsp;F_Number()&nbsp;<BR>{&nbsp;<BR>if(ch==’(‘)&nbsp;//如果当前的指示器指示的字符是’(‘&nbsp;<BR>{&nbsp;//则根据产生式F&nbsp;-&gt;&nbsp;(E)推导&nbsp;<BR>advance();&nbsp;//跳过’(‘，指示器指向下一个字符&nbsp;<BR>E_AddSub();&nbsp;//调用非终结符E的产生式函数分析E&nbsp;<BR>If(ch!=’)’)&nbsp;//判断下一个字符是否是’)’，&nbsp;<BR>//必须保证有右括号与左括号配对使用&nbsp;<BR>error();&nbsp;//如果出错，则进行出错处理。&nbsp;<BR>Advance();&nbsp;//如果有’)’，语法正确，跳过’)’&nbsp;<BR><BR>return;&nbsp;//返回&nbsp;<BR>}&nbsp;<BR>if(ch是数字)&nbsp;//如果当前指示器指示的字符是数字&nbsp;<BR>{&nbsp;//则根据产生式F&nbsp;-&gt;&nbsp;i推导&nbsp;<BR>advance();&nbsp;//跳过该数字，指示器指向下一个字符&nbsp;<BR>}&nbsp;//语法正确，完成F&nbsp;-&gt;&nbsp;i推导&nbsp;<BR>else&nbsp;//如果当前指示器指示的字符不是数字也不是’(‘&nbsp;<BR>error();&nbsp;//则出错，转向出错处理程序&nbsp;<BR><BR>return;&nbsp;//返回&nbsp;<BR>}&nbsp;<BR><BR>由于，符合语法的句子的推导是从开始符E开始的，所以在进行语法分析时，需要在主程序中这样实现：&nbsp;<BR><BR>//清单6：主程序&nbsp;<BR>int&nbsp;main()&nbsp;<BR>{&nbsp;<BR>………………………………&nbsp;<BR>//对输入字符缓冲区和字符指示器初始化&nbsp;<BR>//调用开始符E的分析函数开始自上而下语法分析：&nbsp;<BR>E_AddSub();&nbsp;<BR>//分析结束&nbsp;<BR>………………………………&nbsp;<BR>return&nbsp;0;&nbsp;<BR>}&nbsp;<BR><BR>按照此方法实现的上述函数实现了对语法树的自上到下的遍历，从而展示了自上而下语法分析的过程，然而，这些函数并没有实现具体的功能，比如执行算术表达式或计算求值的功能，在下面的几节里我会陆续考虑这些问题。&nbsp;<BR><BR><BR>3．&nbsp;产生式函数的改进&nbsp;<BR><BR>前两节我们已经实现了自上而下语法分析算法和产生式函数的构造，在这一节，我着重阐述对产生式函数的运行效率和占用空间进行优化的方法。&nbsp;<BR>首先考察一下产生式E&nbsp;-&gt;&nbsp;T+E&nbsp;|&nbsp;T-E&nbsp;|&nbsp;T的分析函数：&nbsp;<BR><BR>void&nbsp;E_AddSub()&nbsp;<BR>{&nbsp;<BR>T_MulDiv();&nbsp;//调用非终结符T的产生式函数分析T&nbsp;<BR>If(ch==’+’&nbsp;||ch==’-‘)&nbsp;//如果当前字符是’+’或’-‘，&nbsp;<BR>//如果是’+’，则用产生式E&nbsp;-&gt;&nbsp;T+E推导，&nbsp;<BR>//如果是’-‘，则用产生式E&nbsp;-&gt;&nbsp;T-E推导。&nbsp;<BR>{&nbsp;<BR>advance();&nbsp;//则取下一个字符&nbsp;<BR>E_AddSub();&nbsp;//调用非终结符E的产生式函数分析E&nbsp;<BR>}&nbsp;//此时产生式E&nbsp;-&gt;&nbsp;T+E&nbsp;|&nbsp;T-E的推导算法结束&nbsp;<BR>//如果下一个字符不是不是’+’或’-‘，&nbsp;<BR>//则本函数是根据产生式E&nbsp;-&gt;&nbsp;T来进行推导的，不必进行错误处理。&nbsp;<BR>}&nbsp;<BR><BR>大家看到，在if语句块中有一句E_AddSub();，这意味着在E_AddSub()函数中的语句可以调用自己本身，即函数中存在递归。然而在这里，我们完全可以节省一部分因递归占用的程序堆栈空间，在这同时也减少了因对程序堆栈进行push/pop操作耗费的时间。在这里我要用到的一种改进的方法是用循环代替if通过消除自身递归来削弱递归深度的方法，比如改进后的E_AddSub()函数如下：&nbsp;<BR><BR>//清单7：改进后的产生式E&nbsp;-&gt;&nbsp;T+E&nbsp;|&nbsp;T-E&nbsp;|&nbsp;T的分析函数&nbsp;<BR>void&nbsp;E_AddSub()&nbsp;<BR>{&nbsp;<BR>T_MulDiv();&nbsp;//调用非终结符T的产生式函数分析T&nbsp;<BR>while(ch==’+’&nbsp;||ch==’-‘)&nbsp;//如果当前字符是’+’或’-‘，&nbsp;<BR>//如果是’+’，则用产生式E&nbsp;-&gt;&nbsp;T+E推导，&nbsp;<BR>//如果是’-‘，则用产生式E&nbsp;-&gt;&nbsp;T-E推导。&nbsp;<BR>{&nbsp;<BR>advance();&nbsp;//则取下一个字符&nbsp;<BR>T_MulDiv();&nbsp;//调用非终结符E的产生式函数分析T&nbsp;<BR>}&nbsp;//若当前指示器指示的符号是’+’或’-‘，则继续while循环&nbsp;<BR>//如果下一个字符不是不是’+’或’-‘，&nbsp;<BR>//则本函数是根据产生式E&nbsp;-&gt;&nbsp;T来进行推导的，不必进行错误处理。&nbsp;<BR>}&nbsp;<BR><BR>我们可以看到在清单7中，在把if变成while的同时，还把while语句块中的E_AddSub()变为T_MulDiv()，这意味着把产生式（其中op为’+’或’-‘）：&nbsp;<BR>E&nbsp;-&gt;&nbsp;T&nbsp;op&nbsp;E&nbsp;<BR>转换为：&nbsp;<BR>E&nbsp;-&gt;&nbsp;T&nbsp;op&nbsp;T&nbsp;op&nbsp;T&nbsp;op&nbsp;T&nbsp;op&nbsp;………………&nbsp;op&nbsp;T&nbsp;<BR>两者是等价的。显然对于型如i&nbsp;op&nbsp;i&nbsp;op&nbsp;i&nbsp;op&nbsp;i这样的句子，后者有更快的推导速度，而前者需要多进行3次递归堆栈。因此，改进后的产生式函数更高效。同样，T_MulDiv()也可以通过这种方法改进。&nbsp;<BR><BR>然而，这种方法并不能完全消除递归，只是减少了递归的调用次数，削减了递归层次。每当分析到括号运算符时，因为F_Number()会被T_MulDiv()调用，T_MulDiv()会被E_AddSub()调用，所以会产生一个对开始符E的产生式函数的递归：&nbsp;<BR><BR>void&nbsp;F_Number()&nbsp;<BR>{&nbsp;<BR>if(ch==’(‘)&nbsp;//如果当前的指示器指示的字符是’(‘&nbsp;<BR>{&nbsp;//则根据产生式F&nbsp;-&gt;&nbsp;(E)推导&nbsp;<BR>advance();&nbsp;//跳过’(‘，指示器指向下一个字符&nbsp;<BR>E_AddSub();&nbsp;//调用非终结符E的产生式函数分析E&nbsp;<BR>If(ch!=’)’)&nbsp;<BR>error();&nbsp;//如果出错，则进行出错处理。&nbsp;<BR>Advance();&nbsp;//如果有’)’，语法正确，跳过’)’&nbsp;<BR>return;&nbsp;//返回&nbsp;<BR>}&nbsp;<BR>………………………………&nbsp;<BR><BR>return;&nbsp;//返回&nbsp;<BR>}&nbsp;<BR><BR>按照这种方法只有在分析括号运算符内的算术表达式时才增加一个递归层次，可以有效地提高语法分析的执行效率。&nbsp;<BR><BR>4．&nbsp;语法分析中的出错处理&nbsp;<BR><BR>进行出错处理也许是件很麻烦的事。想象一个设计良好的编译调试环境，比如Visual&nbsp;Studio，我们在用它开发编译程序时，不光可以知道哪一句错了，而且可以获得出错的原因。我们现在要做的是对一句算术表达式（如果出错的话）找出出错的原因，并把错误原因和相关信息提示给用户。这该怎么办呢？&nbsp;<BR><BR>《编译原理》的课本上大都讲过通过考察FIRST集和FOLLOW集构造语法分析表的方法来处理错误，这样可以把错误的处理方法填充到分析表中。然而在这里，既然我们已经构造好了文法产生式函数，简单地，这样，我们仅仅通过在函数中出现error()函数调用的地方进行分析一下即可逐步实现对错误的处理。&nbsp;<BR><BR>考察清单5中产生式F&nbsp;-&gt;&nbsp;(E)&nbsp;|&nbsp;i的函数：&nbsp;<BR><BR>void&nbsp;F_Number()&nbsp;<BR>{&nbsp;<BR>if(ch==’(‘)&nbsp;<BR>{&nbsp;<BR>advance();&nbsp;<BR>E_AddSub();&nbsp;<BR>If(ch!=’)’)&nbsp;//如果当前指示器指示的字符不是’)’&nbsp;，则产生错误，&nbsp;<BR>error();&nbsp;//错误号：1&nbsp;<BR>advance();&nbsp;<BR><BR>return;&nbsp;<BR>}&nbsp;<BR>if(ch是数字)&nbsp;<BR>{&nbsp;<BR>advance();&nbsp;<BR>}&nbsp;<BR>else&nbsp;//如果当前指示器指示的字符不是数字，则产生错误&nbsp;<BR>error();&nbsp;//错误号：2&nbsp;<BR><BR>return;&nbsp;<BR>}&nbsp;<BR><BR>在上述代码中可以看到有两处可能产生错误的地方，其中1号错误产生的原因很容易看出来，是“缺少与左括号匹配的右括号”。2号错误产生的原因是“当前指示器指示的字符不是数字”，即在算术表达式中该出现数字的地方没有出现数字，比如当指示器指到非法表达式“23+#b”中的“#”、“1-（+”中的“+”和“2+*3”中的“*”时都属于这种情况。2号错误还有两种情况，一种是当括号内无表达式（即括号内表达式为空时），比如“3+（）”，这样需要判断当前指示的字符是否为’）’；第二种是表达式不完整（即表达式提前结束），比如“4*(2+3)+”，这需要判断当前指示的字符是否为’\0’（字符串结束符）。&nbsp;<BR><BR>再考察一下清单6中的主函数：&nbsp;<BR><BR>int&nbsp;main()&nbsp;<BR>{&nbsp;<BR>………………………………&nbsp;<BR>//对输入字符缓冲区和字符指示器初始化&nbsp;<BR>//调用开始符E的分析函数开始自上而下语法分析：&nbsp;<BR>E_AddSub();&nbsp;<BR>//分析结束&nbsp;<BR>………………………………&nbsp;<BR>return&nbsp;0;&nbsp;<BR>}&nbsp;<BR><BR>然而，根据我们的设计，当E_AddSub()&nbsp;函数返回（分析结束）时，在指示器所指字符的后面有可能还有未被分析的字符，凡此时存在未被指示器扫描过的字符的表达式均为错误的表达式。比如当指示器指到非法表达式“2*（3+4）5”中的“5”、“2+3（4+5）”中的“（”和“23a”中的“a”时都属于这种错误情况。&nbsp;<BR><BR>5．&nbsp;自上而下语法分析程序的实现&nbsp;<BR><BR>经过上面4步精心的准备，最令人激动的时刻到了。一般《编译原理》课本上的代码大都是无法在机器上运行的伪代码，在这里，你将要看到的是一个实用的可以检查错误的可以执行求值的基于自上而下语法分析算法的计算算术表达式的程序。&nbsp;<BR><BR>不失一般性，我们规定算术表达式只可以进行整数的四则运算（含括号），这样我们需要扩充下面3个函数：&nbsp;<BR>int&nbsp;E_AddSub();&nbsp;//对应于非终结符E的产生式&nbsp;<BR>int&nbsp;T_MulDiv();&nbsp;//对应于非终结符T的产生式&nbsp;<BR>int&nbsp;F_Number();&nbsp;//对应于非终结符F的产生式&nbsp;<BR>大家看到，上面的函数有返回值int，我们需要让这3个函数返回计算出的结果的值。为了计算出每个函数中子表达式的值，在E_AddSub()和T_MulDiv()函数中，我用一个变量rtn来存储运算符左部的值，用opr2来存储运算符右部的值，根据运算符进行相应的运算。&nbsp;<BR><BR>为了保存输入的算术表达式，我用全局静态字符数组expr来表示输入字符缓冲区，用pos来表示字符指示器的值，这样，指示器取下一个字符的advance()操作可以用pos++来代替，而指示器所指示的字符可以就是expr[pos]。&nbsp;<BR><BR>为了表示错误，我用宏定义了6种错误的错误代码，而且定义了对应的6条错误信息的字符串。同时把error()函数改造为：&nbsp;<BR>void&nbsp;Error(int&nbsp;ErrCode);&nbsp;<BR>这样通过传递错误代码可以使程序对错误进行相应的反应，包括指示错误位置、显示错误信息、发出提示音等。此外，我声明了出错跳转缓冲区静态变量errjb，errjb是一个std::jmp_buf类型的结构，可以通过setjmp()宏把当前程序的运行状态记录到errjb中，错误返回时，可以通过longjmp()函数;直接跳转到main()主程序setjmp()被调用的位置，而不是出错的函数体中。&nbsp;<BR><BR>这样，一个功能齐全的算术表达式分析执行器构造完毕，注意，这样构造的程序不能识别一元运算符，比如输入“-1+1”会报错。&nbsp;<BR><BR>下面是运行结果片段：&nbsp;<BR><BR>1+(&nbsp;<BR>^&nbsp;语法错误&nbsp;！！！&nbsp;表达式非法结束或表达式不完整！&nbsp;<BR>请重新输入!&nbsp;<BR>请输入一个算术表达式（输入“Q”或“q”退出）：&nbsp;<BR>2-()&nbsp;<BR>^&nbsp;语法错误&nbsp;！！！&nbsp;括号内无表达式或表达式不完整！&nbsp;<BR>请重新输入!&nbsp;<BR>请输入一个算术表达式（输入“Q”或“q”退出）：&nbsp;<BR>2+(3+&nbsp;<BR>^&nbsp;语法错误&nbsp;！！！&nbsp;表达式非法结束或表达式不完整！&nbsp;<BR>请重新输入!&nbsp;<BR>请输入一个算术表达式（输入“Q”或“q”退出）：&nbsp;<BR>2+(3*9)+&nbsp;<BR>^&nbsp;语法错误&nbsp;！！！&nbsp;表达式非法结束或表达式不完整！&nbsp;<BR>请重新输入!&nbsp;<BR>请输入一个算术表达式（输入“Q”或“q”退出）：&nbsp;<BR>2*(2+4)4&nbsp;<BR>^&nbsp;语法错误&nbsp;！！！&nbsp;右括号后连接非法字符！&nbsp;<BR>请重新输入!&nbsp;<BR><BR><BR>程序清单如下：&nbsp;<BR><BR>/****算术表达式的分析和计算，文件名：Exp_c.cpp，代码/注释：hifrog****&nbsp;<BR>*****&nbsp;在VC6和Dev-C下调试通过&nbsp;****/&nbsp;<BR>#include&nbsp;<BR>#include&nbsp;<BR>#include&nbsp;<BR>#include&nbsp;<BR>#include&nbsp;<BR><BR>#define&nbsp;EXP_LEN&nbsp;100&nbsp;//定义输入字符缓冲区的长度&nbsp;<BR><BR>/*------------出错代码的宏定义--------------*/&nbsp;<BR>#define&nbsp;INVALID_CHAR_TAIL&nbsp;0&nbsp;//表达式后跟有非法字符&nbsp;<BR>#define&nbsp;CHAR_AFTER_RIGHT&nbsp;1&nbsp;//右括号后连接非法字符&nbsp;<BR>#define&nbsp;LEFT_AFTER_NUM&nbsp;2&nbsp;//数字后非法直接连接左括号&nbsp;<BR>#define&nbsp;INVALID_CHAR_IN&nbsp;3&nbsp;//表达式中含有非法字符&nbsp;<BR>#define&nbsp;NO_RIGHT&nbsp;4&nbsp;//缺少右括号&nbsp;<BR>#define&nbsp;EMPTY_BRACKET&nbsp;5&nbsp;//括号内无表达式&nbsp;<BR>#define&nbsp;UNEXPECTED_END&nbsp;6&nbsp;//预期外的算术表达式结束&nbsp;<BR><BR>using&nbsp;namespace&nbsp;std;&nbsp;<BR><BR>const&nbsp;string&nbsp;ErrCodeStr[]=&nbsp;//表达式出错信息&nbsp;<BR>{&nbsp;<BR>"表达式后跟有非法字符！",&nbsp;<BR>"右括号后连接非法字符！",&nbsp;<BR>"数字后非法直接连接左括号！",&nbsp;<BR>"表达式中含有非法字符！",&nbsp;<BR>"缺少右括号！",&nbsp;<BR>"括号内无表达式或表达式不完整！",&nbsp;<BR>"表达式非法结束或表达式不完整！"&nbsp;<BR>};&nbsp;<BR><BR>static&nbsp;char&nbsp;expr[EXP_LEN];&nbsp;//算术表达式输入字符缓冲区&nbsp;<BR>static&nbsp;int&nbsp;pos;&nbsp;//字符指示器标志：用来保存正在分析的字符的位置&nbsp;<BR>static&nbsp;jmp_buf&nbsp;errjb;&nbsp;//出错跳转缓冲器&nbsp;<BR><BR>//********以下是函数声明*********&nbsp;<BR>//产生式“E&nbsp;-&gt;&nbsp;T+E&nbsp;|&nbsp;T-E&nbsp;|&nbsp;T”的函数，用来分析加减算术表达式。&nbsp;<BR>int&nbsp;E_AddSub();&nbsp;<BR>//产生式“T&nbsp;-&gt;&nbsp;F*T&nbsp;|&nbsp;F/T&nbsp;|&nbsp;F”的函数，用来分析乘除算术表达式。&nbsp;<BR>int&nbsp;T_MulDiv();&nbsp;<BR>//产生式“F&nbsp;-&gt;&nbsp;i&nbsp;|&nbsp;(E)”的函数，用来分析数字和括号内的表达式。&nbsp;<BR>int&nbsp;F_Number();&nbsp;<BR>//出错处理函数，可以指出错误位置，出错信息。&nbsp;<BR>void&nbsp;Error(int&nbsp;ErrCode);&nbsp;<BR><BR>int&nbsp;main()&nbsp;<BR>{&nbsp;<BR>int&nbsp;ans;&nbsp;//保存算术表达式的计算结果&nbsp;<BR>bool&nbsp;quit=false;&nbsp;//是否退出计算&nbsp;<BR><BR>do&nbsp;<BR>{&nbsp;<BR>//在此设定一个跳转目标，如果本程序的其他函数调用longjmp，&nbsp;<BR>//执行指令就跳转到这里，从这里继续执行。&nbsp;<BR>if(setjmp(errjb)==0)&nbsp;//如果没有错误&nbsp;<BR>{&nbsp;<BR>pos=0;&nbsp;//初始化字符指示器为0，即指向输入字符串的第一个字符。&nbsp;<BR><BR>cout&lt;&lt;"请输入一个算术表达式（输入“Q”或“q”退出）："&lt;&nbsp;cin&gt;&gt;expr;&nbsp;//输入表达式，填充表达式字符缓冲区。&nbsp;<BR><BR>if(expr[0]=='q'||expr[0]=='Q')&nbsp;<BR>//检查第一个字符，是否退出？&nbsp;<BR>quit=true;&nbsp;<BR><BR>else&nbsp;<BR>{&nbsp;<BR>//调用推导式“E&nbsp;-&gt;&nbsp;T+E&nbsp;|&nbsp;T-E&nbsp;|&nbsp;T”的函数，&nbsp;<BR>//从起始符号“E”开始推导。&nbsp;<BR>ans=E_AddSub();&nbsp;<BR><BR>//此时，程序认为对表达式的语法分析已经完毕，下面判断出错的原因：&nbsp;<BR><BR>//如果表达式中的某个右括号后直接跟着数字或其他字符，&nbsp;<BR>//则报错，因为数字i不属于FOLLOW(）)集。&nbsp;<BR>if(expr[pos-1]==')'&amp;&amp;expr[pos]!='\0')&nbsp;<BR>Error(CHAR_AFTER_RIGHT);&nbsp;<BR><BR>//如果表达式中的某个数字或右括号后直接跟着左括号，&nbsp;<BR>//则报错，因为左括号不属于FOLLOW(E)集。&nbsp;<BR>if(expr[pos]=='(')&nbsp;<BR>Error(LEFT_AFTER_NUM);&nbsp;<BR><BR>//如果结尾有其他非法字符&nbsp;<BR>if(expr[pos]!='\0')&nbsp;<BR>Error(INVALID_CHAR_TAIL);&nbsp;<BR><BR>cout&lt;&lt;"计算得出表达式的值为："&lt;&nbsp;}&nbsp;<BR>}&nbsp;<BR>else&nbsp;<BR>{&nbsp;<BR>//setjmp(errjb)!=0的情况：&nbsp;<BR>cout&lt;&lt;"请重新输入!"&lt;&nbsp;}&nbsp;<BR>}&nbsp;<BR>while(!quit);&nbsp;<BR><BR>return&nbsp;0;&nbsp;<BR>}&nbsp;<BR><BR>//产生式“E&nbsp;-&gt;&nbsp;T+E&nbsp;|&nbsp;T-E&nbsp;|&nbsp;T”的函数，用来分析加减算术表达式。&nbsp;<BR>//返回计算结果&nbsp;<BR>int&nbsp;E_AddSub()&nbsp;<BR>{&nbsp;<BR>int&nbsp;rtn=T_MulDiv();&nbsp;//计算加减算术表达式的左元&nbsp;<BR><BR>while(expr[pos]=='+'||expr[pos]=='-')&nbsp;<BR>{&nbsp;<BR>int&nbsp;op=expr[pos++];&nbsp;//取字符缓冲区中当前位置的符号到op&nbsp;<BR>int&nbsp;opr2=T_MulDiv();//计算加减算术表达式的右元&nbsp;<BR><BR>//计算求值&nbsp;<BR>if(op=='+')&nbsp;//如果是"+"号&nbsp;<BR>rtn+=opr2;&nbsp;//则用加法计算&nbsp;<BR>else&nbsp;//否则（是"-"号）&nbsp;<BR>rtn-=opr2;&nbsp;//用减法计算&nbsp;<BR>}&nbsp;<BR>return&nbsp;rtn;&nbsp;<BR>}&nbsp;<BR><BR>//推导式“T&nbsp;-&gt;&nbsp;F*T&nbsp;|&nbsp;F/T&nbsp;|&nbsp;F”的函数，用来分析乘除算术表达式。&nbsp;<BR>//返回计算结果&nbsp;<BR>int&nbsp;T_MulDiv()&nbsp;<BR>{&nbsp;<BR>int&nbsp;rtn=F_Number();&nbsp;//计算乘除算术表达式的左元&nbsp;<BR><BR>while(expr[pos]=='*'||expr[pos]=='/')&nbsp;<BR>{&nbsp;<BR>int&nbsp;op=expr[pos++];&nbsp;//取字符缓冲区中当前位置的符号到op&nbsp;<BR>int&nbsp;opr2=F_Number();//计算乘除算术表达式的右元&nbsp;<BR><BR>//计算求值&nbsp;<BR>if(op=='*')&nbsp;//如果是"*"号&nbsp;<BR>rtn*=opr2;&nbsp;//则用乘法计算&nbsp;<BR>else&nbsp;//否则（是"\"号）&nbsp;<BR>rtn/=opr2;&nbsp;//用除法计算&nbsp;<BR>}&nbsp;<BR>return&nbsp;rtn;&nbsp;<BR>}&nbsp;<BR><BR>//产生式“F&nbsp;-&gt;&nbsp;i&nbsp;|&nbsp;(E)”的函数，用来分析数字和括号内的表达式。&nbsp;<BR>int&nbsp;F_Number()&nbsp;<BR>{&nbsp;<BR>int&nbsp;rtn;&nbsp;//声明存储返回值的变量&nbsp;<BR><BR>//用产生式F-&gt;(E)推导：&nbsp;<BR>if(expr[pos]=='(')&nbsp;//如果字符缓冲区当前位置的符号是"("&nbsp;<BR>{&nbsp;<BR>pos++;&nbsp;//则指示器加一指向下一个符号&nbsp;<BR>rtn=E_AddSub();&nbsp;//调用产生式“E&nbsp;-&gt;&nbsp;T+E&nbsp;|&nbsp;T-E&nbsp;|&nbsp;T”的分析函数&nbsp;<BR><BR>if(expr[pos++]!=')')//如果没有与"("匹配的"<IMG onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0>"&nbsp;<BR>Error(NO_RIGHT);//则产生错误&nbsp;<BR><BR>return&nbsp;rtn;&nbsp;<BR>}&nbsp;<BR><BR><BR>if(isdigit(expr[pos]))//如果字符缓冲区中当前位置的字符为数字&nbsp;<BR>{&nbsp;<BR>//则用产生式F&nbsp;-&gt;&nbsp;i推导&nbsp;<BR>//把字符缓冲区中当前位置的字符串转换为整数&nbsp;<BR>rtn=atoi(expr+pos);&nbsp;<BR>//改变指示器的值，跳过字符缓冲区的数字部分，找到下一个输入字符。&nbsp;<BR>while(isdigit(expr[pos]))&nbsp;<BR>pos++;&nbsp;<BR>}&nbsp;<BR>else&nbsp;//如果不是数字则产生相应的错误&nbsp;<BR>{&nbsp;<BR>if(expr[pos]==')')&nbsp;//如果发现一个"<IMG onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0>"&nbsp;<BR>Error(EMPTY_BRACKET);&nbsp;//则是括号是空的，即括号内无算术表达式。&nbsp;<BR>else&nbsp;if(expr[pos]=='\0')&nbsp;//如果此时输入串结束&nbsp;<BR>Error(UNEXPECTED_END);&nbsp;//则算术表达式非法结束&nbsp;<BR>else&nbsp;<BR>Error(INVALID_CHAR_IN);&nbsp;//否则输入字符串中含有非法字符&nbsp;<BR>}&nbsp;<BR><BR>return&nbsp;rtn;&nbsp;<BR>}&nbsp;<BR><BR>//出错处理函数，输入错误代码，可以指出错误位置，出错信息。&nbsp;<BR>void&nbsp;Error(int&nbsp;ErrCode)&nbsp;<BR>{&nbsp;<BR>cout&lt;&lt;'\r';&nbsp;//换行&nbsp;<BR>while(pos--)&nbsp;<BR>cout&lt;&lt;'&nbsp;';&nbsp;//打印空格，把指示错误的"^"移到输入字符串的出错位置&nbsp;<BR>cout&lt;&lt;"^&nbsp;语法错误&nbsp;！！！&nbsp;"&nbsp;<BR>&lt;&nbsp;&lt;&nbsp;<BR>longjmp(errjb,1);&nbsp;//跳转到main()函数中的setjmp调用处，并设置setjmp(errjb)的返回值为1&nbsp;<BR>}&nbsp;<BR><img src ="http://www.cnitblog.com/oliver_yin/aggbug/2212.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/oliver_yin/" target="_blank">生活像一团麻</a> 2005-08-20 13:10 <a href="http://www.cnitblog.com/oliver_yin/articles/2212.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>从lex&amp;yacc说到编译器(转)</title><link>http://www.cnitblog.com/oliver_yin/articles/2211.html</link><dc:creator>生活像一团麻</dc:creator><author>生活像一团麻</author><pubDate>Sat, 20 Aug 2005 05:07:00 GMT</pubDate><guid>http://www.cnitblog.com/oliver_yin/articles/2211.html</guid><wfw:comment>http://www.cnitblog.com/oliver_yin/comments/2211.html</wfw:comment><comments>http://www.cnitblog.com/oliver_yin/articles/2211.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/oliver_yin/comments/commentRss/2211.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/oliver_yin/services/trackbacks/2211.html</trackback:ping><description><![CDATA[从lex&amp;yacc说到编译器 [转]<BR><BR>作者:tangl_99&nbsp;<BR>QQ:8664220&nbsp;<BR>msn:tangl_99@hotmail.com&nbsp;<BR>email:tangl_99@sohu.com&nbsp;<BR><BR>从lex&amp;yacc说到编译器<BR><BR>一、正则表达式<BR><BR>学过编译原理的朋友肯定都接触过LEX这个小型的词法扫描工具.&nbsp;但是却很少有人真正把LEX用在自己的程序里.&nbsp;在构造专业的编译器的时候,常常需要使用到lex和yacc.&nbsp;正是因为这两个工具,使得我们编写编译器,解释器等工具的时候工作变得非常简单.不过话说回来,会使用lex和yacc的人也确实不简单.&nbsp;Lex和yacc里面牵涉到一系列的编译原理的理论知识,不是简单地看看书就能搞懂的.&nbsp;本文只是简单地介绍一下lex和yacc的使用方法.相关编译理请查看本科教材.&nbsp;<BR><BR>国内大学教材里面对于lex和yacc的介绍很少,有些根本就没有,不过在国外的编译原理教材介绍了很多.&nbsp;按照学科的分类,国内大学本科里面开的&lt;&lt;编译原理&gt;&gt;教程只是讲解编译的原理,并不讲解实践.&nbsp;而对于实践方面则是另外一门学科&lt;&lt;编译技术&gt;&gt;.&nbsp;关于编译技术的书籍在国内是少之又少.&nbsp;前不久,&nbsp;听说上海交大的计科内部出版过编译技术的教材.可惜我们这些人就无法得见了.&nbsp;还好,机械工业出版社引进了美国&nbsp;Kenneth&nbsp;C.Louden所著的经典著作&lt;&lt;编译原理及实践&gt;&gt;中,比较详细地介绍lex和yacc的使用.&nbsp;<BR><BR>Lex属于GNU内部的工具,它通常都是gcc的附带工具.&nbsp;如果你使用的Linux操作系统,那么肯定系统本身就有lex和yacc,不过yacc的名字变成了bison.&nbsp;如果你使用的Windows操作系统,那么可以到cygwin或者GNUPro里面找得到.&nbsp;网上也有windows版本lex和yacc,大家可以自己去找一找.&nbsp;<BR><BR>本文一共有两篇,一篇是介绍lex,另一篇是介绍yacc.&nbsp;Lex和yacc搭配使用,&nbsp;我们构造自己的编译器或者解释器就如同儿戏.&nbsp;所以我把本文的名字叫做黄金组合.&nbsp;<BR><BR>本文以flex(&nbsp;Fase&nbsp;Lex)为例,两讲解如何构造扫描程序.&nbsp;<BR>Flex可以通过一个输入文件,然后生成扫描器的C源代码.&nbsp;<BR><BR>其实扫描程序并不只用于编译器&nbsp;.比如编写游戏的脚本引擎的时候,我看到很多开发者都是自己写的扫描器,其算法相当落后(完全没有DFA的概念化),&nbsp;甚至很多脚本引擎开发者的词法扫描器都没有编写,而是在运行过程中寻找token(单词).&nbsp;在现代的计算机速度确实可以上小型的脚本引擎在运行中进行词法扫描,&nbsp;但是作为一个合格的程序员,&nbsp;或者说一个合格的计算机本科毕业生而来说,&nbsp;能够运用编译原理与技术实践,应该是个基本要求.&nbsp;<BR><BR>如果要说到词法分析的扫描器源代码编写,&nbsp;其实也很简单,&nbsp;会C语言的人都会写.&nbsp;可是Kenneth&nbsp;Louden在&lt;&lt;编译原理及技术&gt;里面,花了50多页,原因就是从理论角度,介绍标准的,可扩展的,高效的词法扫描器的编写.&nbsp;里面从正则表达式介绍到DFA(有穷自动机),再到NFA(非确定性有穷自动机),最后才到代码的编写.&nbsp;以自动机原理编译扫描器的方法基本上就是现在词法扫描器的标准方法,&nbsp;也就是Lex使用的方法.&nbsp;在Lex中,我们甚至不需要自己构造词法的DFA,&nbsp;我们只需要把相应的正则表达式输入,&nbsp;然后lex能够为我们自己生成DFA,然后生成源代码,可谓方便之极.&nbsp;<BR><BR>本文不讲DFA,&nbsp;lex的输入是正则表达式,&nbsp;我们直接先看看正则表达式方面知识就可以了.&nbsp;<BR><BR>1.正则表达式(regular&nbsp;expression):&nbsp;<BR><BR>对于学过编译原理的朋友来说,这一节完全可以不看.不过有些东西还是得注意一下,因为在flex中的正则表达式的使用有些具体的问题是在我们的课本上没有说明的.&nbsp;<BR>先看看例子:&nbsp;<BR>例1.1&nbsp;<BR>name&nbsp;Tangl_99&nbsp;<BR>这就是定义了name这个正则表达式,它就等于字符串Tangl_99.所以,如果你的源程序中出现了Tangl_99这个字符传,那么它就等于出现一次name正则表达式.&nbsp;<BR><BR>例1.2&nbsp;<BR>digit&nbsp;0|1|2|3|4|5|6|7|8|9&nbsp;<BR>这个表达式就是说,正则表达式digit就是0,1,2,…,9中的某一个字母.所以无论是0,2,或者是9…都是属于digit这个正则表达式的.&nbsp;<BR>“|”符号表示”或者”的意思.&nbsp;<BR>那么定义正则表达式&nbsp;name&nbsp;Tangl_99|Running,同样的,如果你的源程序中出现了Tangl_99或者Running,那么就等于出现了一次name正则表达式.&nbsp;<BR><BR>例1.3&nbsp;<BR>one&nbsp;1*&nbsp;<BR>“*”符号表示”零到无限次重复”&nbsp;<BR>那么one所表示的字符串就可以是空串(什么字符都没有),&nbsp;1,&nbsp;11,&nbsp;111,&nbsp;11111,&nbsp;11111111111,&nbsp;11111111…等等.总之,one就是由0个或者N个1所组成(N可以为任意自然数).&nbsp;<BR>与”*”相同的有个”+”符号.请看下面的例子1.4&nbsp;<BR><BR>例1.4&nbsp;<BR>realone&nbsp;1+&nbsp;<BR>“+”符号表示”1到无限次重复”&nbsp;<BR>那么realone和one不同的唯一一点就是,realone不包含空串,因为”+”表示至少一次重复,那么realone至少有一个1.所以realone所表达的字符串就是1,11,111,&nbsp;1111,&nbsp;11111…,等等.&nbsp;<BR><BR>例1.5&nbsp;<BR>digit&nbsp;[0-9]&nbsp;<BR>letter&nbsp;[a-zA-Z]&nbsp;<BR>这里的digit等于例1.2中的digit,也就是说,a|b|c就相当于[a-c].&nbsp;<BR>同理,letter也就是相当于&nbsp;a|b|c|d|e|f|…|y|z|A|B|C|D…|Z&nbsp;不过注意的一点就是,你不能把letter写成[A-z],而必须大写和小写都应该各自写出来.&nbsp;<BR><BR>例1.6&nbsp;<BR>notA&nbsp;[^A]&nbsp;<BR>“^”表示非,也就是除了这个字符以外的所有字符&nbsp;<BR>所以notA表示的就是除了A以外的所有字符.&nbsp;<BR>下面让我们来看看一些一般高级程序语言中常用的综合例子.&nbsp;<BR>digit&nbsp;[0-9]&nbsp;<BR>number&nbsp;{digit}+&nbsp;<BR>letter&nbsp;[a-zA-Z_]&nbsp;<BR>digit前面多次提起过,就是0-9的阿拉伯数字.number就是所有的数字组合,也就是整数.&nbsp;<BR>Letter前面也提起过,唯一不同的就是多了一个下划线.因为一般我们的C语言中容许有下划线来表示定义的变量名,所以我也把下划线当成英语字母来处理了.&nbsp;<BR>这里number中使用上面定义的digit正则表达式.在lex中,用{digit}就是表示正则表达式digit.&nbsp;<BR><BR>newline&nbsp;[\n]&nbsp;<BR>whitespace&nbsp;[&nbsp;\t]+&nbsp;<BR>newline就是提行的意思.这里我们使用的是\n这个符号,它和C语言中表示提行号一致.问题是大家可能要问到为什么要使用[]符号.因为在lex中,如果你使用[],那么里面表示的肯定就是单个字符号,而不会被理解成”\”和”n”两个字符.&nbsp;<BR>Whitespace就是空格符号的意思.一般的高级程序语言中有两种,一种就是简单的空格,还有一种就是\t制表符.使用了”+”符号,就表示了这些空白符号的无限组合.&nbsp;<BR><BR><BR>二、flex的使用<BR><BR>看了第一篇的关于正则表达式的说明后,下面我们就来通过它,使用flex这个词法分析工具来构造我们的编译器的词法分析器.&nbsp;<BR><BR>关于lex的教程应该是很多,这里我就简单地介绍一下,然后着重后面的lex和yacc的配合使用以及其技巧.所以,如果你不看了后还是不太明白lex或者yacc的使用,请你自己上网去查查,这方面的教程是很多的.我知道的一篇常见的就是&nbsp;<BR><BR>Yacc&nbsp;与&nbsp;Lex&nbsp;快速入门&nbsp;<BR>Lex&nbsp;与&nbsp;Yacc&nbsp;介绍&nbsp;<BR><BR>它的作者就是Ashish&nbsp;Bansal.&nbsp;<BR><BR><BR><BR>Flex就是fast&nbsp;lex的意思.而lex就是Lexical&nbsp;Analyzar的意思.flex可以在cygwin或者gnupro中找到.它是unix的一个工具,属于GNU组织产品.网上也可以找到单独可以在windows下用的版本.&nbsp;<BR><BR>我们一般把我们的词法扫描程序要扫描的一些单词(token)用正则表达式写好,然后作为lex的输入文件,输入命令flex&nbsp;xxx.l(xxx.l就是输入文件),lex经过处理后,就能得到一个名字叫lex.yy.c的C源代码.这个C源代码文件,就是我们的词法扫描程序.通常lex为我们生成的词法分析器的C源代码都是十分复杂而且庞大的,我们一般根本不会去查看里面的代码(放心好了,flex这个东西不会出错的)&nbsp;<BR><BR>下面让我们看看几个我已经使用过的几个lex输入文件.&nbsp;<BR><BR>这是一个前段时间我为GBA上的一个RPG游戏写的脚本引擎所使用的lex输入文件(部分)&nbsp;<BR><BR>例2.1&nbsp;<BR><BR>%{&nbsp;<BR><BR>/*&nbsp;need&nbsp;this&nbsp;for&nbsp;the&nbsp;call&nbsp;to&nbsp;atof()&nbsp;below&nbsp;*/&nbsp;<BR><BR>#include&nbsp;<BR><BR>#include&nbsp;<BR><BR>#include&nbsp;<BR><BR>#include&nbsp;"globals.h"&nbsp;<BR><BR>%}&nbsp;<BR><BR>digit&nbsp;[0-9]&nbsp;<BR><BR>number&nbsp;("-"|"+"<IMG onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0>?{digit}+&nbsp;<BR><BR>hexnumber&nbsp;"0x"({digit}|[a-fA-F])+&nbsp;<BR><BR>letter&nbsp;[a-zA-Z]&nbsp;<BR><BR>identifier&nbsp;({letter}|_)({number}|{letter}|_)*&nbsp;<BR><BR>newline&nbsp;[\n]&nbsp;<BR><BR>whitespace&nbsp;[&nbsp;\t]+&nbsp;<BR><BR>string&nbsp;\"[^"]*\"&nbsp;<BR><BR>comment&nbsp;"#"[^#]*"#"&nbsp;<BR><BR>%%&nbsp;<BR><BR>{string}&nbsp;{&nbsp;return&nbsp;VM_STRING;&nbsp;}&nbsp;<BR><BR>"Logo"&nbsp;{&nbsp;return&nbsp;VMIN_LOGO;&nbsp;}&nbsp;<BR><BR>"FaceIn"&nbsp;{&nbsp;return&nbsp;VMIN_FACEIN;&nbsp;}&nbsp;<BR><BR>"FaceOut"&nbsp;{&nbsp;return&nbsp;VMIN_FACEOUT;&nbsp;}&nbsp;<BR><BR>"LoadTile"&nbsp;{&nbsp;return&nbsp;VMIN_LOAD_TILE;&nbsp;}&nbsp;<BR><BR>"CreateRole"&nbsp;{&nbsp;return&nbsp;VMIN_CREATE_ROLE;&nbsp;}&nbsp;<BR><BR>"ReleaseRole"&nbsp;{&nbsp;return&nbsp;VMIN_RELEASE_ROLE;}&nbsp;<BR><BR>"CreateMap"&nbsp;{&nbsp;return&nbsp;VMIN_CREATE_MAP;&nbsp;}&nbsp;<BR><BR>"ReleaseMAP"&nbsp;{&nbsp;return&nbsp;VMIN_RELEASE_MAP;}&nbsp;<BR><BR>"ShowBitmap"&nbsp;{&nbsp;return&nbsp;VMIN_SHOWBITMAP;&nbsp;}&nbsp;<BR><BR>"CreateDialog"&nbsp;{&nbsp;return&nbsp;VMIN_CREATE_DIALOG;&nbsp;}&nbsp;<BR><BR>"ReleaseDialog"&nbsp;{&nbsp;return&nbsp;VMIN_RELEASE_DIALOG;}&nbsp;<BR><BR>"Fight"&nbsp;{&nbsp;return&nbsp;VMIN_FIGHT;&nbsp;}&nbsp;<BR><BR>"Delay"&nbsp;{&nbsp;return&nbsp;VMIN_DELAY;&nbsp;}&nbsp;<BR><BR>"PressA"&nbsp;{&nbsp;return&nbsp;VMIN_PRESS_A;&nbsp;}&nbsp;<BR><BR>"PressB"&nbsp;{&nbsp;return&nbsp;VMIN_PRESS_B;&nbsp;}&nbsp;<BR><BR>"PressR"&nbsp;{&nbsp;return&nbsp;VMIN_PRESS_R;&nbsp;}&nbsp;<BR><BR>"PressL"&nbsp;{&nbsp;return&nbsp;VMIN_PRESS_L;&nbsp;}&nbsp;<BR><BR>"PressStart"&nbsp;{&nbsp;return&nbsp;VMIN_PRESS_START;&nbsp;}&nbsp;<BR><BR>"PressSelect"&nbsp;{&nbsp;return&nbsp;VMIN_PRESS_SELECT;}&nbsp;<BR><BR>{number}&nbsp;{&nbsp;return&nbsp;VM_NUMBER;&nbsp;}&nbsp;<BR><BR>{whitespace}&nbsp;{&nbsp;/*&nbsp;skip&nbsp;whitespace&nbsp;*/&nbsp;}&nbsp;<BR><BR>{identifier}&nbsp;{&nbsp;return&nbsp;VM_ID;&nbsp;}&nbsp;<BR><BR>{newline}&nbsp;;&nbsp;<BR><BR>.&nbsp;;&nbsp;<BR><BR>%%&nbsp;<BR><BR>int&nbsp;yywrap()&nbsp;<BR><BR>{&nbsp;<BR><BR>return&nbsp;1;&nbsp;<BR><BR>}&nbsp;<BR><BR>这里的lex输入文件一共有三个部分,用%%分开.第一部分中的%{和}%中的内容就是直接放在lex输出C代码中的顶部.我们通过它可以来定义一些所需要的宏,函数和include一些头文件等等.我的这个lex输入文件中也没什么特别的东西,就是常规的C源文件的include头文件&nbsp;<BR><BR>%{&nbsp;<BR><BR>/*&nbsp;need&nbsp;this&nbsp;for&nbsp;the&nbsp;call&nbsp;to&nbsp;atof()&nbsp;below&nbsp;*/&nbsp;<BR><BR>#include&nbsp;<BR><BR>#include&nbsp;<BR><BR>#include&nbsp;<BR><BR>#include&nbsp;"globals.h"&nbsp;<BR><BR>%}&nbsp;<BR><BR>第一部分中,除了前面的%{和}%包含的部分,下面的就是正则表达式的定义.&nbsp;<BR><BR>看了第一篇的正则表达式,这样你就能够在这里派上用场了.&nbsp;<BR><BR>让我们来看看我这里定义的正则表达式:&nbsp;<BR><BR>digit&nbsp;[0-9]&nbsp;<BR><BR>number&nbsp;("-"|"+"<IMG onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0>?{digit}+&nbsp;<BR><BR>hexnumber&nbsp;"0x"({digit}|[a-fA-F])+&nbsp;<BR><BR>letter&nbsp;[a-zA-Z]&nbsp;<BR><BR>identifier&nbsp;({letter}|_)({number}|{letter}|_)*&nbsp;<BR><BR>newline&nbsp;[\n]&nbsp;<BR><BR>whitespace&nbsp;[&nbsp;\t]+&nbsp;<BR><BR>string&nbsp;\"[^"]*\"&nbsp;<BR><BR>comment&nbsp;"#"[^#]*"#"&nbsp;<BR><BR>digit就不用说了,就是0-9的阿拉伯数字定义,第一篇文章中也举了这个例子.number就是digit的1到无限次的重复,再在其前面加上”+”和”-“符号.&nbsp;<BR><BR>注意:&nbsp;<BR><BR>“a”:&nbsp;即使a是元字符,它仍是字符a&nbsp;<BR><BR>\a:&nbsp;当a是元字符时候,为字符a&nbsp;<BR><BR>a?:&nbsp;一个可选的a,也就是说可以是a,也可以没有a&nbsp;<BR><BR>a|b:&nbsp;a或b&nbsp;<BR><BR>(a):&nbsp;a本身&nbsp;<BR><BR>[abc]:&nbsp;字符a,b或c中的任一个&nbsp;<BR><BR>[a-d]:&nbsp;a,b,d或者d中的任一个&nbsp;<BR><BR>[^ab]:&nbsp;除了a或b外的任何一个字符&nbsp;<BR><BR>.:&nbsp;除了新行之外的任一个字符&nbsp;<BR><BR>{xxx}:&nbsp;名字xxx表示的正则表达式&nbsp;<BR><BR>这里需要特别说明的就是&nbsp;<BR><BR>newline&nbsp;[\n]&nbsp;<BR><BR>newline就是新行,这里我使用了[]把\n换行号括起来.因为如果我直接用\n表示的话,那么按照上面的规则,那就会看成\和n两个字符,所以我使用了[\n].有些时候newline也被写成[\n]|[\r\n].因为在文本文件中,一般换行一次,那么就是一个\n(0xA),可是在二进制文件中,换行有时候又是\r\n(0xD,0xA)一共两个字符号.&nbsp;<BR><BR>第二部分就是定义扫描到正则表达式的动作.&nbsp;<BR><BR>这些动作其实就是C代码,它们将会被镶嵌在lex输出的C文件中的yylex()函数中.&nbsp;<BR><BR>上面的例子的动作其实十分平常,就是返回一个值.&nbsp;<BR><BR>我们在外部使用这个lex为我们生成C代码的时候,只需要使用它的int&nbsp;yylex()函数.当我们使用一次yylex(),那么就会自动去扫描一个匹配的正则表达式,然后完成它相应的动作.这里的动作都是返回一值,那么yylex就会返回这个值.通常默认yylex返回0时候,表示文件扫描结束,所以你的动作中最好不要返回0,以免发生冲突.当然,动作中也可以不返回一值,那么yylex就会完成这个动作后自动扫描下一个可以被匹配的字符串,一直到扫描到文件结束.&nbsp;<BR><BR>当扫描到一个可以被匹配的字符串,那么这个时候,全局变量yytext就等于这个字符串&nbsp;<BR><BR>请大家一定记住这些正则表达式的顺序.&nbsp;<BR><BR>如果出现一个字符串,可以同时匹配多个正则表达式,那么它将会被定义在前面的正则表达式匹配.所以我一般把字符串string定义在最前面.&nbsp;<BR><BR>如果文件中的字符没有被lex输入文件中任何一个字符匹配,那么它会自动地被标准输出.所以大家一定要记住在每个正则表达式处理完毕后,一定要加上{newline}和.这两个正则表达式的动作.&nbsp;<BR><BR>好,让我们看看lex为我们输出C文件中提供一些常量&nbsp;<BR><BR>Lex&nbsp;变量&nbsp;<BR><BR>yyin&nbsp;<BR>FILE*&nbsp;类型。&nbsp;它指向&nbsp;lexer&nbsp;正在解析的当前文件。&nbsp;<BR><BR>yyout&nbsp;<BR>FILE*&nbsp;类型。&nbsp;它指向记录&nbsp;lexer&nbsp;输出的位置。&nbsp;缺省情况下，yyin&nbsp;和&nbsp;yyout&nbsp;都指向标准输入和输出。&nbsp;<BR><BR>yytext&nbsp;<BR>匹配模式的文本存储在这一变量中（char*）。&nbsp;<BR><BR>yyleng&nbsp;<BR>给出匹配模式的长度。&nbsp;<BR><BR>yylineno&nbsp;<BR>提供当前的行数信息。（lexer不一定支持。）&nbsp;<BR><BR>例2.2&nbsp;<BR><BR>这是&lt;&lt;编译原理与实践&gt;&gt;书中配套的源代码的lex输入文件.大家可以参考一下,作者为它自己定义的一个Tiny&nbsp;C编译所做的词法扫描器.&nbsp;<BR><BR>/****************************************************/&nbsp;<BR><BR>/*&nbsp;File:&nbsp;tiny.l&nbsp;*/&nbsp;<BR><BR>/*&nbsp;Lex&nbsp;specification&nbsp;for&nbsp;TINY&nbsp;*/&nbsp;<BR><BR>/*&nbsp;Compiler&nbsp;Construction:&nbsp;Principles&nbsp;and&nbsp;Practice&nbsp;*/&nbsp;<BR><BR>/*&nbsp;Kenneth&nbsp;C.&nbsp;Louden&nbsp;*/&nbsp;<BR><BR>/****************************************************/&nbsp;<BR><BR><BR><BR>%{&nbsp;<BR><BR>#include&nbsp;"globals.h"&nbsp;<BR><BR>#include&nbsp;"util.h"&nbsp;<BR><BR>#include&nbsp;"scan.h"&nbsp;<BR><BR>/*&nbsp;lexeme&nbsp;of&nbsp;identifier&nbsp;or&nbsp;reserved&nbsp;word&nbsp;*/&nbsp;<BR><BR>char&nbsp;tokenString[MAXTOKENLEN+1];&nbsp;<BR><BR>%}&nbsp;<BR><BR><BR><BR>digit&nbsp;[0-9]&nbsp;<BR><BR>number&nbsp;{digit}+&nbsp;<BR><BR>letter&nbsp;[a-zA-Z]&nbsp;<BR><BR>identifier&nbsp;{letter}+&nbsp;<BR><BR>newline&nbsp;\n&nbsp;<BR><BR>whitespace&nbsp;[&nbsp;\t]+&nbsp;<BR><BR><BR><BR>%%&nbsp;<BR><BR><BR><BR>"if"&nbsp;{return&nbsp;IF;}&nbsp;<BR><BR>"then"&nbsp;{return&nbsp;THEN;}&nbsp;<BR><BR>"else"&nbsp;{return&nbsp;ELSE;}&nbsp;<BR><BR>"end"&nbsp;{return&nbsp;END;}&nbsp;<BR><BR>"repeat"&nbsp;{return&nbsp;REPEAT;}&nbsp;<BR><BR>"until"&nbsp;{return&nbsp;UNTIL;}&nbsp;<BR><BR>"read"&nbsp;{return&nbsp;READ;}&nbsp;<BR><BR>"write"&nbsp;{return&nbsp;WRITE;}&nbsp;<BR><BR>":="&nbsp;{return&nbsp;ASSIGN;}&nbsp;<BR><BR>"="&nbsp;{return&nbsp;EQ;}&nbsp;<BR><BR>"&lt;"&nbsp;{return&nbsp;LT;}&nbsp;<BR><BR>"+"&nbsp;{return&nbsp;PLUS;}&nbsp;<BR><BR>"-"&nbsp;{return&nbsp;MINUS;}&nbsp;<BR><BR>"*"&nbsp;{return&nbsp;TIMES;}&nbsp;<BR><BR>"/"&nbsp;{return&nbsp;OVER;}&nbsp;<BR><BR>"("&nbsp;{return&nbsp;LPAREN;}&nbsp;<BR><BR>"<IMG onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0>"&nbsp;{return&nbsp;RPAREN;}&nbsp;<BR><BR>";"&nbsp;{return&nbsp;SEMI;}&nbsp;<BR><BR>{number}&nbsp;{return&nbsp;NUM;}&nbsp;<BR><BR>{identifier}&nbsp;{return&nbsp;ID;}&nbsp;<BR><BR>{newline}&nbsp;{lineno++;}&nbsp;<BR><BR>{whitespace}&nbsp;{/*&nbsp;skip&nbsp;whitespace&nbsp;*/}&nbsp;<BR><BR>"{"&nbsp;{&nbsp;char&nbsp;c;&nbsp;<BR><BR>do&nbsp;<BR><BR>{&nbsp;c&nbsp;=&nbsp;input();&nbsp;<BR><BR>if&nbsp;(c&nbsp;==&nbsp;EOF)&nbsp;break;&nbsp;<BR><BR>if&nbsp;(c&nbsp;==&nbsp;'\n')&nbsp;lineno++;&nbsp;<BR><BR>}&nbsp;while&nbsp;(c&nbsp;!=&nbsp;'}');&nbsp;<BR><BR>}&nbsp;<BR><BR>.&nbsp;{return&nbsp;ERROR;}&nbsp;<BR><BR><BR><BR>%%&nbsp;<BR><BR><BR><BR>TokenType&nbsp;getToken(void)&nbsp;<BR><BR>{&nbsp;static&nbsp;int&nbsp;firstTime&nbsp;=&nbsp;TRUE;&nbsp;<BR><BR>TokenType&nbsp;currentToken;&nbsp;<BR><BR>if&nbsp;(firstTime)&nbsp;<BR><BR>{&nbsp;firstTime&nbsp;=&nbsp;FALSE;&nbsp;<BR><BR>lineno++;&nbsp;<BR><BR>yyin&nbsp;=&nbsp;source;&nbsp;<BR><BR>yyout&nbsp;=&nbsp;listing;&nbsp;<BR><BR>}&nbsp;<BR><BR>currentToken&nbsp;=&nbsp;yylex();&nbsp;<BR><BR>strncpy(tokenString,yytext,MAXTOKENLEN);&nbsp;<BR><BR>if&nbsp;(TraceScan)&nbsp;{&nbsp;<BR><BR>fprintf(listing,"\t%d:&nbsp;",lineno);&nbsp;<BR><BR>printToken(currentToken,tokenString);&nbsp;<BR><BR>}&nbsp;<BR><BR>return&nbsp;currentToken;&nbsp;<BR><BR>}&nbsp;<BR><BR><BR><BR>这里有点不同的就是,作者用了另外一个getToken函数来代替yylex作为外部输出函数.其中getToken里面也使用了lex默认的输出函数yylex(),同时还做了一些其它的事情.不过我建议大家不要像作者那样另外写自己的结果输出函数,因为在后面,需要和yacc搭配工作的时候,yacc生成的语法分析程序只认名字叫yylex()的词法结果输出函数.&nbsp;<BR><BR>if&nbsp;(firstTime)&nbsp;<BR><BR>{&nbsp;firstTime&nbsp;=&nbsp;FALSE;&nbsp;<BR><BR>lineno++;&nbsp;<BR><BR>yyin&nbsp;=&nbsp;source;&nbsp;<BR><BR>yyout&nbsp;=&nbsp;listing;&nbsp;<BR><BR>}&nbsp;<BR><BR>其中的yyin,yyout,source,listing都是FILE*类型.yyin就是要lex生成的词法扫描程序要扫描的文件,yyout就是基本输出文件(其实我们通常都不用yyout,即使要生成一些输出信息,我们都是自己通过fprintf来输出).&nbsp;<BR><BR>"{"&nbsp;{&nbsp;char&nbsp;c;&nbsp;<BR><BR>do&nbsp;<BR><BR>{&nbsp;c&nbsp;=&nbsp;input();&nbsp;<BR><BR>if&nbsp;(c&nbsp;==&nbsp;EOF)&nbsp;break;&nbsp;<BR><BR>if&nbsp;(c&nbsp;==&nbsp;'\n')&nbsp;lineno++;&nbsp;<BR><BR>}&nbsp;while&nbsp;(c&nbsp;!=&nbsp;'}');&nbsp;<BR><BR>}&nbsp;<BR><BR>其中,作者的这个Tiny&nbsp;C是以{}来包括注释信息.作者并没有写出注释信息的正则表达式,但是它可以通过检索“{”,然后用lex内部函数input()一一检查&nbsp;{&nbsp;后面的字符是不是&nbsp;}&nbsp;来跳过注释文字.(C语言的/*&nbsp;*/注释文字正则表达式十分难写,所以很多时候我们都用这种方法直接把它的DFA(扫描自动机)写出来).&nbsp;<BR><BR>本文就是通过简单地举出两个比较实际的例子来讲解flex输入文件的.再次说明,如果你是第一次接触lex,那么请看看前面我推荐的文章,你可以在IBM的开发者网上查到.下一篇关于yacc于BNF文法的说明也是如此.请大家先参考一下其它标准的教程.&nbsp;<BR><BR><BR>三、范式文法<BR><BR>从这一节开始,我们就算进入编译器构造的正题了.不得不说,前面的词法扫描器在整个编译器部分只是个很小很小的组成,而这两节讲述的语言构造器才能真正为我们的编译工作起到重要的作用.这些东西相信大家在大学的编译原理的课程已经学了不少,那么本文我也只是大致地带过,让大家回忆起大学的知识,重要的yacc使用技巧等等,我将在后面的内容讲出.&nbsp;<BR><BR>例3.1&nbsp;<BR>exp&nbsp;-&gt;&nbsp;exp&nbsp;op&nbsp;exp&nbsp;|&nbsp;(exp)&nbsp;|&nbsp;number&nbsp;<BR>op&nbsp;-&gt;&nbsp;+&nbsp;|&nbsp;-&nbsp;|&nbsp;*&nbsp;<BR><BR>这里就是一个定义的带有加法,减法,乘法的简单整数算术表达式的文法.其中粗体表示的是终结符号,也就是不能有产生式生成的符号.而exp,op就是非终结符,它们都是由一个”-&gt;”符号来产生的.&nbsp;<BR>比如100&nbsp;+&nbsp;222&nbsp;*123123&nbsp;-(888+11)就是符合上述文法的具体的表达式.&nbsp;<BR>注意,在文法定义中,是可以递归的.所以exp产生式右边的式子中可以再次出现exp.&nbsp;<BR>这里的|和正则表达式一样,表示的选择的意思,也就是说,exp可以是exp&nbsp;op&nbsp;exp或者(exp)再或者number.&nbsp;<BR><BR>下面让我们看看&lt;&lt;编译原理及实践&gt;&gt;书中的一个关于BNF文法的介绍.&nbsp;<BR>比如说我们有个数学表达式(34-3)*42,然后我们来看看上面的exp文法怎么来推导识别它.&nbsp;<BR><BR>(1)&nbsp;exp&nbsp;=&gt;&nbsp;exp&nbsp;op&nbsp;exp&nbsp;[exp&nbsp;-&gt;exp&nbsp;op&nbsp;exp]&nbsp;<BR>(2)&nbsp;=&gt;&nbsp;exp&nbsp;op&nbsp;number&nbsp;[exp&nbsp;-&gt;number&nbsp;]&nbsp;<BR>(3)&nbsp;=&gt;&nbsp;exp&nbsp;*&nbsp;number&nbsp;[op&nbsp;-&gt;&nbsp;*&nbsp;]&nbsp;<BR>(4)&nbsp;=&gt;&nbsp;(exp)&nbsp;*&nbsp;number&nbsp;[exp&nbsp;-&gt;(exp)&nbsp;]&nbsp;<BR>(5)&nbsp;=&gt;&nbsp;(exp&nbsp;op&nbsp;exp)&nbsp;*&nbsp;number&nbsp;[exp&nbsp;-&gt;exp&nbsp;op&nbsp;exp]&nbsp;<BR>(6)&nbsp;=&gt;&nbsp;(exp&nbsp;op&nbsp;number)*&nbsp;number&nbsp;[exp&nbsp;-&gt;&nbsp;number&nbsp;]&nbsp;<BR>(7)&nbsp;=&gt;&nbsp;(exp&nbsp;-&nbsp;number)&nbsp;*&nbsp;number&nbsp;[op&nbsp;-&gt;&nbsp;-&nbsp;]&nbsp;<BR>(8)&nbsp;=&gt;&nbsp;(number-number)*&nbsp;number&nbsp;[exp&nbsp;-&gt;&nbsp;number&nbsp;]&nbsp;<BR><BR>最终,exp里面全部的非终结符号全部变成了终结符号.那么推导完成.&nbsp;<BR>这种推导十分像我们在离散数学中讲到的命题推理.其实形式语言的推导的数学基础就是我们离散数学的命题推理.&nbsp;<BR><BR>在推导过程中,其实就是把原来的文法中的递归展开.那么我们在推导的过程,也就很容易实现分析树的生成.而分析树就是我们编译程序中十分重要的信息源.我们之所以前面又做词法分析,又做语法分析的目标就是为了生成分析树.有了它,我们编译程序在后面的代码生成过程中将变得容易百倍.&nbsp;<BR><BR>请看:&nbsp;<BR><BR>例3.2&nbsp;<BR>同样是&lt;&lt;编译原理及实践&gt;&gt;书上的例子.&nbsp;<BR>设E&nbsp;-&gt;&nbsp;E+a&nbsp;|&nbsp;a&nbsp;表示的文法为G,那么考虑它生成的表达L(G)&nbsp;<BR>如果由标准的数学定义,那么我们用公式L(G)={s&nbsp;|&nbsp;exp&nbsp;=&gt;*&nbsp;s&nbsp;}表示一种文法G.&nbsp;<BR>s代表记号符号的任意数组串,也就是我们的终结符号.而exp代表非终结符号,=&gt;*表示一系列的从非终结符到终结符号的推导过程.这里*有点像我们在讲述正则表达式中的*符号一样,它表示0到无限次的重复.所以=&gt;*就是表示0次到无限次的推导过程.&nbsp;<BR><BR>L(G)&nbsp;=&nbsp;{a,a+a,a+a+a,a+a+a+a,…}&nbsp;<BR>E&nbsp;=&gt;&nbsp;E+a&nbsp;=&gt;&nbsp;E+a+a&nbsp;=&gt;&nbsp;E+a+a+a&nbsp;<BR><BR>同时,在我们的编译课本上,又经常讲述另一种数学表达方式来阐述文法的定义.&nbsp;<BR>G=(T,N,P,S)&nbsp;<BR>注意,这里的T,N,P,S都是集合.&nbsp;<BR>T表示终结符号(terminal),也就是这里{a,+}&nbsp;<BR>N表示非终结符号(nonterminal),也就是这里{E},但是N不能与T相交.&nbsp;<BR>P表示产生式(production)或者文法规则(grammar&nbsp;rule)的集合,这里它只有一个元素:&nbsp;E&nbsp;-&gt;&nbsp;E+a&nbsp;<BR>S表示集合N的开始符号(start&nbsp;symbol).关于S,本人也搞不清楚它的用处,所以很抱歉!&nbsp;<BR><BR>例3.3&nbsp;<BR>这是我们C程序语言中经常使用if&nbsp;else文法&nbsp;<BR>statement&nbsp;-&gt;&nbsp;if-stmt&nbsp;|&nbsp;other&nbsp;<BR>if-stmt&nbsp;-&gt;&nbsp;if&nbsp;(exp)&nbsp;statement&nbsp;|&nbsp;if&nbsp;(exp)&nbsp;statement&nbsp;else&nbsp;statement&nbsp;<BR>exp&nbsp;-&gt;&nbsp;0|1&nbsp;<BR><BR>statement就是我们C语言中使用语句,它的产生式包括了两种可能,一是if-stmt语句,二是other.然后我们又另外定义if-stmt语句的产生式.这里有两种情况,一是没有else的,另外就是有else的.里面我们又使用了递归.if-stmt本来是包含在statement里面的,可是我们又在if-stmt的产生式中使用statement.正是因为文法中允许递归,所以它比起我们前面讲的正则表达式有更广泛的表示能力,但同时,文法的推导识别也更加法复杂.按照编译原理的书籍,一般讲完BNF文法后,就要重点讲解文法的推导算法.一共有两种,一是LL算法,自顶向下的算法,二是LR算法,自底向上的算法.LL算法比较简单,其中还有一种特殊的情况,就是我们下一节要讲的递归下降的算法.由于C语言中的函数本来就可以递归,那么实现这中递归下降的算法是十分简单的,而且对于我们一般的程序设计语言来说,虽然它的算法能力很弱,但是已经是足够用了.而关于LR的算法,那么就是一个大难题了.它的算法能力最强,但是实现起来十分困难,还好,已经有科学家为我们提供了yacc(或者叫bison)这个工具,可以来自动生成LR的文法推导算法.这就是我们一直在提到的yacc工具了.&nbsp;<BR><BR>回过头来,我们看看下面的程序&nbsp;<BR>if(0)&nbsp;other&nbsp;else&nbsp;other&nbsp;<BR>的分析树&nbsp;<BR><BR>思考:&nbsp;为什么要把文法最终分析成树结构?&nbsp;<BR>因为文法本身是递归的,而表示的递归的最好数据结构就是树,所以我们把文法弄成树结构后,后面在处理代码生成等问题上,也可以用递归来很容易地完成.&nbsp;<BR><BR>例3.4&nbsp;<BR>这里我给出microsoft在msdn中对于C语言的statement的文法&nbsp;<BR>注意,这里用:符号替代了我们前面产生式的-&gt;&nbsp;<BR><BR>statement&nbsp;:&nbsp;<BR>labeled-statement&nbsp;<BR>compound-statement&nbsp;<BR>expression-statement&nbsp;<BR>selection-statement&nbsp;<BR>iteration-statement&nbsp;<BR>jump-statement&nbsp;<BR>try-except-statement&nbsp;/*&nbsp;Microsoft&nbsp;Specific&nbsp;*/&nbsp;<BR>try-finally-statement&nbsp;/*&nbsp;Microsoft&nbsp;Specific&nbsp;*/&nbsp;<BR>jump-statement&nbsp;:&nbsp;<BR>goto&nbsp;identifier&nbsp;;&nbsp;<BR>continue;&nbsp;<BR>break;&nbsp;<BR>return&nbsp;expression&nbsp;opt&nbsp;;&nbsp;<BR>compound-statement&nbsp;:&nbsp;<BR>{&nbsp;declaration-list&nbsp;opt&nbsp;statement-list&nbsp;opt&nbsp;}&nbsp;<BR>declaration-list&nbsp;:&nbsp;<BR>declaration&nbsp;<BR>declaration-list&nbsp;declaration&nbsp;<BR>statement-list&nbsp;:&nbsp;<BR>statement&nbsp;<BR>statement-list&nbsp;statement&nbsp;<BR>expression-statement&nbsp;:&nbsp;<BR>expression&nbsp;opt&nbsp;;&nbsp;<BR>iteration-statement&nbsp;:&nbsp;<BR>while&nbsp;(&nbsp;expression&nbsp;<IMG onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0>&nbsp;statement&nbsp;<BR>do&nbsp;statement&nbsp;while&nbsp;(&nbsp;expression&nbsp;<IMG onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0>;&nbsp;<BR>for&nbsp;(&nbsp;expression&nbsp;opt&nbsp;;&nbsp;expression&nbsp;opt&nbsp;;&nbsp;expression&nbsp;opt&nbsp;<IMG onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0>&nbsp;statement&nbsp;<BR>selection-statement&nbsp;:&nbsp;<BR>if&nbsp;(&nbsp;expression&nbsp;<IMG onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0>&nbsp;statement&nbsp;<BR>if&nbsp;(&nbsp;expression&nbsp;<IMG onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0>&nbsp;statement&nbsp;else&nbsp;statement&nbsp;<BR>switch&nbsp;(&nbsp;expression&nbsp;<IMG onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0>&nbsp;statement&nbsp;<BR>labeled-statement&nbsp;:&nbsp;<BR>identifier&nbsp;:&nbsp;statement&nbsp;<BR>case&nbsp;constant-expression&nbsp;:&nbsp;statement&nbsp;<BR>default&nbsp;:&nbsp;statement&nbsp;<BR>try-except-statement&nbsp;:&nbsp;/*&nbsp;Microsoft&nbsp;Specific&nbsp;*/&nbsp;<BR>__try&nbsp;compound-statement&nbsp;<BR>__except&nbsp;(&nbsp;expression&nbsp;<IMG onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0>&nbsp;compound-statement&nbsp;<BR>try-finally-statement&nbsp;:&nbsp;/*&nbsp;Microsoft&nbsp;Specific&nbsp;*/&nbsp;<BR>__try&nbsp;compound-statement&nbsp;<BR>__finally&nbsp;compound-statement&nbsp;<BR><BR><BR>四、文法识别(一)<BR><BR>没想到这一系列文件能得到csdn和大家的这么看好,首先要感谢大家的赏识和csdn的推荐.那么我就更没有理由不写好这下面的几篇文章了.本来我的计划是简单把lex和yacc介绍完后就直接进入编译器的构造的技术细节问题讨论,但是最近看了一些国外经典教材后,发现文法的识别问题在编译原理和技术中是个绝不能忽视的问题.即使现在有了yacc工具来帮助我来识别文法,但是有些时候还是需要我们自己来写简单的语法分析器.&nbsp;<BR><BR>1.什么是文法识别(语法分析)&nbsp;<BR>首先要告诉大家的是,这里的文法识别是指的上下文无关的文法,也就是上一节我们一直在讨论的那些&nbsp;BNF式.&nbsp;<BR><BR>比如说,我写了一句&nbsp;<BR>if&nbsp;(a&gt;6+5)&nbsp;printf(“OK!”);&nbsp;else&nbsp;printf(“No!”);&nbsp;<BR>那么它匹配的文法也就是&nbsp;<BR>if-stmt&nbsp;-&gt;&nbsp;if&nbsp;expr&nbsp;stmt&nbsp;<BR>|&nbsp;if&nbsp;expr&nbsp;stmt&nbsp;else&nbsp;stmt&nbsp;<BR>我们通常要为一个程序语言写出很多BNF式的文法,怎么知道这句话是匹配的哪个文法,这就是语法分析器(或者叫文法分析器要做的工作).知道了是那句文法后,我们才能对这句话做出正确的解释,所以文法识别是个不可忽视的工作.下面我来看看我们常使用的文法识别的算法.&nbsp;<BR><BR>2.自顶向下的算法(LL算法)&nbsp;<BR>自顶向下的语法分析算法是十分简单的.自顶向下的算法也叫LL算法.LL(k)就是向前预测k个符号的自顶向下的算法.不过无论是我们国内的编译教程还是国外的经典教程都是只讨论了LL(1)算法.因为一般的程序语言,只使用LL(1)算法就已经足够了.这里我们同样也只是讨论LL(1)算法.&nbsp;<BR><BR>其中有种特殊的算法叫做递归下降的算法,在C语言中,由于函数本身是可以递归的,所以实现这种算法就只需要写简单的几个函数的递归过程就是了.&nbsp;<BR>为什么叫自顶向下呢?因为在分析过程中,我们是从语法树的树顶逐步向树底分析的,所以叫自顶向下的算法.&nbsp;<BR><BR>为了方便说明自顶向下算法的简单性,我们来看一下&lt;&gt;中的一个例子.(本系列文章经常要引用国外经典著作的范例,希望大家不要告我抄袭,我实在找不到比大师的范例更经典的范例了)&nbsp;<BR><BR>例4.1&nbsp;<BR>考虑一个Pascal中定义变量的文法.&nbsp;<BR><BR>特别说明,这里的dotdot表示”..”&nbsp;<BR>type&nbsp;-&gt;&nbsp;simple&nbsp;|&nbsp;id&nbsp;|&nbsp;array&nbsp;[&nbsp;simple&nbsp;]&nbsp;of&nbsp;type&nbsp;<BR>simple&nbsp;-&gt;&nbsp;integer&nbsp;|&nbsp;char&nbsp;|&nbsp;num&nbsp;dotdot&nbsp;num&nbsp;<BR><BR>在为array[&nbsp;num&nbsp;dotdot&nbsp;num]&nbsp;of&nbsp;integer构造一个分析数的时候,该算法就是从根结点开始.&nbsp;<BR>下面我们通过其中主要的三个步骤来看看算法的实现原理.&nbsp;<BR><BR>第一步分析:&nbsp;<BR><BR>首先分析的是输入的字符串第一个串”array”,判断出它属于type的First集合.所以在图中的分析树部分,我们的当前分析就是树根结点type.(图中标上箭头,就表示是当前正在分析的部分).&nbsp;<BR>这里遇到一个新名词:First集合.在大学里的编译课程肯定是讲过First集合的吧.不过我还是要在这里重复一次了.&nbsp;<BR><BR>名词解释First集合:&nbsp;<BR><BR>在对文法产生式进行判断的时候,每个产生式都是由好几个终结符和非终结符构成.比如&nbsp;<BR>本例中的文法&nbsp;<BR>type&nbsp;-&gt;&nbsp;simple&nbsp;<BR>|&nbsp;id&nbsp;<BR>|&nbsp;array&nbsp;[&nbsp;simple&nbsp;]&nbsp;of&nbsp;type&nbsp;<BR>simple&nbsp;-&gt;&nbsp;integer&nbsp;<BR>|&nbsp;char&nbsp;<BR>|&nbsp;num&nbsp;dotdot&nbsp;num&nbsp;<BR><BR>判断type的产生式的时候,如果我们把每个产生式里面的simple,id,array,&nbsp;[&nbsp;,simple&nbsp;,]&nbsp;,&nbsp;of&nbsp;,&nbsp;type这些终结符和非终结符都进行判断的话,那么就会涉及到”试验和错误”的问题.当一个文法产生式分析到最后,发现并不匹配,就必然会产生回溯的问题,就要回到头,从新开始对第二个产生式逐步进行判断分析.我们知道,回溯的算法效率肯定是十分低效率的.但是实际上我们完全可以避免这种回溯算法,而完成同样工作的文法分析.这就产生了计算First集合的理论和以及后面的左提公因式的问题.&nbsp;<BR><BR>First集合简单地说,就是一个非终结符的最开头的字符串(终结符号)的集合.比如说.&nbsp;<BR>First(simple)&nbsp;=&nbsp;{&nbsp;integer,&nbsp;char,&nbsp;num&nbsp;}&nbsp;<BR>First(type)&nbsp;=&nbsp;First(simple)&nbsp;U&nbsp;{&nbsp;id,&nbsp;array&nbsp;}&nbsp;<BR>这里的type的一个产生式中有个simple非终结符在其开头,那么simple的开头字符串同时也可以是simple,所以First(simple)也是First(type)的一部分.&nbsp;<BR>为什么我们只计算每个非终结符的最开头的终结符?&nbsp;因为我们这里是考虑的LL(1)算法,LL(1)算法只向前预测一个字符号,所以我们只考虑一个First集合就可以判断出是哪个文法产生式了.&nbsp;<BR><BR>这里听起来似乎有些不太可能,一个产生式有那么千百万化,如果单单只看第一个非终结符号,如果就能说明一个输入串到底是哪个产生式呢?&nbsp;如果有两个产生式的最开头一样怎么办,比如像if语句,那怎么办?&nbsp;但其实我们几乎所有的程序语言的文法都可以通过LL(1)来分析出来.原因是我们可以通过左提公因式来把最开头的相同的产生式的公共终结符号提取出来,留下两个子产生式,而他们的最开头的非终结符号不相同.&nbsp;<BR><BR>左提公因式:&nbsp;<BR>例4.2&nbsp;<BR>考虑文法&nbsp;<BR>A&nbsp;-&gt;&nbsp;ab&nbsp;<BR>|ac&nbsp;<BR>这里,A的两个产生式中最开头的终结符号都是’a’,那么就无法通过这个最开头的终结符号来判断一个输入串到底该是哪个产生式了.那么我们可以修改文法成&nbsp;<BR>A&nbsp;-&gt;&nbsp;aA’&nbsp;<BR>A’-&gt;&nbsp;b&nbsp;|&nbsp;c&nbsp;<BR>这样一来,一个文法变成两个,但是无论A还是A’,它们的每个产生式的First集合都是不相交的.所以,他们能够只通过最开头的终结符号来判断是哪个产生式.&nbsp;<BR>这个变化过程有点想我们的代数里面的&nbsp;ab&nbsp;+&nbsp;ac&nbsp;=&nbsp;a(b+c),所以叫它左提公因式.&nbsp;<BR>这只是个简单的左提公因式的例子,实际当中还会遇到一些复杂的问题.但是无论是哪个编译教材,都会给出针对一切文法的左提公因式的算法.同样,计算First集合的算法也在教材中详细讲解了.我就不在这里再描述了.&nbsp;<BR><BR>第二步分析:&nbsp;<BR><BR>经过第一步的考察输入串中的第一个串为”array”属于非终结符号type第三个产生式的First集合,那么就可以确定这个串确实为type文法第三个产生式的串.所以在第二步中,展开出type的第三个产生式出来.&nbsp;type&nbsp;-&gt;&nbsp;array&nbsp;[&nbsp;simple&nbsp;]&nbsp;of&nbsp;integer&nbsp;<BR>那么接下来就是继续分析构造出来的type&nbsp;-&gt;&nbsp;array[&nbsp;simple]&nbsp;of&nbsp;integer产生式中的每个结点.&nbsp;<BR>所以箭头又放到了分析树中type的第一个孩结点array上.因为array是终结符号,如果它和输入中的当前箭头所指的终结符号相同,那么箭头都往下移动一结点到’[‘符号.同样地,由于分析树中的’[‘是终结符号,那么只要看输入中的串是否是’[‘就可以了.如果是,那么继续往下分析.分析到分析数中的simple的时候,由于simple是非终结符号,那么就需要考虑simple的产生式了.&nbsp;<BR><BR>第三步分析:&nbsp;<BR><BR>在第二步中,分析到分析数中的simple子结点的时候,由于simple是非终结符号,那么就需要考虑simple的产生式.simple一共有三个产生式.通过输入串当前的串是”num”,是属于simple产生式中第3个产生式的First集合,所以simple在分析数中就按第三个产生式simple&nbsp;-&gt;&nbsp;num&nbsp;dotdot&nbsp;num&nbsp;来展开.那么分析箭头同样,也自动移动到simple的第一个子结点num上继续分析.&nbsp;<BR><BR><BR>总体说来,这中自顶向下的分析原理就基本上是上面的过程.通过计算产生式的First集合,来逐步产生非终结符的产生式.最后的分析树都会划归到终结符来进行判断(非终结符号是无法进行直接判断的,一定要展开过后才行).&nbsp;<BR><BR>看了原理,我们再看实现的伪代码.代码很简单.&nbsp;<BR>void&nbsp;match(char&nbsp;token)&nbsp;<BR>{&nbsp;<BR>if&nbsp;lookahead&nbsp;==&nbsp;token)&nbsp;<BR>lookahead&nbsp;=&nbsp;token;&nbsp;<BR>else&nbsp;<BR>error(0);&nbsp;<BR>}&nbsp;<BR><BR>void&nbsp;type()&nbsp;<BR>{&nbsp;<BR>if(&nbsp;lookahead&nbsp;==&nbsp;integer&nbsp;||&nbsp;lookeahead&nbsp;==&nbsp;char&nbsp;||&nbsp;lookahead&nbsp;==&nbsp;num)&nbsp;<BR>simple();&nbsp;<BR>else&nbsp;if(&nbsp;lookahead&nbsp;==&nbsp;id)&nbsp;<BR>match(id);&nbsp;<BR>else&nbsp;if(&nbsp;lookahead&nbsp;==&nbsp;array)&nbsp;<BR>{&nbsp;<BR>match(array);&nbsp;match(')');&nbsp;simple();&nbsp;match(')');&nbsp;match(of);&nbsp;type();&nbsp;<BR>}&nbsp;<BR>else&nbsp;<BR>error(0);&nbsp;<BR>}&nbsp;<BR><BR>void&nbsp;simple()&nbsp;<BR>{&nbsp;<BR>if(&nbsp;lookahead&nbsp;==&nbsp;integar)&nbsp;match(integer);&nbsp;<BR>else&nbsp;if(&nbsp;lookahead&nbsp;==&nbsp;char)&nbsp;match(char);&nbsp;<BR>else&nbsp;if(&nbsp;lookahead&nbsp;==&nbsp;num)&nbsp;<BR>{&nbsp;<BR>match(num);&nbsp;match(dotdot);&nbsp;match(num);&nbsp;<BR>}&nbsp;<BR>else&nbsp;<BR>error(0);&nbsp;<BR>}&nbsp;<BR><BR>注意:这里的代码都是纯的语法分析代码,实际执行过程中并没有什么用处,但是我们构造语法树parse-tree的代码就是镶嵌在这些纯的语法分析代码中.<BR><BR><BR>&nbsp;五、实用javacc<BR><BR>前言&nbsp;<BR><BR>本系列的文章的宗旨是让大家能够写出自己的编译器,解释器或者脚本引擎,所以每到理论介绍到一个程度后,我都会来讨论实践问题.理论方面,编译原理的教材已经是够多了,而实践的问题却很少讨论.&nbsp;<BR><BR>前几节文章只讨论到了词法分析和LL文法分析,关键的LR文法分析这里却还没有讲,我们先不要管复杂的LR文法和算法,让我们使用LL算法来实际做一些东西后再说.本文将介绍一个在JAVA上广泛使用的LL算法分析工具Javacc.(这是我唯一能找到的使用LL算法的语法分析器构造工具).这一节的文章并非只针对JAVA开发者,如果你是C/C++开发者,那么也请你来看看这个JAVA下的优秀工具,或许你将来也用得着它.&nbsp;<BR><BR>Lex和yacc这两个工具是经典的词法分析和语法分析工具,但是它们都是基于C语言下面的工具,而使用JAVA的朋友们就用不上了.但是JAVA下已经有了lex和yacc的替代品javacc(Java&nbsp;Compiler&nbsp;Compiler&nbsp;<IMG onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0>.同时javacc也是使用LL算法的工具,我们也可以实践一下前面学的LL算法.&nbsp;<BR><BR>首先声明我不是一个JAVA专家,我也是刚刚才接触JAVA.Java里面或许有很多类似javacc一样的工具,但是据我所知,javacc还是最广泛,最标准的JAVA下的词法语法分析器.&nbsp;<BR><BR>Javacc的获取&nbsp;<BR>同lex和yacc一样,javacc也是一个免费可以获取的通用工具,它可以在很多JAVA相关的工具下载网站下载,当然,javacc所占的磁盘空间比起lex和yacc更大一些,里面有标准的文档和examples.相对lex和yacc来说,javacc做得更人性化,更容易一些.如果你实在找不到javacc,还是可以联系我,我这里有.现在最新的就是javacc&nbsp;3.2版本.&nbsp;<BR><BR>Javacc的原理&nbsp;<BR><BR>Javacc可以同时完成对text的词法分析和语法分析的工作,使用起来相当方便.同样,它和lex和yacc一样,先输入一个按照它规定的格式的文件,然后javacc根据你输入的文件来生成相应的词法分析于语法分析程序.同时,新版本的Javacc除了常规的词法分析和语法分析以外,还提供JJTree等工具来帮助我们建立语法树.总之,Javacc在很多地方做得都比lex和yacc要人性化,这个在后面的输入文件格式中也能体现出来.&nbsp;<BR><BR>Javacc的输入文件&nbsp;<BR><BR>Javacc的输入文件格式做得比较简单.每个非终结符产生式对应一个Class中的函数,函数中可以嵌入相应的识别出该终结符文法时候的处理代码(也叫动作).这个与YACC中是一致的.&nbsp;<BR><BR>Javacc的输入文件中,有一系列的系统参数,比如其中lookahead可以设置成大于1的整数,那么就是说,它可以为我们生成LL(k)算法(k&gt;=1),而不是简单的递归下降那样的LL(1)算法了.要知道,LL(2)文法比起前面讨论的LL(1)文法判断每个非终结符时候需要看前面两个记号而不是一个,那么对于文法形式的限制就更少.不过LL(2)的算法当然也比LL(1)算法慢了不少.作为一般的计算机程序设计语言,LL(1)算法已经是足够了.就算不是LL(1)算法,我们也可以通过前面讲的左提公因式把它变成一个LL(1)文法来处理.不过既然javacc都把lookahead选择做出来了,那么在某些特定的情况下,我们可以直接调整一个lookahead的参数就可以,而不必纠正我们的文法.&nbsp;<BR><BR><BR>下面我们来看看Javacc中自带的example中的例子.&nbsp;<BR><BR>例5.1&nbsp;<BR><BR>这个例子可以在javacc-3.2/doc/examples/SimpleExamples/Simple1.jj看到&nbsp;<BR><BR>PARSER_BEGIN(Simple1)&nbsp;<BR><BR>public&nbsp;class&nbsp;Simple1&nbsp;{&nbsp;<BR><BR>public&nbsp;static&nbsp;void&nbsp;main(String&nbsp;args[])&nbsp;throws&nbsp;ParseException&nbsp;{&nbsp;<BR><BR>Simple1&nbsp;parser&nbsp;=&nbsp;new&nbsp;Simple1(System.in);&nbsp;<BR><BR>parser.Input();&nbsp;<BR><BR>}&nbsp;<BR><BR>}&nbsp;<BR><BR>PARSER_END(Simple1)&nbsp;<BR><BR>void&nbsp;Input()&nbsp;:&nbsp;<BR><BR>{}&nbsp;<BR><BR>{&nbsp;<BR><BR>MatchedBraces()&nbsp;("\n"|"\r"<IMG onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0>*&nbsp;<BR><BR>}&nbsp;<BR><BR>void&nbsp;MatchedBraces()&nbsp;:&nbsp;<BR><BR>{}&nbsp;<BR><BR>{&nbsp;<BR><BR>"{"&nbsp;[&nbsp;MatchedBraces()&nbsp;]&nbsp;"}"&nbsp;<BR><BR>}&nbsp;<BR><BR><BR><BR>设置好javacc的bin目录后,在命令提示符下输入&nbsp;<BR><BR>javacc&nbsp;Simple1.jj&nbsp;<BR><BR>然后javacc就会为你生成下面几个java源代码文件&nbsp;<BR><BR>Simple1.java&nbsp;<BR><BR>Simple1TokenManager.java&nbsp;<BR><BR>Simple1Constants.java&nbsp;<BR><BR>SimpleCharStream.java&nbsp;<BR><BR>Token.java&nbsp;<BR><BR>TokenMgrError.java&nbsp;<BR><BR><BR><BR>其中Simple1就是你的语法分析器的对象,它的构造函数参数就是要分析的输入流,这里的是System.in.&nbsp;<BR><BR>class&nbsp;Simple1就定义在标记PARSER_BEGIN(Simple1)&nbsp;<BR><BR>PARSER_END(Simple1)之间.&nbsp;<BR><BR>但是必须清楚的是,PARSER_BEGIN和PARSER_END中的名字必须是词法分析器的名字(这里是Simple1).&nbsp;<BR><BR>PARSER_END下面的定义就是文法非终结符号的定义了.&nbsp;<BR><BR>Simple1的文法基本就是:&nbsp;<BR><BR>Input&nbsp;-&gt;&nbsp;MatchedBraces&nbsp;("\n"|"\r"<IMG onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0>*&nbsp;<BR><BR>MatchedBraces&nbsp;-&gt;&nbsp;“{“&nbsp;MatchedBraces&nbsp;“}”&nbsp;<BR><BR>从它的定义我们可以看到,每个非终结符号对于一个过程.&nbsp;<BR><BR>比如Input的过程&nbsp;<BR><BR>void&nbsp;Input()&nbsp;:&nbsp;<BR><BR>{}&nbsp;<BR><BR>{&nbsp;<BR><BR>MatchedBraces()&nbsp;("\n"|"\r"<IMG onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0>*&nbsp;<BR><BR>}&nbsp;<BR><BR><BR>在定义void&nbsp;Input后面记住需要加上一个冒号”:”,然后接下来是两个块{}的定义.&nbsp;<BR><BR>第一个{}中的代码是定义数据,初试化数据的代码.第二个{}中的部分就是真正定义Input的产生式了.&nbsp;<BR><BR>每个产生式之间用”|”符号连接.&nbsp;<BR><BR>注意:&nbsp;这里的产生式并非需要严格BNF范式文法,它的文法既可以是BNF,同时还可以是混合了正则表达式中的定义方法.比如上面的&nbsp;<BR><BR>Input&nbsp;-&gt;&nbsp;MatchedBraces&nbsp;("\n"|"\r"<IMG onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0>*&nbsp;<BR><BR>中(“\n”|”\r”)*&nbsp;就是个正则表达式,表示的是\n或者\r的0个到无限个的重复的记号.&nbsp;<BR><BR>而是javacc系统定义的记号(TOKEN),表示文件结束符号.&nbsp;<BR><BR>除了,无论是系统定义的TOKEN,还是自定义的TOKEN,&nbsp;里面的TOKEN都是以的方式表示.&nbsp;<BR><BR>每个非终结符号(Input和MatchedBraces)都会在javacc生成的Simple1.java中形成Class&nbsp;Simple1的成员函数.当你在外部调用Simple1的Input,那么语法分析器就会开始进行语法分析了.&nbsp;<BR><BR>例5.2&nbsp;<BR><BR>在javacc提供的example里面没有.javacc提供的example里面提供的例子中SimpleExamples过于简单,而其它例子又过于庞大.下面我以我们最常见的数学四则混合运算的文法来构造一个javacc的文法识别器.这个例子是我自己写的,十分简单,.其中还包括了文法识别同时嵌入的构建语法树Parse-Tree的代码.不过由于篇幅的原因,我并没有给出全部的代码,这里只给了javacc输入部分相关的代码.而Parse-tree就是一个普通的4叉树,3个child,1个next(平行结点),相信大家在学习数据结构的时候应该都是学过的.所以这里就省略过去了.&nbsp;<BR><BR>在大家看这些输入代码之前,我先给出它所使用的文法定义,好让大家有个清楚的框架.&nbsp;<BR><BR>Expression&nbsp;-&gt;&nbsp;Term&nbsp;{&nbsp;Addop&nbsp;Term&nbsp;}&nbsp;<BR>Addop&nbsp;-&gt;&nbsp;"+"&nbsp;|&nbsp;"-"&nbsp;<BR>Term&nbsp;-&gt;&nbsp;Factor&nbsp;{&nbsp;Mulop&nbsp;Factor&nbsp;}&nbsp;<BR>Mulop&nbsp;-&gt;&nbsp;"*"&nbsp;|&nbsp;"/"&nbsp;<BR>Factor&nbsp;-&gt;&nbsp;ID&nbsp;|&nbsp;NUM&nbsp;|&nbsp;"("&nbsp;Expression&nbsp;"<IMG onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0>"&nbsp;<BR><BR>这里的文法可能和BNF范式有点不同.{}的意思就是0次到无限次重复,它跟我们在学习正则表达式的时候的”*”符号相同,所以，在Javacc中的文法表示的时候，{…}部分的就是用(…)*来表示.&nbsp;<BR><BR>为了让词法分析做得更简单,我们通常都不会在文法分析的时候,使用”(”,”)“等字符号串来表示终结符号,而需要转而使用LPAREN,&nbsp;RPAREN这样的整型符号来表示.&nbsp;<BR><BR>PARSER_BEGIN(Grammar)&nbsp;<BR><BR>public&nbsp;class&nbsp;Grammar&nbsp;implements&nbsp;NodeType&nbsp;{&nbsp;<BR><BR>public&nbsp;ParseTreeNode&nbsp;GetParseTree(InputStream&nbsp;in)&nbsp;throws&nbsp;ParseException&nbsp;<BR><BR>{&nbsp;<BR><BR>Grammar&nbsp;parser&nbsp;=new&nbsp;Grammar(in);&nbsp;<BR><BR>return&nbsp;parser.Expression();&nbsp;<BR><BR>}&nbsp;<BR><BR><BR>}&nbsp;<BR><BR>PARSER_END(Grammar)&nbsp;<BR><BR>SKIP&nbsp;:&nbsp;<BR><BR>{&nbsp;<BR><BR>"&nbsp;"&nbsp;|&nbsp;"\t"&nbsp;|&nbsp;"\n"&nbsp;|&nbsp;"\r"&nbsp;<BR><BR>}&nbsp;<BR><BR>TOKEN&nbsp;:&nbsp;<BR><BR>{&nbsp;<BR><BR>&lt;&nbsp;ID:&nbsp;["a"-"z","A"-"Z","_"]&nbsp;(&nbsp;["a"-"z","A"-"Z","_","0"-"9"]&nbsp;<IMG onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0>*&nbsp;&gt;&nbsp;<BR><BR>|&nbsp;&lt;&nbsp;NUM:&nbsp;(&nbsp;["0"-"9"]&nbsp;<IMG onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0>+&nbsp;&gt;&nbsp;<BR><BR>|&nbsp;&lt;&nbsp;PLUS:&nbsp;"+"&nbsp;&gt;&nbsp;<BR><BR>|&nbsp;&lt;&nbsp;MINUS:&nbsp;"-"&nbsp;&gt;&nbsp;<BR><BR>|&nbsp;&lt;&nbsp;TIMERS:&nbsp;"*"&nbsp;&gt;&nbsp;<BR><BR>|&nbsp;&lt;&nbsp;OVER:&nbsp;"/"&nbsp;&gt;&nbsp;<BR><BR>|&nbsp;&lt;&nbsp;LPAREN:&nbsp;"("&nbsp;&gt;&nbsp;<BR><BR>|&nbsp;&lt;&nbsp;RPAREN:&nbsp;"<IMG onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0>"&nbsp;&gt;&nbsp;<BR><BR>}&nbsp;<BR><BR><BR>ParseTreeNode&nbsp;Expression()&nbsp;:&nbsp;<BR><BR>{&nbsp;<BR><BR>ParseTreeNode&nbsp;ParseTree&nbsp;=&nbsp;null;&nbsp;<BR><BR>ParseTreeNode&nbsp;node;&nbsp;<BR><BR>}&nbsp;<BR><BR>{&nbsp;<BR><BR>(&nbsp;node=Simple_Expression()&nbsp;<BR><BR>{&nbsp;<BR><BR>if(ParseTree&nbsp;==&nbsp;null)&nbsp;<BR><BR>ParseTree&nbsp;=node;&nbsp;<BR><BR>else&nbsp;<BR><BR>{&nbsp;<BR><BR>ParseTreeNode&nbsp;t;&nbsp;<BR><BR>t=&nbsp;ParseTree;&nbsp;<BR><BR>while(t.next&nbsp;!=&nbsp;null)&nbsp;<BR><BR>t=t.next;&nbsp;<BR><BR>t.next&nbsp;=&nbsp;node;&nbsp;<BR><BR>}&nbsp;<BR><BR>}&nbsp;<BR><BR>)*&nbsp;<BR><BR>{&nbsp;return&nbsp;ParseTree;}&nbsp;<BR><BR>}&nbsp;<BR><BR>ParseTreeNode&nbsp;Simple_Expression()&nbsp;:&nbsp;<BR><BR>{&nbsp;<BR><BR>ParseTreeNode&nbsp;node;&nbsp;<BR><BR>ParseTreeNode&nbsp;t;&nbsp;<BR><BR>int&nbsp;op;&nbsp;<BR><BR>}&nbsp;<BR><BR>{&nbsp;<BR><BR>node=Term(){}&nbsp;<BR><BR>(&nbsp;<BR><BR>op=addop()&nbsp;t=Term()&nbsp;<BR><BR>{&nbsp;<BR><BR>ParseTreeNode&nbsp;newNode&nbsp;=&nbsp;new&nbsp;ParseTreeNode();&nbsp;<BR><BR>newNode.nodetype&nbsp;=&nbsp;op;&nbsp;<BR><BR>newNode.child[0]&nbsp;=&nbsp;node;&nbsp;<BR><BR>newNode.child[1]&nbsp;=&nbsp;t;&nbsp;<BR><BR>switch(op)&nbsp;<BR><BR>{&nbsp;<BR><BR>case&nbsp;PlusOP:&nbsp;<BR><BR>newNode.name&nbsp;=&nbsp;"Operator:&nbsp;+";&nbsp;<BR><BR>break;&nbsp;<BR><BR>case&nbsp;MinusOP:&nbsp;<BR><BR>newNode.name&nbsp;=&nbsp;"Operator:&nbsp;-";&nbsp;<BR><BR>break;&nbsp;<BR><BR>}&nbsp;<BR><BR>node&nbsp;=&nbsp;newNode;&nbsp;<BR><BR>}&nbsp;<BR><BR>)*&nbsp;<BR><BR>{&nbsp;return&nbsp;node;&nbsp;}&nbsp;<BR><BR>}&nbsp;<BR><BR>int&nbsp;addop()&nbsp;:&nbsp;{}&nbsp;<BR><BR>{&nbsp;<BR><BR>{&nbsp;return&nbsp;PlusOP;&nbsp;}&nbsp;<BR><BR>|&nbsp;{&nbsp;return&nbsp;MinusOP;&nbsp;}&nbsp;<BR><BR>}&nbsp;<BR><BR>ParseTreeNode&nbsp;Term()&nbsp;:&nbsp;<BR><BR>{&nbsp;<BR><BR>ParseTreeNode&nbsp;node;&nbsp;<BR><BR>ParseTreeNode&nbsp;t;&nbsp;<BR><BR>int&nbsp;op;&nbsp;<BR><BR>}&nbsp;<BR><BR>{&nbsp;<BR><BR>node=Factor(){}&nbsp;<BR><BR>(&nbsp;<BR><BR>op=mulop()&nbsp;t=Factor()&nbsp;<BR><BR>{&nbsp;<BR><BR>ParseTreeNode&nbsp;newNode&nbsp;=&nbsp;new&nbsp;ParseTreeNode();&nbsp;<BR><BR>newNode.nodetype&nbsp;=&nbsp;op;&nbsp;<BR><BR>newNode.child[0]&nbsp;=&nbsp;node;&nbsp;<BR><BR>newNode.child[1]&nbsp;=&nbsp;t;&nbsp;<BR><BR>switch(op)&nbsp;<BR><BR>{&nbsp;<BR><BR>case&nbsp;TimersOP:&nbsp;<BR><BR>newNode.name&nbsp;=&nbsp;"Operator:&nbsp;*";&nbsp;<BR><BR>break;&nbsp;<BR><BR>case&nbsp;OverOP:&nbsp;<BR><BR>newNode.name&nbsp;=&nbsp;"Operator:&nbsp;/";&nbsp;<BR><BR>break;&nbsp;<BR><BR>}&nbsp;<BR><BR>node&nbsp;=&nbsp;newNode;&nbsp;<BR><BR>}&nbsp;<BR><BR>)*&nbsp;<BR><BR>{&nbsp;<BR><BR>return&nbsp;node;&nbsp;<BR><BR>}&nbsp;<BR><BR>}&nbsp;<BR><BR>int&nbsp;mulop()&nbsp;:{}&nbsp;<BR><BR>{&nbsp;<BR><BR>{&nbsp;return&nbsp;TimersOP;&nbsp;}&nbsp;<BR><BR>|&nbsp;{&nbsp;return&nbsp;OverOP;&nbsp;}&nbsp;<BR><BR>}&nbsp;<BR><BR>ParseTreeNode&nbsp;Factor()&nbsp;:&nbsp;<BR><BR>{&nbsp;<BR><BR>ParseTreeNode&nbsp;node;&nbsp;<BR><BR>Token&nbsp;t;&nbsp;<BR><BR>}&nbsp;<BR><BR>{&nbsp;<BR><BR>t=&nbsp;<BR><BR>{&nbsp;<BR><BR>node=new&nbsp;ParseTreeNode();&nbsp;<BR><BR>node.nodetype=&nbsp;IDstmt;&nbsp;<BR><BR>node.name&nbsp;=&nbsp;t.image;&nbsp;<BR><BR>return&nbsp;node;&nbsp;<BR><BR>}&nbsp;<BR><BR>|&nbsp;<BR><BR>t=&nbsp;<BR><BR>{&nbsp;<BR><BR>node=new&nbsp;ParseTreeNode();&nbsp;<BR><BR>node.nodetype=&nbsp;NUMstmt;&nbsp;<BR><BR>node.name&nbsp;=&nbsp;t.image;&nbsp;<BR><BR>node.value=&nbsp;Integer.parseInt(t.image);&nbsp;<BR><BR>return&nbsp;node;&nbsp;<BR><BR>}&nbsp;<BR><BR>|&nbsp;<BR><BR>node=Simple_Expression()&nbsp;<BR><BR>{&nbsp;<BR><BR>return&nbsp;node;&nbsp;<BR><BR>}&nbsp;<BR><BR>}&nbsp;<BR><BR><BR><BR>其中SKIP&nbsp;中的定义就是在进行词法分析的同时,忽略掉的记号.TOKEN中的,就是需要在做词法分析的时候,识别的词法记号.当然,这一切都是以正则表达式来表示的.&nbsp;<BR><BR>这个例子就有多个非终结符号,可以看出,我们需要为每个非终结符号写出一个过程.不同的非终结符号的识别过程中可以互相调用.&nbsp;<BR><BR>以Simple_Expression()过程为例,它的产生式是Expression&nbsp;-&gt;&nbsp;Term&nbsp;{&nbsp;addop&nbsp;Term&nbsp;},而在javacc的输入文件格式是，它的识别是这样写的node=Term(){}&nbsp;(&nbsp;op=addop()&nbsp;t=Term(){&nbsp;…&nbsp;})*&nbsp;前面说过，这里的”*”符号和正则表达式是一样的，就是0次到无限次的重复.那么Simple_Expression等于文法Term&nbsp;Addop&nbsp;Term&nbsp;Addop&nbsp;Term&nbsp;Addop&nbsp;Term&nbsp;…&nbsp;而Addop也就相当于PLUS和MINUS两个运算符号.这里我们在写Expression的文法的时候，同时还使用了赋值表达式，因为这个和Yacc不同的时候，Javacc把文法识别完全地做到了函数过程中，那么如果我们要识别Simple_Expression的文法，就相当于按顺序识别Term和Addop两个文法,而识别那个文法，就相当于调用那两个非终结符的识别函数.正是这一点，我觉得Javacc的文法识别处理上就很接近程序的操作过程,我们不需要像YACC那样使用严格的文法表示格式，复杂的系统参数了.&nbsp;<BR><BR>关于Yacc的使用，其实比Javacc要复杂，还需要考虑到和词法分析器接口的问题，这个我会在以后细细讲到.&nbsp;<BR><BR>至于其它的文法操作解释我就不再多说了,如果要说,就是再写上十篇这样的文章也写不完.本文只能给读者们一个方向,至于深入的研究,还是请大家看javacc提供的官方文档资料.&nbsp;<BR><BR>最后&nbsp;<BR><BR>由于国外使用JAVA做项目的程序员比国内多,那么讨论JAVA技术的人员也比较多.可能来这里读我的文章的人都是C/C++程序员,但是关注其它领域同方向的技术也是可以让我们的知识领域更加宽广.关于JavaCC的讨论主要是在国际新闻组comp.compilers.tools.javacc如果大家在使用JavaCC做实际问题的时候遇到什么问题,不妨上去找找专家.&nbsp;<BR><BR>2003-11-16&nbsp;<BR><BR>成都,四川大学,计算机学院&nbsp; <SPAN id=dissub3>
<DIV id=blog_sub_st name="blog_sub_st"><BR></DIV></SPAN><img src ="http://www.cnitblog.com/oliver_yin/aggbug/2211.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/oliver_yin/" target="_blank">生活像一团麻</a> 2005-08-20 13:07 <a href="http://www.cnitblog.com/oliver_yin/articles/2211.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>经典正则表达式--转载</title><link>http://www.cnitblog.com/oliver_yin/articles/2209.html</link><dc:creator>生活像一团麻</dc:creator><author>生活像一团麻</author><pubDate>Sat, 20 Aug 2005 04:43:00 GMT</pubDate><guid>http://www.cnitblog.com/oliver_yin/articles/2209.html</guid><wfw:comment>http://www.cnitblog.com/oliver_yin/comments/2209.html</wfw:comment><comments>http://www.cnitblog.com/oliver_yin/articles/2209.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/oliver_yin/comments/commentRss/2209.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/oliver_yin/services/trackbacks/2209.html</trackback:ping><description><![CDATA[<P>正则表达式用于字符串处理，表单验证等场合，实用高效，但用到时总是不太把握，以致往往要上网查一番。我将一些常用的表达式收藏在这里，作备忘之用。本贴随时会更新。</P>
<P>匹配中文字符的正则表达式： [\u4e00-\u9fa5]</P>
<P>匹配双字节字符(包括汉字在内)：[^\x00-\xff]</P>
<P>应用：计算字符串的长度（一个双字节字符长度计2，ASCII字符计1）</P>
<P>String.prototype.len=function(){return this.replace([^\x00-\xff]/g,"aa").length;}</P>
<P>匹配空行的正则表达式：\n[\s| ]*\r</P>
<P>匹配HTML标记的正则表达式：/&lt;(.*)&gt;.*&lt;\/\1&gt;|&lt;(.*) \/&gt;/ </P>
<P>匹配首尾空格的正则表达式：(^\s*)|(\s*$)</P>
<P>应用：javascript中没有像vbscript那样的trim函数，我们就可以利用这个表达式来实现，如下：</P>
<P>String.prototype.trim = function()<BR>{<BR>return this.replace(/(^\s*)|(\s*$)/g, "");<BR>}</P>
<P>利用正则表达式分解和转换IP地址：</P>
<P>下面是利用正则表达式匹配IP地址，并将IP地址转换成对应数值的Javascript程序：</P>
<P>function IP2V(ip)<BR>{<BR>re=/(\d+)\.(\d+)\.(\d+)\.(\d+)/g //匹配IP地址的正则表达式<BR>if(re.test(ip))<BR>{<BR>return RegExp.$1*Math.pow(255,3))+RegExp.$2*Math.pow(255,2))+RegExp.$3*255+RegExp.$4*1<BR>}<BR>else<BR>{<BR>throw new Error("Not a valid IP address!")<BR>}<BR>}</P>
<P>不过上面的程序如果不用正则表达式，而直接用split函数来分解可能更简单，程序如下：</P>
<P>var ip="10.100.20.168"<BR>ip=ip.split(".")<BR>alert("IP值是："+(ip[0]*255*255*255+ip[1]*255*255+ip[2]*255+ip[3]*1))</P>
<P>匹配Email地址的正则表达式：\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*</P>
<P>匹配网址URL的正则表达式：<A href="http://([/w-]+/.)+[/w-]+(/[/w">http://([\w-]+\.)+[\w-]+(/[\w</A>- ./?%&amp;=]*)?</P>
<P>利用正则表达式去除字串中重复的字符的算法程序：</P>
<P>var s="abacabefgeeii"<BR>var s1=s.replace(/(.).*\1/g,"$1")<BR>var re=new RegExp("["+s1+"]","g")<BR>var s2=s.replace(re,"") <BR>alert(s1+s2) //结果为：abcefgi</P>
<P>我原来在CSDN上发贴寻求一个表达式来实现去除重复字符的方法，最终没有找到，这是我能想到的最简单的实现方法。思路是使用后向引用取出包括重复的字符，再以重复的字符建立第二个表达式，取到不重复的字符，两者串连。这个方法对于字符顺序有要求的字符串可能不适用。</P>
<P>得用正则表达式从URL地址中提取文件名的javascript程序，如下结果为page1</P>
<P>s="<A href="http://www.9499.net/page1.htm">http://www.9499.net/page1.htm</A>"<BR>s=s.replace(/(.*\/){0,}([^\.]+).*/ig,"$2")<BR>alert(s)</P>
<P>利用正则表达式限制网页表单里的文本框输入内容：</P>
<P>用正则表达式限制只能输入中文：onkeyup="value=value.replace(/[^\u4E00-\u9FA5]/g,'')" onbeforepaste="clipboardData.setData('text',clipboardData.getData('text').replace(/[^\u4E00-\u9FA5]/g,''))"</P>
<P>用正则表达式限制只能输入全角字符： onkeyup="value=value.replace(/[^\uFF00-\uFFFF]/g,'')" onbeforepaste="clipboardData.setData('text',clipboardData.getData('text').replace(/[^\uFF00-\uFFFF]/g,''))"</P>
<P>用正则表达式限制只能输入数字：onkeyup="value=value.replace(/[^\d]/g,'') "onbeforepaste="clipboardData.setData('text',clipboardData.getData('text').replace(/[^\d]/g,''))"</P>
<P>用正则表达式限制只能输入数字和英文：onkeyup="value=value.replace(/[\W]/g,'') "onbeforepaste="clipboardData.setData('text',clipboardData.getData('text').replace(/[^\d]/g,''))" </P>
<P>&nbsp;</P><img src ="http://www.cnitblog.com/oliver_yin/aggbug/2209.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/oliver_yin/" target="_blank">生活像一团麻</a> 2005-08-20 12:43 <a href="http://www.cnitblog.com/oliver_yin/articles/2209.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>个人使用VBScript5.6写的正则表达式的测试工具</title><link>http://www.cnitblog.com/oliver_yin/articles/2208.html</link><dc:creator>生活像一团麻</dc:creator><author>生活像一团麻</author><pubDate>Sat, 20 Aug 2005 04:15:00 GMT</pubDate><guid>http://www.cnitblog.com/oliver_yin/articles/2208.html</guid><wfw:comment>http://www.cnitblog.com/oliver_yin/comments/2208.html</wfw:comment><comments>http://www.cnitblog.com/oliver_yin/articles/2208.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/oliver_yin/comments/commentRss/2208.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/oliver_yin/services/trackbacks/2208.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 要正确运行本工具, 需事先要安装vbScript5.6 + VB开发工具.整个文件可以通过下面的链接下载:http://www.cnitblog.com/Files/oliver_yin/RegualTool.rar工程文件: PrjRegualExpressTool.vbpType=ExeReference=*\G{00020430-0000-0000-C000-000000000046}#2....&nbsp;&nbsp;<a href='http://www.cnitblog.com/oliver_yin/articles/2208.html'>阅读全文</a><img src ="http://www.cnitblog.com/oliver_yin/aggbug/2208.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/oliver_yin/" target="_blank">生活像一团麻</a> 2005-08-20 12:15 <a href="http://www.cnitblog.com/oliver_yin/articles/2208.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>正则表达式在论坛上的运用</title><link>http://www.cnitblog.com/oliver_yin/articles/2207.html</link><dc:creator>生活像一团麻</dc:creator><author>生活像一团麻</author><pubDate>Sat, 20 Aug 2005 03:56:00 GMT</pubDate><guid>http://www.cnitblog.com/oliver_yin/articles/2207.html</guid><wfw:comment>http://www.cnitblog.com/oliver_yin/comments/2207.html</wfw:comment><comments>http://www.cnitblog.com/oliver_yin/articles/2207.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/oliver_yin/comments/commentRss/2207.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/oliver_yin/services/trackbacks/2207.html</trackback:ping><description><![CDATA[一、读者指引<BR><BR>　　读者指引帮助你掌握本文的梗概。以免你看了大半才明白这编文章不适合你，给你造成视觉污染。<BR>　　如果你正在用ＡＳＰ写程序，或者你正在写一些诸如ＢＢＳ、留言溥或表单数据检查之类的东东那就值得一看。<BR><BR>　　如果你对正则表达式已经了如指掌，那么你不必一行行的看，只要看看我写的模板，再比较一下，取其精华就行了。<BR>　　如果你还是第一次接触正则表达式，那么你最好一行行的看，并逐条试验<BR><BR>　　当你熟练的掌握了正则表达式的用法，你就会发现其乐无穷。<BR><BR>二、正则表达式的概念<BR><BR>　　什么是ＵＢＢ代码？什么是正则表达式？<BR><BR>　　ＵＢＢ代码是HTML的一个变种。一般情况下，ＵＢＢ论坛不允许你使用ＨＴＭＬ代码，而只能用ＵＢＢ代码替代ＨＴＭＬ代码。<BR>　　ＵＢＢ代码是一套由流行的ＵＢＢ标签组成了固定代码，代码有统一的格式。用户只要遵循代码规则就可以实现用户想要的功能。如：<BR>　　想要显示粗体的how are you 字样，就应该输入<B> how are you</B>而不是输入&lt;b&gt;how are you&lt;/b&gt;<BR><BR>　　你也许会问：ＡＳＰ是怎样把<B> how are you</B>转换为&lt;b&gt;how are you&lt;/b&gt;的呢？<BR>　　回答这个问题就是：用正则表达式。<BR><BR>三、正则表达式的用途<BR><BR>有时我们在制作网站表单数据处理的时候（尤其是ＵＢＢ论坛）,都需要进行数据验证和字符串替代，特别是ＵＢＢ论坛要进行大量的数据安全性和字符串替代<BR><BR>邮于一般的论坛不支持HTML语法这就使得用户不能修改字体，不能贴图等等一些功能。这样使得论坛失去了吸引用户的一个强有力的途径。可能说一个强大的论坛在吸引用户数量上还是很重要的。这样就出现了一个ＵＢＢ解决方案，即在论坛不支持HTML语法的情况下用户仍然可以定制自已贴子的样式，贴图，增加链接，转贴网页等等诸多的功能，可能达到支持HTML语法同样的效果，而且这样可以使得论坛相对于HTML的论坛安全性大大提高。用户基本不能对论坛过行任何恶意攻击。<BR><BR>四、正则表达式的语法规则和标记<BR><BR>　　现在我们正式进入则表达式的学习，我会根据实例结合讲解正则表达式的用法，看完后你就会觉得写ＵＢＢ代码如此简单了，只要你一步一步的跟着我学 看完本文章后你就成为ＵＢＢ高手了。激动人心的就是你能写出自已的ＵＢＢ标签来了，再也不用到别人那里去拷贝现成的代码和模板了。 还好VBScritp5.0给我们提供了“正则表达式”对象，只要你的服务器安装了IE5.x，就可以运行了.<BR><BR>　　字符描述：<BR><BR>　　^符号匹配字符串的开头。例如：<BR>　　　　^abc　与“abc xyz”匹配，而不与“xyz abc”匹配<BR><BR>　　$符号匹配字符串的结尾。例如：<BR>　　　　abc$　与“xyz abc”匹配，而不与“abc xyz”匹配。<BR>　　　　注意：如果同时使用^符号和$符号，将进行精确匹配。例如：<BR>　　　　　　　^abc$　只与“abc”匹配　　　<BR><BR>　　*符号匹配０个或多个前面的字符。例如：<BR>　　　　ab*　可以匹配“ab”、“abb”、“abbb”等<BR><BR>　　+符号匹配至少一个前面的字符。例如：<BR>　　　　ab+　可以匹配“abb”、“abbb”等，但不匹配“ab”。 <BR><BR>　　?符号匹配０个或１个前面的字符。例如：<BR>　　　　ab?c?　可以且只能匹配“abc”、“abbc”、“abcc”和“abbcc”<BR><BR>　　.符号匹配除换行符以外的任何字符。例如：<BR>　　　　(.)+　匹配除换行符以外的所有字符串<BR><BR>　　x|y匹配“x”或“y”。例如：<BR>　　　　abc|xyz　可匹配 “abc”或 “xyz”，而“ab(c|x)yz”匹配 “abcyz”和“abxyz”<BR><BR>　　{n}匹配恰好n次（n为非负整数）前面的字符。例如：<BR>　　　　a{2}　可以匹配“aa“，但不匹配“a”<BR><BR>　　{n,}匹配至少n次（n为非负整数）前面的字符。例如：<BR>　　　　a{3,}　匹配“aaa”、“aaaa”等，但不匹配“a”和“aa”。<BR>　　　　注意：a{1,}等价于a+<BR>　　　　　　　a{0,}等价于a*<BR><BR>　　{m,n}匹配至少m个，至多n个前面的字符。例如：<BR>　　　　a{1,3}　只匹配“a”、“aa”和“aaa”。<BR>　　　　注意：a{0,1}等价于a?<BR><BR>　　[xyz]表示一个字符集，匹配括号中字符的其中之一。例如：<BR>　　　　[abc]　匹配“a”、“b”和“c”<BR><BR>　　[^xyz]表示一个否定的字符集。匹配不在此括号中的任何字符。例如：<BR>　　　　[^abc]　可以匹配除“a”、“b”和“c”之外的任何字符<BR><BR>　　[a-z]表示某个范围内的字符，匹配指定区间内的任何字符。例如：<BR>　　　　[a-z]　匹配从“a”到“z”之间的任何一个小写字母字符<BR><BR>　　[^m-n]表示某个范围之外的字符，匹配不在指定范围内的字符。例如：<BR>　　　　[m-n]　匹配除从“m”到“n”之间的任何字符<BR><BR>　　\符号是转义操作符。例如：<BR>　　　　\n　换行符<BR>　　　　\f　分页符<BR>　　　　\r　回车<BR>　　　　\t　制表符<BR>　　　　\v　垂直制表符<BR><BR>　　　　\\　匹配“\”<BR>　　　　\/　匹配“/”<BR><BR>　　　　\s　任何白字符，包括空格、制表符、分页符等。等价于“[ \f\n\r\t\v]”<BR>　　　　\S　任何非空白的字符。等价于“^\f\n\r\t\v]”<BR>　　　　\w　任何单词字符，包括字母和下划线。等价于“[A-Za-z0-9_]”<BR>　　　　\W　任何非单词字符。等价于“[^A-Za-z0-9_]”<BR><BR>　　　　\b匹配单词的结尾。例如：<BR>　　　　　　ve\b　匹配单词“love”等，但不匹配“very”、“even”等<BR><BR>　　　　\B匹配单词的开头。例如：<BR>　　　　　　ve\B　匹配单词“very”等，但不匹配“love”等<BR><BR>　　　　\d匹配一个数字字符，等价于[0-9]。例如：<BR>　　　　　　abc\dxyz　匹配“abc2xyz”、“abc4xyz”等，但不匹配“abcaxyz”、“abc-xyz”等<BR><BR>　　　　\D匹配一个非数字字符，等价于[^0-9]。例如：<BR>　　　　　　abc\Dxyz　匹配“abcaxyz”、“abc-xyz”等，但不匹配“abc2xyz”、“abc4xyz”等<BR><BR>　　　　\NUM匹配NUM个（其中NUM为一个正整数），引用回到记住的匹配。例如：<BR>　　　　　　(.)\1　匹配两个连续相同的字符。 <BR><BR>　　　　\oNUM匹配n（其中n为一个小于２５６的八进制换码值）。例如：<BR>　　　　　　\o011　匹配制表符<BR><BR>　　　　\xNUM匹配NUM（其中NUM为一个小于２５６的十六进制换码值）。例如：<BR>　　　　　　\x41　匹配字符“A”<BR><BR><BR>五、实例分析<BR><BR>1）在字符串中精确查找链接地址<BR><BR>((http|https|ftp):(\/\/|\\\\)((\w)+[.]){1,}(net|com|cn|org|cc|tv|[0-9]{1,3})(((\/[\~]*|\\[\~]*)<BR>(\w)+)|[.](\w)+)*(((([?](\w)+){1}[=]*))*((\w)+){1}([\&amp;](\w)+[\=](\w)+)*)*)<BR><BR>我们知道，链接地址一般以http或者https或者ftp等形式出现。初步总结一下就是，链接地址必须符合如下条件：<BR><BR>条件１<BR>　以http://或者https://或者ftp://等开头（当然还有其它形式，这里只列出主要的）<BR><BR>条件２<BR>　http://后面必须跟一个单词字符，紧接着单词字符后面的是"."（这样的组合必须出现一次或多次）。紧跟着“.”后面的是域名后缀（如net或者com或者cn等，如果是以IP地址的形式出现就可以是数字）<BR><BR>条件３<BR>　出现完整的链接地址后，还可以出现下一级或者更多级的目录（还要注意个人主页的地址有可能出现"~"符号）<BR><BR>条件４<BR>　链接地址末尾可以带参数。如典型的页数?PageNo=2&amp;action=display等<BR><BR>现在我们用下面的代码来逐个匹配上面的条件——<BR><BR>1、((http|https|ftp):(\/\/|\\\\) 满足条件１<BR>表示http:// http:\\ https:// https:\\ ftp:// ftp:\\都匹配（在这里考虑了某些用户可能把"//"输成“\\”的易发性错误）<BR>注意："|"表示“或者”，"\"是转义字符。“\/\/”表示"//"，“\\\\”表示"\\"<BR><BR>2、((\w)+[.]){1,}(net|com|cn|org|cc|tv|[0-9]{1,3}) 满足条件２<BR>“((\w)+[.]){1,}”表示一个单词字符加一个点号可以出现1次或者多次（这里考虑了某些用户喜欢省略www而将http://www.w3c.com写成http://w3c.com）<BR>“(net|com|cn|org|cc|tv|[0-9]{1,3})”表示必须要以net或者com或者cn或者org或者cc或者tv或者三位以下的数字结束<BR>[0-9]{1,3}表示三位以下的数字，因为ip地址的任何段不能超过255<BR><BR>3、(((\/[\~]*|\\[\~]*)(\w)+)|[.](\w)+)* 满足条件３<BR>“(\/[\~]*|\\[\~]*)”表示可以出现"/~"或者是"\~"，（其中“[\~]*”表示 ~ 可以出现也可以不出现），因为不是每个链接地址都有下一级目录<BR>“(\w)+)|[.](\w)+)”表示必须出现一个单词字符（即目录或者是一个带有扩展名的文件）<BR>注意：最后还有一个“*”表示上面括号内的可以出现也可以不出现，否则就只能匹配有下一级目录的链接地址了。<BR><BR>4、(((([?](\w)+){1}[=]*))*((\w)+){1}([\&amp;](\w)+[\=](\w)+)*)*)满足条件４<BR>“((([?](\w)+){1}[=]*))*((\w)+){1}”表示形如"?PageNo=2"的字符串可以出现也可以不出现，如果出现则只能出现一次（因为不可能有两个“？”号出现）。<BR><BR>“([\&amp;](\w)+[\=](\w)+)*)”表示形如“&amp;action=display”的字符串可以出现也可以不出现（因为并不是每个网页都带有两个以上的参数。<BR><BR>整个“((([?](\w)+){1}[=]*))*((\w)+){1}([\&amp;](\w)+[\=](\w)+)*)*”表示形如“?PageNo=2&amp;action=display”的字符串可以出现也可以不出现（即链接地址可以有参数也可以没有参数）<BR><BR><BR>把上面的组合起来，我们就可以匹配一个比较全面的链接地址了。比用简单的“(http:\/\/\S+)”来匹配一个链接地址要好，读者可以自行行测试比较。当然，这段代码还有很多不足之处，希望大家能够继续改进。<BR><BR>2）替代典型的UBB标签:<B>[/b]<BR>我们的目的就是要把[b]</B>成对的替换成&lt;b&gt;&lt;/b&gt;下面来看我们实现它的模板<BR>　　(\[b\])(.+)(\[\/b\])<BR>这里用了"(.+)"来配匹<B>到</B>之间的整个字符串，在替代的时候我们要写成这样<BR>　　str=checkexp(re,str,"&lt;b&gt;$2&lt;/b&gt;")<BR>（注意：checkexp是我自定义的函数，将在后面给出。这个函数将把<B>[/b]按照我们提供的模板进行替代。）<BR><BR>也许你会问这里出现一个"$2"是什么东东,呵注意了这个$2可是很重要的，它代表了"(.+)"所配匹的整个字符串。<BR>为什么是$2而不是$1、$3呢？因为$1代表(\[b\])所匹配的"[b]"字符串，$3代表(\[\/b\])所匹配的"</B>"字符串，显然这里我们需要的是$2而不是$1$3。<BR><BR><BR>六）ＵＢＢ正则表达模板实例<BR>下面是我写的一个ＵＢＢ函数，这个函数基本上能使你的论坛成为一个优秀的ＵＢＢ代码论坛了。当然，通过改进后，你可以得到一个更强大的ＵＢＢ论坛。<BR><BR>Function ReThestr(face,str)<BR>　dim re,str<BR><BR>　re="\&gt;"<BR>　str=checkexp(re,str,"&amp;gt;")<BR><BR>　re="\&lt;"<BR>　str=checkexp(re,str,"&amp;lt;")<BR><BR>　re="\n\r\n/"<BR>　str=checkexp(re,str,"&lt;P&gt;")<BR><BR>　re=chr(32)<BR>　str=checkexp(re,str,"&amp;nbsp;") <BR><BR>　re="\r"<BR>　str=checkexp(re,str," ")<BR><BR>　re="\[img\]((http:(\/\/|\\\\)){1}((\w)+[.]){1,3}(net|com|cn|org|cc|tv)(((\/[\~]*|\\[\~]*)<BR>(\w)+)|[.](\w)+)*(\w)+[.]{1}(gif|jpg|png))\[\/img\]" '查找图片地址<BR>　str=checkexp(re,str," &lt;img src='$1'&gt; ")<BR><BR>　re="\[w\](http:(\/\/|\\\\)((\w)+[.]){1,}(net|com|cn|org|cc|tv)(((\/[\~]*|\\[\~]*)(\w)+)|[.](\w)+)*<BR>(((([?](\w)+){1}[=]*))*((\w)+){1}([\&amp;](\w)+[\=](\w)+)*)*)\[\/w\]" '查找帧地址<BR>　str=checkexp(re,str,"&lt;iframe width='300' height='300' src='$1'&gt;&lt;/iframe&gt;")<BR><BR>　re="([^('&gt;)])(&lt;br&gt;)*((http|https|ftp):(\/\/|\\\\)((\w)+[.]){1,}(net|com|cn|org|cc|tv|([0-9]{1,3}))(((\/[\~]*|\\[\~]*)(\w)+)|[.](\w)+)*(((([?](\w)+){1}[=]*))*((\w)+){1}([\&amp;](\w)+[\=](\w)+)*)*)" '查找链接地址<BR>　str=checkexp(re,str,"$1$2 &lt;a href='$3' target=_blank&gt;$3&lt;/a&gt; ")<BR><BR>　re="([^(http://|http:\\)])((www|cn)[.](\w)+[.]{1,}(net|com|cn|org|cc)(((\/[\~]*|\\[\~]*)(\w)+)|[.](\w)+)*<BR>(((([?](\w)+){1}[=]*))*((\w)+){1}([\&amp;](\w)+[\=](\w)+)*)*)" '查找不以http://开头的地址<BR>　str=checkexp(re,str,"$1 &lt;a href='http://$2' target=_blank&gt;$2&lt;/a&gt; ")<BR><BR>　re="([^(=)])((\w)+[@]{1}((\w)+[.]){1,3}(\w)+)" '查找邮件地址<BR>　str=checkexp(re,str," &lt;a href='mailto:$2'&gt;$2&lt;/a&gt; ")<BR><BR>　re="\<FONT color=#000000>[0-F]{6})\]((.)+)\[\/color\]" '替换字体色彩<BR>　str=checkexp(re,str,"&lt;font color='$1'&gt;$4&lt;/font&gt;")<BR><BR>　re="\[size=([0-9]{1})\]((.)+)\[\/size\]" '替换字体大小<BR>　str=checkexp(re,str,"&lt;font size='$1'&gt;$2&lt;/font&gt;")<BR><BR>　re="\<FONT face=((.)+){1,3}\>((.)+)\[\/font\]" '替换字体<BR>　str=checkexp(re,str,"&lt;font face='$1'&gt;$3&lt;/font&gt;")<BR><BR>　re="(\[b\])(.+)(\[\/b\])" '加粗字体<BR>　str=checkexp(re,str,"&lt;b&gt;$2&lt;/b&gt;")<BR><BR>　re="(\[u\])(.+)(\[\/u\])" '下画线<BR>　str=checkexp(re,str,"&lt;u&gt;$2&lt;/u&gt;")<BR><BR>　re="(\[li\])(.+)(\[\/li\])" '列表<BR>　str=checkexp(re,str,"&lt;li&gt;$2&lt;/li&gt;")<BR><BR>　re="(\[QUOTE\])(.+)(\[\/QUOTE\])" '引用<BR>　str=checkexp(re,str,"&lt;BLOCKQUOTE&gt;引用:&lt;HR SIZE=1&gt;$2&lt;HR SIZE=1&gt;&lt;/BLOCKQUOTE&gt;")<BR><BR>　re="\[email=((\w)+[@]{1}((\w)+[.]){1,3}(\w)+)\](.+)(\[\/email\])" '邮件<BR>　str=checkexp(re,str,"&lt;a href=mailto:$1&gt;$6&lt;/a&gt;")<BR><BR>　re="(\[center\])(.+)(\[\/center\])" '居中<BR>　str=checkexp(re,str,"&lt;center&gt;$2&lt;/center&gt;")<BR><BR>　re="fuck"<BR>　str=checkexp(re,str,"***")<BR><BR>　re="操"<BR>　str=checkexp(re,str,"***")<BR><BR>　re="sex"<BR>　str=checkexp(re,str,"***") <BR><BR>　re="TMD"<BR>　str=checkexp(re,str,"***")<BR><BR>　re="shit"<BR>　str=checkexp(re,str,"***")<BR><BR>　ReThestr=str<BR>end function<BR><BR>ＵＢＢ代码如下：<BR><B></B>[i] [/i] [u] [/u] 
<CENTER></CENTER>[url] [/url] [email=] [/email] [img] [/img] <BR></FONT></FONT><img src ="http://www.cnitblog.com/oliver_yin/aggbug/2207.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/oliver_yin/" target="_blank">生活像一团麻</a> 2005-08-20 11:56 <a href="http://www.cnitblog.com/oliver_yin/articles/2207.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>