javacc教程2 入门示例

第一个例子,我们做一个花括号的匹配,控制台输入一组左大括号,后面跟着相同数量的右大括号,最后是零个或多个行结束符(回车),最后是文件结束符(ctrl+d)。
合法字符串的示例:
"{}", "{ { { { {}}}}}", ...

非法字符串的例子:

"{ { { {", "{}{}", "{}}", "{ {}{}}", ...

第一个实例的代码如下:

PARSER_BEGIN(Simple)
package com.github.gambo.javacc.simple;

/** Simple brace matcher. */
public class Simple {

  /** Main entry point. */
  public static void main(String args[]) throws ParseException {
    Simple parser = new Simple(System.in);
    parser.Input();
  }

}

PARSER_END(Simple)

/** Root production. */
void Input() :
{}
{
  MatchedBraces() ("\n"|"\r")* <EOF>
}

/** Brace matching production. */
void MatchedBraces() :
{}
{
  "{" [ MatchedBraces() ] "}"
}

终结符和非终结符

JavaCC输入语法中的token(正则表达式)要么是简单的字符串,或者更复杂的正则表达式。我们看到ibput方法里有一个这样的正则表达式“<EOF>”,它匹配文件结束符。所有复杂正则表达式都包含在尖括号内。而这种代表具体的符号正则表达式我们称之为“终结符”,("\n"|"\r")则可以归为此类。

而上例的两个方法Input和MatchedBraces则可以称之为非终结符。其可以被定义为一组终结符和/或其他非终结符所组成序列而形成的语法。

非终结符各在后面定义了一个大括号,这个大括号内一般包含了一堆声明和语句,这些声明和语句作为通用声明和语句生成到生成的方法中。在上面的例子中,没有任何声明,因此显示为大括号是空的。后面继续跟着一个大括号括起来的拓展。

非终结符“Input”可以展开为非终结符“matchedbrace”,后面跟着零个或多个行结束符("\n"或"\r"),然后是文件结束符。

而非终结符“matchedbrace”展开以为token“{”开头,后面是可选的嵌套“matchedbrace”,最后以token“}”结尾。方括号[xxx]在JavaCC输入文件中表示xxx是可有可无的。也可以写成 (xxx)?。这两种形式是等价的。非终结符中的元素还可以定义为以下结构:

e1 | e2 | e3 | ... : e1, e2, e3, 等当中选择一个.
( e )+                :e出现一次或多次
( e )*                 : e出现0次或多次

这些结构也可以彼此嵌套,所以我们可以得到这样的结构:

(( e1 | e2 )* [ e3 ] ) | e4

构建解析器

要构建这个解析器,只需在这个文件上运行JavaCC命令并编译生成的Java文件:
javacc Simple.jj
javac *.java

由于我们搭建了ant环境,可以按照上一节的方法构建解析器。构建操作和生成的代码如下:

接下来我们运行生成的解析器验证一下结果

正确运行结果如下:

{
   
   {
   
   {}}}

^D
Disconnected from the target VM, address: '127.0.0.1:54377', transport: 'socket'

错误实例的运行结果如下:

{
   
   {
   
   {}}}}}}{}
Exception in thread "main" com.github.gambo.javacc.simple.ParseException: Encountered " "}" "} "" at line 1, column 7.
Was expecting one of:
    <EOF> 
    "\n" ...
    "\r" ...
    
	at com.github.gambo.javacc.simple.Simple.generateParseException(Simple.java:249)
	at com.github.gambo.javacc.simple.Simple.jj_consume_token(Simple.java:187)
	at com.github.gambo.javacc.simple.Simple.Input(Simple.java:44)
	at com.github.gambo.javacc.simple.Simple.main(Simple.java:11)

接下来我们对上面的解析器做一个拓展,使其支持空格字符穿插在大括号之间:
"{ { }\n}\n\n"这将是合法的。代码如下:
 

词法规范

PARSER_BEGIN(Simple2)
package com.github.gambo.javacc.simple;
/** Simple brace matcher. */
public class Simple2 {

  /** Main entry point. */
  public static void main(String args[]) throws ParseException {
    Simple2 parser = new Simple2(System.in);
    parser.Input();
  }

}

PARSER_END(Simple2)

SKIP :
{
  " "
| "\t"
| "\n"
| "\r"
}

/** Root production. */
void Input() :
{}
{
  MatchedBraces() <EOF>
}

/** Brace matching production. */
void MatchedBraces() :
{}
{
  "{" [ MatchedBraces() ] "}"
}

这里比之前的实例多了一个以“SKIP”开头的区域。在这个区域中有4个正则表达式——空格、制表符、换行符和return。这表示忽略这些正则表达式匹配到的部分。因此,无论何时遇到这4个字符中的任何一个,它们都会被丢弃。

除了SKIP之外,JavaCC还有另外三个词法规范:

  • TOKEN: 这用于指定词法标记。
  • SPECIAL_TOKEN: 这用于指定在解析过程中要忽略的词法令牌。从这个角度上看SPECIAL_TOKEN与SKIP相同。但是,可以在解析器操作中恢复这些令牌,以便进行适当处理。
  • MORE: 这指定了部分令牌。一个完整的令牌由一系列MORE组成,后面跟着一个token或SPECIAL_TOKEN。

SPECIAL_TOKEN和MORE我们会在后面的章节做介绍。

我们进一步对上面的例子做一个拓展使其计算匹配大括号的数量。

PARSER_BEGIN(Simple)
package com.github.gambo.javacc.simple;
/** Simple brace matcher. */
public class Simple {

  /** Main entry point. */
  public static void main(String args[]) throws ParseException {
    Simple parser = new Simple3(System.in);
    parser.Input();
  }

}

PARSER_END(Simple)

SKIP :
{
  " "
| "\t"
| "\n"
| "\r"
}

TOKEN :
{
  <LBRACE: "{">
| <RBRACE: "}">
}

/** Root production. */
void Input() :
{ int count; }
{
  count=MatchedBraces() <EOF>
  { System.out.println("The levels of nesting is " + count); }
}

/** Brace counting production. */
int MatchedBraces() :
{ int nested_count=0; }
{
  <LBRACE> [ nested_count=MatchedBraces() ] <RBRACE>
  { return ++nested_count; }
}

这里比上个示例又多了个TOKEN定义。

在JavaCC的词法规范中,TOKEN是用来定义终结符的部分,它由一组终结符的定义组成。每个终结符定义都由一个或多个词法单元组成,这些词法单元可以是一个字符串、一个正则表达式,或是其他的一些定义方式。本例中使用TOKEN定义了两个终结符,分别是LBRACERBRACE代表左花括号和右花括号。并且用尖括号括起来。

这样的标记规范用于一般用于复杂的正则标记,对于简单的token一般保持原样。
本例中需要计算匹配大括号的数量。使用了声明区域来声明变量"count"和"nested_count"。注意非终结符“matchedbrace”如何将其值作为函数返回值返回。

示例代码:https://github.com/ziyiyu/javacc-tutorial

猜你喜欢

转载自blog.csdn.net/gambool/article/details/132854016