javacc jjtree 写法 以及 jj写法 基本语法 以及应用
2012-2-7阅读2279 评论0
/***********************************************************/ >我使用的测试jjt,jj文件来自于javacc5.0版本 >dir_hier/javacc-5.0/javacc-5.0/examples/ JJTreeExamples SimpleExamples /***********************************************************/ /***********************************************************/ 0.javacc(the java Compiler Compiler)概要 >Abstract summary: javacc一般用于编写某种语言的书写规则,如java的语法规则 变量必须以字母或者$开头或者以数字结尾等,这是一种书写规范, 书写规范是这门语言特有的,他必须满足,那么他是怎么去判断 书写的字符窜符合了规则了,那就需要进行验证,javacc就是 一种用于定义某种特定输入格式字符窜规则的工具,也是验证这种 特定格式字符窜的验证者。定义字符窜输入格式,以及验证输入的 字符窜是javacc的最基本作用。 javacc语法规则描述文件的书写方式有两种一种是一种是.jj文件, 另外一种是.jjt文件,两种文件的书写方式差异性不大,但是jjt文件 比jj文件表达语法规则更加容易,jj文件的可选参数项和jjt有很大的 不同,这是他们最大的区别。 /***********************************************************/ /***********************************************************/ 1.javacc语法规则书写操作基本代码 >如何在生产表达式中写条件结构 ( term = < TERM > | term = < STAR > | term = < PREFIXTERM > | term = < WILDTERM > | term = < NUMBER > ) >生成后的代码对应到 分析: 因为在javacc中(<A> | <B>)被表示成了在两个规则中任意选择一个的意思。 switch(tokenkind){ case TERM: /** doSomthing...*/ break; case STAR: /** doSomthing...*/ break; ... } void ifExp():{int k=0;/** java局部变量声明处*/}{ {/*在{}中写任意的java代码并且是以面向对象的方式*/} ( { if(k==2){break;} System.out.println("k="+k); k++; } )* } >如何在生产表达式中写循环结构 ->( { if(k==2){break;} System.out.println("k="+k); k++; } ->)* >生成对应的代码 while(true){ if(k==2){ break; } System.out.println("k="+k); k++; } >在生产表达式中调用其他的生产表达式 对应到java中的方法调用 void B(): { StringBuffer sb = new StringBuffer(); Token t = null; }{ t = A /** 这里对应生成的java代码t=jj_consume_token(A); * 因为等于就是要消费一个记号把消费的这记号的应用给t变量 */ } void A():{}{} >javacc中的词法状态介绍 <*>TOKEN:{} <DEFAULT>TOKEN:{<A:"2001 TO 2002">:DateRange | <B:"anything"> } <DateRange>TOKEN:{} 解析:<*>,<DEFAULT>,<DateRange>是词法状态 定义词法状态的好处是,如果在生产表达式中 正好有一个字符窜匹配到了2001 TO 2002这个字符窜 那么他就会马上把下一个要匹配的模式字符窜定义 为DateRange这种词法状态中定义的Token >有了循环有了条件结构有了定义变量的方式 javacc语法文件就相当完美了。 /***********************************************************/ /***********************************************************/ 2.JJT语法规则描述文件书写方法 >jjt概要 jjt能够很清晰的表达出语法分析的思路, 并且他把每一个生产表达式都作为一个节点来表示, 将这些节点的执行顺序有效的组织成了一颗语法分析树 >.options >BUILD_NODE_FILES (default: true) 为SimpleNode以及语法中使用的其它节点创建样本实现。 >MULTI(default: false) 创建多模式解析树。 此选项默认为False,生成一个单一模式解析树。 >NODE_DEFAULT_VOID (default: false) 此选项设置为True时, 不在使每个非包装产生式定义一个节点,取而代之为空。 >NODE_FACTORY (default: false) 用下面的方式使用一个工厂方法创建一个节点: public static Node jjtCreate(int id) >NODE_PACKAGE (default: "") 被放进生成节点类里的包。默认为解析器的包。 >NODE_PREFIX (default: "AST") AST意思为抽象节点 在多模式中,前缀用来从节点标志符构造节点类名字。 默认前缀为” AST” >NODE_SCOPE_HOOK (default: false) 在节点作用域入口和出口处插入调用用户自定义的解析方法。 在节点的生命开始和结束之前需要调用的方法被成为钩子。 参见:节点作用域钩子。 >NODE_USES_PARSER (default: false) 是否将当前解析器对象也出入到节点对象的属性中 JJTree会使用一个选择的形式将解析对象传给构造函数。例如: public static Node MyNode.jjtCreate(MyParser p, int id); MyNode(MyParser p, int id); >STATIC (default: true) 为静态解析器生成代码。 选项默认为True。 这必须一致的通过等效的JavaCC选项被使用。 选项的值发布于JavaCC的源码中。 >VISITOR (default: false) 在节点类中插入jjtAccept()方法, 为语法中使用的每个节点类型产生一个访问者实现。 >VISITOR_EXCEPTION (default: "") 如果这个选项被设置,它将使用jjtAccept()和visit()方法的形式。 注意:这个选项将会在以后的某个JJTree版本中删除。如果不影响你请不要使用它。 >JJTREE_OUTPUT_DIRECTORY (default: use value of OUTPUT_DIRECTORY) 默认情况下,在全局OUTPUT_DIRECTORY设置中指定JJTree生成的输出目录。 明确的设置这个选项允许用户从树文件中分离解析器。 /***********************************************************/ /***********************************************************/ 3.语法分析树节点(Node) >javacc把每一个生产表达式都看作为一个简单节点(SimpleNode)在默认 MULTI(default: false)的情况下,javacc已经提供了这个SimpleNode类的简单实现。 >MULTI(default: true)将为每一个节点都按照[NODE_PREFIX_生产表达式的名字]这样 一种形式来提供默认的简单实现类。 >定义节点的方式 明确定义:一个以指定子节点数创建的节点 void AdditiveExpression() #void : {} { ( MultiplicativeExpression() ( ( "+" | "-" )MultiplicativeExpression() )* )#Add(3) } >语法分析树参考结果: Add Integer Integer Integer >#void不会为这个生产表达式生成对应的节点 >#Add表示在循环执行过程中生成的所有节点中的前三个节点作为 以Add为命名的儿子节点。 按照条件定义: void AdditiveExpression() #void : {} { ( MultiplicativeExpression() ( ( "+" | "-" ) MultiplicativeExpression() )* ) #Add(>3) } >#Add(>1)另外一种写法#Add(jjtree.arity() > 1) 生成的源码: jjtree.closeNodeScope(jjtn001, jjtree.nodeArity() > 1); 在执行循环过程中创建的节点个数大于1的话就会将所有的节点 做为Add的子节点然后将Add节点添加到堆栈中,如果条件不满足 那么创建的所有节点将会保留在栈中默认会在做为上一个节点的 子节点 >语法分析树参考结果: Start /** 因为在执行循环过程中创建的节点数没有达到指定的个数所以是start*/ Integer Integer >notes: ( ... ) #N ( a() ) 上面表达式逻辑不清, 你必须明确的使用条件式: ( ... ) #N(true) ( a() ) >为产生式的节点指定名称 void ProductExp() #MyNode:{} {//doAnything} #MyNode为这个生产表达式所对应的节点的名称 >特殊应用 void P3():{} { P4() ( P5() )+ #ListOfP5s P6() } #name=jjtree.closeNodeScope(jjtn001, true); #name(3)=jjtree.closeNodeScope(jjtn001, 3); #name(>3)=jjtree.closeNodeScope(jjtn001, jjtree.nodeArity() > 1); >什么是jjThis ASTStart Start() : {} { Expression() ";" { return jjtThis; } } 分析: jjThis代表了当前节点的引用地址, 就好像ASTStart Start()这个生产表达式对应到Start这个 节点那么jjtThis就指向了这个对象, 在其内部的实现代码为: ASTStart jjtn000 = new ASTStart(JJTSTART); >什么是NODE_SCOPE_HOOK="true"(默认为true表示有钩子) 这个名气叫的很有味,钩子,正好是在某个节点被创建的 时候调用指定的钩子方法,在节点添加到栈的时候要调用的方法 钩子在open的时候可以做一些预备工作,结束可以做一些后续工作 示例代码: static final public void AdditiveExpression() throws ParseException { ASTAdd jjtn001 = new ASTAdd(JJTADD);// 创建节点 boolean jjtc001 = true; jjtree.openNodeScope(jjtn001); // 这就是钩子 try { //do something... } finally { if (jjtc001) { jjtree.closeNodeScope(jjtn001, 3);// 这就是钩子 } } } /***********************************************************/ 4.jjt文件生成的文件解析 SimpleQueryParser.jjt SimpleQueryParser.jj JJTSimpleQueryParserState.java Node.java ParseException.java SimpleCharStream.java SimpleNode.java SimpleQueryParser.java SimpleQueryParserConstants.java SimpleQueryParserTokenManager.java SimpleQueryParserTreeConstants.java Token.java TokenMgrError.java >jjt文件可以生成.jj文件 >SimpleQueryParser这个名字在很多类名中都以重复的字眼出现,这是通过 Parser_Begin(SimpleQueryParser)...Parser_End(SimpleQueryParser) 指定的。 >JJTSimpleQueryParserState.java用于记录节点的组织情况 这个类有单独的说明,在JJTSimpleQueryParserState.java 中,他是一个以堆栈形式设计的类。 >Node.java是一个接口jjt中节点必须实现他,在选项MULTI(default: false) 的时候javacc编译器已经帮我们简单的做了实现这个类为SimpleNode.java。 >ParseException.java在语法分析的时候遇到不符合规则的字符时抛出的异常 >SimpleQueryParserConstants.java这个类主要用于关联定义的Token >SimpleQueryParserTreeConstants.java主要用于关联定义的节点 如果没有给生产表达式命名那么他默认就是用生产表达式的名字做为节点的 名字 >SimpleQueryParserTokenManager.java这是最重要的类他用于生产Token对象 在getNextToken()中体现了这么一点 >Token.java这个对象作为最小的记号单元用于封装SimpleQueryParserTokenManager 生成的记号 >SimpleQueryParser.java将类综合起来提供语法分析服务,他是消费Token对象的, 在jj_consume_Token(token_kind)中可以体现这一点,传入一个记号的类型返回这个 类型的一个token对象 >TokenMgrError.java在生产Token对象的过程中遇到错误 >SimpleCharStream.java用于封装输入的字符窜 >SimpleQueryParserTokenManager.java/**用于产生Token*/ SimpleQueryParserTokenManager.getNextToken()/** 生产Token的方法*/ /** 消费Token的SimpleQueryParser利用这些Token生成特定的逻辑对象*/ SimpleQueryParser.java SimpleQueryParser.jj_consume_token(int kind);/** 根据指定的类型消费一个token*/ SimpleQueryParser.jj_ntk()/** 如果当前jj_ntk变量为-1 说明当前token对像的下一个token没有找到需要接着要到tokenmanage里面 拿一个token出来作为当前token的next*/ /***********************************************************/ /***********************************************************/ 5.jj文件写法解析 >options >STATIC=false; 意思所有的生产表达式对应到java代码的时候 不是静态的 /***********************************************************/
时间: 2024-10-07 00:32:00