扯一下.jj文件的构成与基本语法
主要由四部分构成
options{}部分:这个部分对产生的语法分析器的特性进行说明,例如向前看的token的个数(用来解除冲突)。这一部分是可以省略的,因为每一个选项都有默认值,当我们没有对某个选项进行说明时,它就采用默认值。也可以把这些选项作为javacc命令的参数来启动javacc,可以达到同样的效果。
options { static = true; }
解析器类的声明:这个部分指定了分析器类的名字,以及其他类中成员的声明。这个部分是必须有的。这个部分的声明如下:
PARSER_BEGIN(MyNewGrammar) package com.pngfi.test;//指定生成代码存放的包名 public class MyNewGrammar { public static void main(String args []) throws ParseException { MyNewGrammar parser = new MyNewGrammar(System.in); while (true) { System.out.println("Reading from standard input..."); System.out.print("Enter an expression like \"1+(2+3)*4;\" :"); try { switch (MyNewGrammar.one_line()) { case 0 : System.out.println("OK."); break; case 1 : System.out.println("Goodbye."); break; default : break; } } catch (Exception e) { System.out.println("NOK."); System.out.println(e.getMessage()); MyNewGrammar.ReInit(System.in); } catch (Error e) { System.out.println("Oops."); System.out.println(e.getMessage()); break; } } } } PARSER_END(MyNewGrammar)
词法部分声明:这里面有四类:SKIP、TOKEN、SPECIAL_TOKEN、MORE。其中,SKIP用来说明被忽略的串,下面是一个例子:
SKIP : { " " | "\r" | "\t" | "\n" }
TOKEN用来说明在词法层次上识别的token,下面是一个例子:
TOKEN : /* OPERATORS */ { < PLUS : "+" > | < MINUS : "-" > | < MULTIPLY : "*" > | < DIVIDE : "/" > } TOKEN : { < CONSTANT : (< DIGIT >)+ > | < #DIGIT : [ "0"-"9" ] > }
在词法声明部分,以#开头的token只是在词法分析时使用,不能作为语法分析的输入,也就是说,它相对词法分析是局部的。
语法声明和动作代码:这一部分生成的代码会直接插入解析器类声明的结束括号之前。一般而言,语法中的每一个非终结符都对应一个规则,每一个规则都对应生成代码中的一个函数,其中规则的形式如下:
返回值类型 规则名():
{
变量声明和一些初始化的动作
}
{
上下文无关文法的右部分,其中每个组成部分的形式如下:
语法部分 {动作部分}
}
两个部分都可以省略。语法部分可以是一个字符串(简单的token常常可以这样处理),TOKEN中声明的token,或一个对某个非终结符对应的函数的调用。
JavaCC模板文件中自带的代码例子如下:int one_line() : {} { sum() ";" { return 0; } | ";" { return 1; } } void sum() : {} { term() ( ( < PLUS > | < MINUS > ) term() )* } void term() : {} { unary() ( ( < MULTIPLY > | < DIVIDE > ) unary() )* } void unary() : {} { < MINUS > element() | element() } void element() : {} { < CONSTANT > | "(" sum() ")" }
说完构成,说一下基本语法
[]:在词法(Token)部分表示其中的项中选择一个,在语法部分表示的内容是可选的
例如下面DIGIT表示0-9中的任意一个数字
TOKEN : /* 定义整数 和实数*/ { < DIGIT : [ "0"-"9" ]> }
在规则中表示匹配一个数字后面可以有分号,也可以没有分号
void sum() : {} { < DIGIT > [ ; ] }
+:前面的内容出现一次或多次。
表示匹配至少一个连续的数字,如99999,6,045等void sum() : {} { (< DIGIT >)+ }
*: 前面的内容出现0次或多次。
?:前面的内容出现0次或一次。
这两个和上面的类似-:前后构成的闭区间。
- ~:后面的内容的补。
|:或者
匹配阿拉伯字母大小写的任意一个TOKEN : /* 定义整数 和实数*/ { < LETTER: [ "a"-"z"|"A"-"Z" ]> }
():改变运算的优先级,把其中的内容作为一个整体。
表示匹配0个或多个数字字母的集合。比如1a2b3a4t, ,6y,7u8o等等
void sum() : {} { (< DIGIT >< LETTER >)* }