JAVACC是什么       

        Java Compiler Compiler(JAVACC) 基于Java应用实现的最受欢迎的语法解析器生成器。它是一个生成器,用于生成词法分析器(lexical analysers)和语法分析器(parsers)。它可以通过读取一个javacc编写的语法规则文件(包含了词法定义,语法定义的 jj,jjt结尾的文件),来生成一个java程序,这个java程序就包括了词法分析器和语法分析器。接着就可以用生成的词法分析器和语法分析器来对我们的输入进行判断,判断输入是否符合我们所要求的语法规则。

        我是最近一年在项目中使用的定义了一套的表达式解析(包含特殊四则运算、函数的递归调用等),有了一定的使用经验,对于以Java为主的开发者来说javacc上手比较容易类似在写jsp(jj文件里可以写java代码)。虽然在公司也做过两次关于javacc技术分享给同事讲解,但是比较简单粗略,所有打算把使用javacc经验成一个系列总结。

 参考官方文档:JavaCC | The most popular parser generator for use with Java applications.

安装及环境设置

1、准备JAVA运行环境

        javacc是用java语言编写的,其运行必须要有jre。建议安装jdk8+,至于如何安装及配置java环境变量请自行百度。

2、下载JAVACC

jflex语法解析 java_后端

         下载javacc-7.0.10.zipjavacc-7.0.10.jar。将javacc-7.0.10.zip解压,在解压后的目录中创建

taget文件夹,再把javacc-7.0.10.jar拷贝到taget目录下并重名为javacc.jar。

jflex语法解析 java_zookeeper_02

 3.配置javacc的环境变量

        将javacc-7.0.10下的scripts目录添加到Path系统环境变量里

jflex语法解析 java_java_03

        配置验证:在cmd窗口中键入javacc 【enter】将会看到版本号及参数说明,则配置成功。

 

jflex语法解析 java_jflex语法解析 java_04

 IDEA上的javaCC插件

jflex语法解析 java_java_05

        在插件市场上搜索javaCC,然后安装重启IDEA即可。

        该插件提供了jj,jjt语法的高亮颜色区别显示及少量的语法检查。生成java语法解析器仍然要通javacc命令。

JAVACC版的Hello word

        很多大神的编程书或(教程)都以一个Hello world小示例作为入门语言的开始。我也尊重传 统向大神致敬,下面编写简单的javacc版语法文件Hello.jj开始学习javacc。

        在展示Hello.jj文件前,先讲一下Hello word的需求语义:解析 以 Hello 或 hello 或 HELLO 开头 跟着至少一个变量,多个变量时用“,”分割开,且以“!”结尾的字符串;解析完成后返回一个字符串Javacc Say :Hello 变量 当前时间,示例如下。

//输入
Hello world !
//输出
Javacc Say : Hello world ! 2022-01-01 21:15:08

//输入
hello 致敬javacc , everyone !
//输出
Javacc Say : Hello 致敬javacc , everyone  ! 2022-01-01 21:15:21

Hello.jj

//可选配置参数
options{
     STATIC = false; //关闭生成java方法是静态的,默认是true
     DEBUG_PARSER = true;//开启调试解析打印,默认是false
     JDK_VERSION = "1.8";//生产java时使用jdk版本,默认1.5
     UNICODE_INPUT=true;//接收unicode编码的输入,默认是false
}
//固定格式
PARSER_BEGIN(Hello)
//像java一样的包名定义,生成的java文件会带上此包名
package com.javacc.hello;
//导入需要引用java
import cn.hutool.core.date.DateUtil;

public class Hello {
    //可以再里面定义初始化信息,字符串接收方式,异常处理..

    //此处的main方法不是必须,只是为了方便此次调测,后面的示例将去除
    public static void main(String[] args){
        Hello helloParser = new Hello(System.in);
        try {
            String res = helloParser.sayHello();
            System.out.println(res);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

//固定格式
PARSER_END(Hello)

//词法定义

//SKIP是一种词法 要跳过忽略的字符串
SKIP : {" "}

//TOKEN也是一种词法,代表要识别的字符串
TOKEN : {
 /**定义一个Token名为HELLO,类似java语言中关键字“class”,会把符串中
 * 有 "Hello" , "hello" ,"HELLO" 识别成此TOKEN。
 **/
 <HELLO : "Hello"
            | "hello"
            | "HELLO">
 |

 //定义一个Token名为IDENTITY,类似自定义变量,可以是英文,中文unicode("\u4e00"-"\u9fa5")
 // ["a" - "z"]表示的范围,小写a到z
 //(...)+ 表示括号中的内容至少出现一次
 <IDENTITY : (["a"-"z"
               ,"A"-"Z"
               ,"\u4e00"-"\u9fa5"])+>
}

//可以写多个TOKEN,此处主要定义特殊符号
TOKEN : {
  <EXCLAMATION : "!">
 | <COMMA : ",">
}
//为了调试方便将换行定义为一个特殊的token
TOKEN : { < EOL : "\n" | "\r" | "\r\n" > }
//定义语法
String sayHello():
{
  Token token;
  StringBuilder builder = new StringBuilder("Javacc Say : Hello ");
}
{
  <HELLO> token = <IDENTITY>
            //匹配到第一IDENTITY执行的动作
            {builder.append(token.image); }

            (
             <COMMA> token = <IDENTITY>
             //匹配到逗号后的IDENTITY时执行的动作
             {  builder.append(" , ");
                builder.append(token.image);
             }
            )*
            <EXCLAMATION>

   {
   builder.append(" ! ").append(DateUtil.now());
   return builder.toString();
   }

}

        示例上注释基本上很全面了,jj语法文件大致结构可成分为:options可选配置、解析类的声明、词法定义、语法定义和动作执行。

options

        主要功能是javacc在读取*.jj语法文件生成java程序时,配置生成参数的。options的相关参数有很多,在javacc源码的Options类中大概有48个参数,都是有个默认值,它还可以通过javacc命令去配置,所有不去配置也是可以的, 但是还是要根据实际的情况合理的配置,后面对options写一篇专门的总结。

解析类的声明       

PARSER_BEGIN(类名)

pakage 包名 //生成java文件采用此包名

import ....
public class 类 {
      ......
}
 
PARSER_END

        按照固定格式去编写即可。这里的声明主要为了确定生成java语法解析器的类名、包名、import的java依赖类及构造函数,成员变量等。

词法定义    

TOKEN : {
  <定义词1 :匹配规则1
          | 匹配规则n>
 | <定义词2 : 匹配规则>
.....
}

        符号“|”是或者的含义,匹配规则类似正则表达式。javacc的词法类型有SKIP、TOKEN 、MORE、SPECIAL_TOKEN。词法定义是语法解析的基础,好的词法定义可以让语法解析时很容易处理到达事半功倍的效果。下一篇博客将会对词法进行较为详细的总结。

语法定义和动作执行

返回类型 语法规则名(参数) 异常抛出声明 : 
{变量声明}
{
  //语法规则
   定义词的组合规则
  {匹配规则时的执行动作,即使java代码执行 ;}
}

        语法定义的格式有点像java方法的定义:有返回类型,语法规则名,异常抛出声明 ,变量声明,语法主体及动作执行。

返回类型:无返则声明为 void,有返回类型 则要在动作执行中至少一次的 return 返回类型

语法规则名:类似java方法,可以无参数,格式遵守java方法的方式。

异常抛出声明 :可以忽略不写,如果执行动作(java代码执行)中有抛出异常且未处理则要声明抛出的异常。

 变量声明:无变量声明可以为空,但是这一对“{}”是不能省略。声明的变量为语法主体要使用的,其作用范围仅限在该语法规则中。

语法主体及动作执行:语法规则可以说是根据需要千变万化,其实质就是定义的词法的规则组合,其规则组合也类似正则表达式。要写好语法规则,除了要定义好词法外,重要就要理解清楚这个规则的语义,清楚了语义写规则就是信手捏来,那么动作执行就水到渠成。

生成语法(包括词法)解析程序     

javacc option-settings xx.jj文件路径

//例如
javacc Hello.jj

     option-settings 就是上文介绍的options中的参数,此选项也是可以省略不写的。作用的优先级是命令设置>jj文件中定义的>默认值。

执行javacc命令,如果成功会有生成解析程序的反馈信息。

jflex语法解析 java_大数据_06

 从反馈信息可以看到生成了几个java文件。其实目前的javacc版本会生成7个文件。

jflex语法解析 java_zookeeper_07

Hello.java  主要的语法解析程序。

HelloConstants.java 常量定义,jj文件定义的TOKEN在这里以常量形式定义

HelloTokenManager.java  词法解析器

ParseException.java  解析异常类

SimpleCharStream 字符流,用于处理ASCII字符

Token   Token token;

TokenMgrError 词法错误类

        当然如果编写的jj文件有问题,javacc 命令执行时会反馈错误的信息,并给错误点或者推荐改正的地方。

        现在可以运行Hello程序看看执行效果了。

测试1:Hello xiaoming !

jflex语法解析 java_jflex语法解析 java_08

开启解析打印调试后(DEBUG_PARSER = true),对字符串的解析过程会输出打印,可以看到消费(匹配)的Token情况

测试2:hello 致敬javacc , everyone !

 

jflex语法解析 java_java_09

开启对unicode支持,可以进行中文字符解析

测试3:How are you

jflex语法解析 java_java_10

        当输入字符串不匹配时,会抛出异常就是上面生成的ParseException,错误信息提示它期望得到HELLO,错误信息很明确。

测试4:HELLO Fan ,jack

jflex语法解析 java_zookeeper_11

        这个错误是没有正确结尾,它期望的是“!”或“,”。从打印信息的可以看到解析过程它是解析了大部分字符串,但是最后的解析不正确了抛出了异常。