不知道大家了解Lucence这个项目么,一个开源的搜索引擎。不过想看懂很难,因为需要很多相关的知识,我这里就翻译了一篇文档,JavaCC技术手册之JJTree参考文档,Lucence在一些主要的文法分析中用到了JavaCC,而JJTree是JavaCC的一个预处理。下面就是翻译:
简洁<o:p></o:p>
JJTree在JavaCC源代码的不同地方嵌入语法树构建动作,可以理解为JavaCC的预处理工具。通过运行JavaCC创建解析器生成JJTree输出文件。这篇文档描述了如何使用JJTree,以及如何从中分离你的解析器。
JJTree默认为每个非终结符生成代码来创建语法分析树节点。这种行为可以被修改以便于一些非终结符不生成节点或者因为一部分内容扩充而生成一个节点。
JJTree已经定义了一个所有语法分析树节点必须实现的接口Node,这个接口定义诸如:设置节点的父节点、增加子节点、重新获得子节点 等操作方法。
JJTree(为想要更多条件)可以设置 “simple” 和 “multi”两种模式之一。在“simple”[单一]模式下语法分析树节点都是“SimpleNode”这个具体类型。而在“multi”[多个]模式下语法分析树节点类型取决于节点的名字。如果你不为“Node”接口提供实现JJTree会为你生成一些基于“SimpleNode”的样品实现,你可以修改这个实现以适应需求。
虽然JavaCC是一个从顶至下的解析器(即LL(K) 通常我们只要用LL(1)即可 ),但是JJTree构造语法树过程却是从底至上构建的。为此使用堆栈,当他们被创建后它可以把节点压入堆栈中。当它找到一个父节点时,从堆栈中弹出子节点并且将其添加到父节点下,最后把新的父节点压入堆栈。堆栈是开放的意味着你可以使用内部语法行为对其访问:入堆栈,出堆栈,另外不管你有多么的适应请熟练使用它的内容。更多的重要信息请参看下面“节点作用域和用户行为”章节的介绍。
JJTree提供2种基本节点种类定义,可以依据造句法的缩写使其使用起来更方便。
1、 明确节点:一个以指定子节点数创建的节点。其中许多节点可以被弹出堆栈成为新节点的子节点,然后把这个新节点压入栈。你可以像这样定义一个明确节点:
#ADefiniteNode(INTEGER EXPRESSION)<o:p></o:p>
虽然INTEGER是目前为止使用最多的表达式,但明确节点参数“EXPRESSION”可以使用任何INTEGER 表达式。
2、 条件节点:当且仅当条件值为True时,带着所有被压入节点区域内部的子节点创建条件节点。如果条件值为False节点不会被创建,所有的子节点将残留在节点堆栈中。你可以像这样定义一个条件节点:
#ConditionalNode(BOOLEAN EXPRESSION)
一个条件节点描述符“EXPRESSION
”可以使用任何BOOLEAN
表达式。下边有2个常用的条件节点内容需要记忆:
1、不确定节点
#IndefiniteNode
是 #IndefiniteNode(true) 的简写
2、大节点
#GTNode(>1)
是 #GTNode(jjtree.arity() > 1) 的简写
在JJTree源代码中,当简写的不确定节点表达跟着一个括号表达式时,可能导致歧义。在那种情况下简写必须用完全写法替代。例如:
( ... ) #N ( a() )
上面表达式逻辑不清,你必须明确的使用条件式:
( ... ) #N(true) ( a() )
注意:节点描述符表达式不能有副作用,JJTree并没有指定表达式将被计算多少次。
默认情况下JJTree把每一个非终结符作为一个不确定节点对待,从它的产生式的名
字衍生出节点名字。也可以用下面的语法给他一个不同的名字:
void P1() #MyNode : { ... } { ... }
当解析器识别出了一个P1非终结符,它先定义一个节点,并在堆栈中进行标记,以便于一些由扩展P1非终结符而构建和入栈的分析树节点被弹出并成为 MyNode节点的子节点。
如果想要使某个产生式禁止创建节点,你可以使用下面的语法:
void P2() #void : { ... } { ... }
这样,一些根据非终结符入栈作为P2扩展的语法分析树节点会留在堆栈中,会被将来的产生式弹出作为其子节点,对
non-decorated[非包装]
节点
你也可以通过使用NODE_DEFAULT_VOID
选项使之成为默认行为。
void P3() : {}
{
P4() ( P5() )+ P6()
}
这个例子中,一个不确定的节点P3被启动并标记堆栈,然后是1个P4节点,1个或多个P5节点和1个P6节点被解析。被入堆栈的任何节点都会被弹出成为P3的子节点。你可以更深一步的定制生成树:
void P3() : {}
{
P4() ( P5() )+ #ListOfP5s P6()
我认为这里是P4() ( P5() ) #ListOfP5s + P6()<o:p></o:p>
}
现在P3节点会有一个P4节点、一个ListOfP5s节点和一个P6节点作为子节点。#Name结构表示一个后缀操作符,它的区域直接就是前面的扩展单元。
<o:p> </o:p>
节点作用域和用户行为<o:p></o:p>
每个节点都有其作用域。这个作用域内的用户行为可以通过使用特殊标志符“jjtThis
”来访问已被创建的节点。这个标志符被隐含的声明为节点的正确类型,这样此节点的一些字段、方法可以轻易的被访问。
一个作用域就是前面一个节点定义的扩展单位。这可能是一个括号表达式。当产生式符号被定义(或者是隐含定义的默认节点),它的作用域就是包括声明块在内的整个产生式的右侧。
你可以使用一个包括“jjtThis”扩展引用左侧的表达式。例如:
... ( jjtThis.my_foo = foo() ) #Baz ...
这里“jjtThis”引用了一个含有“my_foo
”字段的“Baz
”节点。foo()
产生式解析的结果是被赋给“my_foo
”。
节点作用域内用户的最终行为不同于所有的其他行为。当代码执行过程中,子节点已经被从堆栈中弹出并被添加给这个被压入堆栈的节点。这些孩子节点可以通过诸如这个节点的方法jjtGetChild()
.进行访问。除了最终用户行为之外的用户行为仅能访问堆栈中的子节点。此节点的方法对那些没有被赋给这个节点的孩子节点是无效的。
一个条件计算结果为False的条件表达式条件节点既不能被得到以添加到堆栈中,其孩子节点也不能被添加。条件节点作用域内的最终用户行为可以通过调用方法nodeCreated()
来
决定此节点是否被创建。如果节点条件被满足、节点被创建、并且被压入堆栈的话结果返回True,否则返回False。
<o:p> </o:p>
异常处理<o:p></o:p>
一个由节点作用域内扩展抛出却没有在节点作用域内捕获的异常将由JJTree自行处理。当这种情况发生时,那些再节点作用域内被压入堆栈的节点会被弹出堆栈并被抛弃。之后一个异常会被重新抛出。
这样做的意图是使分析其可以进行错误校验并且使节点堆栈继续处于一个可知的状态。
注意:一般JJTree 不能识别出此异常是否由节点作用于内用户行为抛出的。这样的异常有可能被不正确的处理。
<o:p> </o:p>
节点范围钩子<o:p></o:p>
如果NODE_SCOPE_HOOK
选项被置为True,JJTree会在每个节点作用域的入口和出口处调用用户自定义的2个解析器方法。这些方法必须有下面的形式:
void jjtreeOpenNodeScope(Node n)
void jjtreeCloseNodeScope(Node n)
如果解析器是静态的,那么这些方法也必须被声明成静态的。他们都以当前节点作为被调用的参数。
一种用途是这些功能将用来存储节点的首尾标记符以便于输入可以很容易的被重现。例如:
void jjtreeOpenNodeScope(Node n)
{
((MySimpleNode)n).first_token = getToken(1);
}
<o:p> </o:p>
void jjtreeCloseNodeScope(Node n)
{
((MySimpleNode)n).last_token = getToken(0);
}
基于SimpleNode
的MySimpleNode类中有下面2个额外的字段:<o:p></o:p>
Token first_token, last_token;
另外一个用途是用于将解析器对象自身存储于节点以便于状态可以被解析器提供的所有节点所共享。
节点的生存周期<o:p></o:p>
一个节点的建立经历了一个很好被确定的序列步骤。下面是从节点自身的透视图观察的序列。
1、创建节点需要一个独特的整形参量。这个参数确定了节点的种类,这在单一模式中有其有用。JJTree自动生成了一个声明了有效常量的类 parserTreeConstants.java 。 常量的名字取决于JJT前缀加文件中节点名字的大写字符串。用字符“.”代替字符“_” 。为方便起见,在同一个文件中维护了一个被称为jjtNodeName[]
的字符串数组,它遍历了节点的未修改名字的常量。<o:p></o:p>
2、节点的方法jjtOpen()
被调用<o:p></o:p>
3
、如果NODE_SCOPE_HOOK选项被设置为True,那么用户自定义的方openNodeScope()将被调用并且以此节点为参数。这个方法可以初始化节点的字段或者调用节点的方法。例如,他可以存储节点的首标记符。<o:p></o:p>
4
、如果一个节点被解析时抛出了一个未捕获的异常,节点将被抛弃。
JJTree将不再对其进行引用。虽然用户自定义的节点作用域钩子closeNodeHook()
将不再调用此节点作为参数,但
节点不会被关闭。<o:p></o:p>
5
、另外,如果一个条件节点的条件计算值为False,节点将被抛弃。虽然用户自定义节点作用域钩子closeNodeHook()可能调用此节点作为参数,但节点不会被关闭。<o:p></o:p>
6
、另外,一个明确节点通过整形表达式制定的所有孩子节点或者在一个条件节点作用域内被压入堆栈的所有节点被赋给此节点。他们的添加次序并没有被确定。<o:p></o:p>
7
、节点的方法jjtClose()被调用。<o:p></o:p>
8
、节点被压入堆栈。<o:p></o:p>
9
、如果NODE_SCOPE_HOOK选项被设置为True,则用户自定义方法closenNodeScope()将被调用并且以此节点作为参数。<o:p></o:p>
10
、如果节点不是根节点,他将作为其他节点的字节点被添加并且它的jjtSetParent()方法被调用。
<o:p> </o:p>
访问者支持<o:p></o:p>
JJTree为访问者提供了一些基本的设计模式。如果VISITOR
选项被设置为True,
JJTree将会在生成所有节点类时插入jjtAccept()
方法,并且生成一个以此节点为参数而被实现的访问者接口。[public Object visit(具体Node类,Object)]
访问者接口的名字由解析器名字加Visitor来构造。每当JJTree运行时接口会被生成,以便于他可以准确地表现解析其所使用的那些节点。如果实现类不能被新节点更新将会产生编译时错误。只是一个特性。
<o:p> </o:p>
选项<o:p></o:p>
JJTree在命令行或者JavaCC选项声明中提供了下面这些选项:
BUILD_NODE_FILES
(default: true
)<o:p></o:p>
为SimpleNode以及语法中使用的其它节点创建样本实现。
MULTI (default: false) <o:p></o:p>
创建多模式解析树。此选项默认为False,生成一个单一模式解析树。
NODE_DEFAULT_VOID
(default: false
)<o:p></o:p>
此选项设置为True时,不在使每个非包装产生式定义一个节点,取而代之为空。
NODE_FACTORY
(default: false
)<o:p></o:p>
用下面的方式使用一个工厂方法创建一个节点:
public static Node jjtCreate(int id)<o:p></o:p>