JavaCC 学习笔记
<!-- /* Font Definitions */ @font-face {font-family:宋体; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-alt:SimSun; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 680460288 22 0 262145 0;} @font-face {font-family:黑体; panose-1:2 1 6 9 6 1 1 1 1 1; mso-font-alt:SimHei; mso-font-charset:134; mso-generic-font-family:modern; mso-font-pitch:fixed; mso-font-signature:-2147482945 953122042 22 0 262145 0;} @font-face {font-family:"Cambria Math"; panose-1:2 4 5 3 5 4 6 3 2 4; mso-font-charset:0; mso-generic-font-family:roman; mso-font-pitch:variable; mso-font-signature:-1610611985 1107304683 0 0 415 0;} @font-face {font-family:Cambria; panose-1:2 4 5 3 5 4 6 3 2 4; mso-font-charset:0; mso-generic-font-family:roman; mso-font-pitch:variable; mso-font-signature:-1610611985 1073741899 0 0 415 0;} @font-face {font-family:"/@宋体"; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 680460288 22 0 262145 0;} @font-face {font-family:"/@黑体"; panose-1:2 1 6 9 6 1 1 1 1 1; mso-font-charset:134; mso-generic-font-family:modern; mso-font-pitch:fixed; mso-font-signature:-2147482945 953122042 22 0 262145 0;} /* Style Definitions */ p.MsoNormal, li.MsoNormal, div.MsoNormal {mso-style-unhide:no; mso-style-qformat:yes; mso-style-parent:""; margin:0cm; margin-bottom:.0001pt; text-indent:24.1pt; mso-pagination:widow-orphan; tab-stops:45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt; font-size:12.0pt; font-family:"Courier New"; mso-fareast-font-family:宋体; mso-bidi-font-family:宋体;} h1 {mso-style-unhide:no; mso-style-qformat:yes; mso-style-link:"标题 1 Char"; mso-style-next:正文; margin-top:17.0pt; margin-right:0cm; margin-bottom:16.5pt; margin-left:21.0pt; text-indent:-21.0pt; line-height:240%; mso-pagination:widow-orphan lines-together; page-break-after:avoid; mso-outline-level:1; mso-list:l0 level1 lfo1; tab-stops:45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt; font-size:18.0pt; mso-bidi-font-size:12.0pt; font-family:"Courier New"; mso-fareast-font-family:黑体; mso-bidi-font-family:宋体; mso-font-kerning:22.0pt; font-weight:normal;} p.MsoHeader, li.MsoHeader, div.MsoHeader {mso-style-priority:99; mso-style-unhide:no; mso-style-link:"页眉 Char"; margin:0cm; margin-bottom:.0001pt; text-align:justify; text-justify:inter-ideograph; text-indent:24.1pt; mso-pagination:widow-orphan; tab-stops:45.8pt 91.6pt 137.4pt 183.2pt center 207.65pt left 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt right 415.3pt left 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt; layout-grid-mode:char; border:none; mso-border-alt:none windowtext 0cm; padding:0cm; mso-padding-alt:1.0pt 4.0pt 1.0pt 4.0pt; font-size:9.0pt; mso-bidi-font-size:12.0pt; font-family:"Times New Roman","serif"; mso-fareast-font-family:宋体; mso-bidi-font-family:宋体;} p.MsoFooter, li.MsoFooter, div.MsoFooter {mso-style-priority:99; mso-style-unhide:no; mso-style-link:"页脚 Char"; margin:0cm; margin-bottom:.0001pt; text-indent:24.1pt; mso-pagination:widow-orphan; tab-stops:45.8pt 91.6pt 137.4pt 183.2pt center 207.65pt left 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt right 415.3pt left 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt; layout-grid-mode:char; font-size:9.0pt; mso-bidi-font-size:12.0pt; font-family:"Courier New"; mso-fareast-font-family:宋体; mso-bidi-font-family:宋体;} p.MsoCaption, li.MsoCaption, div.MsoCaption {mso-style-unhide:no; mso-style-qformat:yes; mso-style-next:正文; margin:0cm; margin-bottom:.0001pt; text-indent:24.1pt; mso-pagination:widow-orphan; tab-stops:45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt; font-size:10.0pt; mso-bidi-font-size:12.0pt; font-family:"Cambria","serif"; mso-fareast-font-family:黑体; mso-bidi-font-family:宋体;} pre {mso-style-priority:99; mso-style-link:"HTML 预设格式 Char"; margin:0cm; margin-bottom:.0001pt; text-indent:24.1pt; mso-pagination:widow-orphan; font-size:12.0pt; font-family:宋体; mso-bidi-font-family:宋体;} span.1Char {mso-style-name:"标题 1 Char"; mso-style-unhide:no; mso-style-locked:yes; mso-style-link:"标题 1"; mso-ansi-font-size:18.0pt; mso-bidi-font-size:12.0pt; font-family:"Courier New"; mso-ascii-font-family:"Courier New"; mso-fareast-font-family:黑体; mso-hansi-font-family:"Courier New"; mso-bidi-font-family:宋体; mso-font-kerning:22.0pt;} span.Char {mso-style-name:"页脚 Char"; mso-style-priority:99; mso-style-unhide:no; mso-style-locked:yes; mso-style-link:页脚; mso-ansi-font-size:9.0pt; mso-bidi-font-size:12.0pt; font-family:"Courier New"; mso-ascii-font-family:"Courier New"; mso-hansi-font-family:"Courier New"; mso-bidi-font-family:宋体;} span.Char0 {mso-style-name:"页眉 Char"; mso-style-priority:99; mso-style-unhide:no; mso-style-locked:yes; mso-style-link:页眉; mso-ansi-font-size:9.0pt; mso-bidi-font-size:12.0pt; font-family:宋体; mso-bidi-font-family:宋体;} span.HTMLChar {mso-style-name:"HTML 预设格式 Char"; mso-style-priority:99; mso-style-unhide:no; mso-style-locked:yes; mso-style-link:"HTML 预设格式"; mso-ansi-font-size:12.0pt; mso-bidi-font-size:12.0pt; font-family:宋体; mso-ascii-font-family:宋体; mso-hansi-font-family:宋体; mso-bidi-font-family:宋体;} .MsoChpDefault {mso-style-type:export-only; mso-default-props:yes; font-size:10.0pt; mso-ansi-font-size:10.0pt; mso-bidi-font-size:10.0pt; mso-ascii-font-family:"Times New Roman"; mso-fareast-font-family:宋体; mso-hansi-font-family:"Times New Roman"; mso-font-kerning:0pt;} /* Page Definitions */ @page {mso-page-border-surround-header:no; mso-page-border-surround-footer:no;} @page Section1 {size:595.3pt 841.9pt; margin:72.0pt 90.0pt 72.0pt 90.0pt; mso-header-margin:42.55pt; mso-footer-margin:49.6pt; mso-paper-source:0; layout-grid:15.6pt;} div.Section1 {page:Section1;} /* List Definitions */ @list l0 {mso-list-id:830759668; mso-list-type:hybrid; mso-list-template-ids:-8985208 -1379771708 67698713 67698715 67698703 67698713 67698715 67698703 67698713 67698715;} @list l0:level1 {mso-level-style-link:"标题 1"; mso-level-tab-stop:none; mso-level-number-position:left; margin-left:21.0pt; text-indent:-21.0pt;} ol {margin-bottom:0cm;} ul {margin-bottom:0cm;} -->
1. 基本信息
JavaCC 版本: 5.0
系统环境: Windows7
Java 版本: 1.6.0_15
2. 安装
下载 JavaCC 安装程序, vivizhyy 同学使用的 Windows 环境下载 zip 版本。
设置环境变量:
JAVACC_HOME =” D:/javacc-5.0/bin”
path = %JAVACC_HOME%/bin
编写
bat
文件:
JavaCC5.0
默认没有提供
bat
文件,需要自己编写,
vivizhyy
同学自己编写的如下:
@echo off java -classpath "%~dp0lib/javacc.jar;%~f0/../lib/javacc.jar" javacc %1 %2 %3 %4 %5 %6 %7 %8 %9 |
(图略)
图 1 : JavaCC5.0 安装成功后界面
3. 利用命令行运行 Simple1
生成
java
文件:
转到
examples/SimpleExamples
目录,运行
javacc
Simple1.jj
生成
ParseException.java,
Simple1.java, Simple1Constants.java, Simple1TokenManager.java, SimpleCharStream.java,
Token.java, TokenMgrError.java
文件
编译生成的 java 文件: javac *.java
运行 parser: java Simple1
输入 {{}}<return> 出现:
(图略)
图 2 : 输入 {{}}<return> 后界面
4. Simple1.jj 学习
JavaCC 的语法是左括号、右括号嵌套作为终结符的,但是括号嵌套中不能出现同级嵌套,例如:
"{}", "{{{{{}}}}}", 是合法的,但是 "{{{{", "{}{}", "{}}", "{{}{}}" 却是非法的。
语法文件以 JavaCC 的设定选项开始,一般是默认的。设定选项不是必须的,可以忽略。那些选项的细节是用来描述 JavaCC 网页文档的。
接下来是在"PARSER_BEGIN(name)" 和 "PARSER_END(name)" 之间的java 的编译单元,这个编译单元可以很复杂或者很简单。编译单元的唯一限制就是必须定义一个叫做 “name” 的类,并且要和 "PARSER_BEGIN(name)" 和 "PARSER_END(name)" 同一个参数。这个参数(name )是用来当作 parser 生成 java 文件的文件头。Parser 代码紧跟在 “name” 类的后面。
在上面的例子中,parser 生成的代码中包含 main 程序。这个 main 程序创建了一个 parser 对象的实例,其中构造函数包含一个 java.io.InputStream 类型的参数。
Main 程序调用一个非终结的语法,在这个里面就会解析 Input. 所有的非终结符在 JavaCC 生成器中都有一个对应的状态,这样一个非终结符就可能和其他任何非终结符关联。
接下来是一系列的生成。在这个例子中,有两个产出是用来分别定义非终结符 “Input” 和 “MatchedBraces” 的。在 JavaCC 的语法中,非终结符非终结符是用 java 的方法写成和实现的。左结合中,它需要被生命并且遵循 java 的句法,右结合中它就像 java 中的方法调用。
每一个 production
定义左结合采用后面紧跟冒号的方式,这样后面跟了许多的用大括号括起来的定义和声明。大括号的定义和声明都是在生成方法中生成的。这样后来很多的扩展也用大括号括起来了。
语义的 token 在 JavaCC 中既不是一件简单的事情,也没有比正则表达式更复杂。在上面的例子中,只有一个所谓的正则表达式 “<EOF>” ,它用来匹配文件的结尾。所有的复杂正则表达式都用尖括号括起来。
第二个要说的非终结符产生式是将 “{” 扩展成选择性带 “}” 。方括号在JavaCC 的输入文件中是可选的。
方括号也许可以写成 (…)?. 这两种方式是等价的。其他的结构也可以是如下:
e1 | e2 | e3 | ... : A choice of e1, e2, e3, etc.
( e )+
: One or more occurrences of e
( e )*
: Zero or more occurrences of e
|
但是它们是可以相互嵌套的,例如:
(( e1 | e2 )* [ e3 ] ) | e4
Build 这个 parser 就像前面介绍过的方式就 OK.
然后就是将语法文件导入,并且尝试去键入一些错误的花括号、空格,或者不正确的返回值,看看出错信息。
5. Simple2.jj 学习
Simple2.jj 是 Simple1.jj 的一个小小的改进,允许空格在花括号中间存在,也就是说可以这样输入:
"{{
}/n}/n/n"
合法。
值得注意的是,这个文件包含了一个词典描述,那个以 “SKIP” 开头的区域。在这个区域中有 4 个正则表达式,空格, tab, 新行,和返回。这说明但匹配这些正则表达式的内容将会被忽略。这样,不管这四种中的哪种被遇见了都会将其丢弃不在解析。
不仅仅是 “SKIP”, JavaCC 还有其他三个词典描述区域:
TOKEN: This is used to specify lexical tokens (see next example) |
SPECIAL_TOKEN: This is used to specify lexical tokens that are to be ignored during parsing. In this sense, SPECIAL_TOKEN is the same as SKIP. However, these tokens can be recovered within parser actions to be handled appropriately. |
MORE: This specifies a partial token. A complete token is made up of a sequence of MORE's followed by a TOKEN or SPECIAL_TOKEN. |
你可以直接调用生成的 parser 从的键盘读入标准输入,或者也可以加上 debug 选项来查看输出是什么结果:
javacc -debug_parser Simple2.jj
javac Simple2*.java
java Simple2
|
然后输入:
javacc -debug_token_manager Simple2.jj
javac Simple2*.java
java Simple2
|
不过, debug 产生的很多诊断信息只是用来一次跟踪一个 token 的。
6. Simple3.jj 学习
Simple3 是匹配括号的终结探测器。它用来说明怎样利用 TOKEN 区域来对于词典 token 的特殊化。在这个例子中, “{” 和 “{“ 被分别定义为 LBRACE 和 RBRACE 。这些标记可以被用作尖括号关联到这个 token. 尤其是这种 token 说明被用作复杂 token 例如标识符。 Token 是简单字符串的忽略。
这个例子也说明了语法生成的动作。这个插入的动作计算匹配的括号数。这个例子的定义中,声明变量 “count” 和 “nested_count”. 非终结符 “MatchedBraces” 像函数返回值一样返回结果。
7. NL_Xlator.jj 学习
这个例子展现了在 JavaCC 语法文件中写正则表达式。它也展现了一点点如何将表达式描述的动作转化成英语。
与上面的例子不同的是,这个例子用了更复杂的正则表达式。比如:
< ID: ["a"-"z","A"-"Z","_"] ( ["a"-"z","A"-"Z","_","0"-"9"] )* >
|
这句话创建了一个新的正则表达式,名字叫 ID , 这个可以在人和其他的地方引用: <ID>. 方括号中的是被允许的的字符,这样大写或者小写或者下划线都合法。这后面跟了 0 个或者更多的大写、小写、字符、数字、下划线的情况。
其他的一些构想:
( ... )+
|
One or more occurrences of ...
|
( ... )?
|
An optional occurrence of ... (Note that in the case of lexical tokens, (...)? and [...] are not equivalent)
|
( r1 | r2 | ... )
|
Any one of r1, r2, ...
|
如果一个正则表达式在扩展中用到了,它有一个 Token 类型的值。这个被 parser 生成一个 Token.java 文件。
8. IdList.jj 学习
这是一个重要属性 SKIP 的特殊说明。值得注意的是,这个正则表达式中定了 *token 之间 * 当并非 *token 之内 * 。这个语法接受任何含有空格的序列。比如:
"abc xyz123 A B C /t/n aaa"
这个是合法的,因为任何 SKIP 正则表达式在连续的 <Id> 之间是合法的,但是下面的这个不是合法的:
"xyz 123"
这是因为, ”xyz” 字母后面的空白是 SKIP 类别中的,因此导致了一个 token 结束另一个 token 已经开始。这要求 “123” 需要被 token 分开并且这合乎规则。
如果空白和 <Id> OK 的话,所需要做的只是:
TOKEN :
{
< Id: ["a"-"z","A"-"Z"] ( (" ")* ["a"-"z","A"-"Z","0"-"9"] )* >
}
|
注意,一个空白字符用 TOKEN 定义并非表示空白字符不能在 SKIP 使用。所有这些只是用来说明空白字符如果出现在上下文清晰的地方将匹配 <Id>, 但是其他情况将会被忽略。这个匹配的细节逻辑体现在 JacaCC 的文档中。