自制编译器篇(2):JavaCC 应用案例——解析正整数加法运算

Author: stormQ

Created: Saturday, 27. February 2021 10:20PM

Last Modified: Sunday, 28. February 2021 09:40PM



摘要

本文描述了如何基于 JavaCC 生成用于“两个正整数加法运算”的解析器,并简单地分析了 JavaCC 所生成解析器的源码。


编写语法描述文件

本节描述了如何编写 JavaCC 的语法描述文件,从而生成用于“两个正整数加法运算”的解析器。

step 1: 编写语法描述文件的“options”部分

在 JavaCC 中,STATIC选项的默认值为true,意味着 Java CC 生成的所有成员和方法都将被定义为static

为了所生成的解析器能够在多线程环境下使用,我们将STATIC选项的值设置为false。如下所示:

options {
  STATIC = false;
}

step 2: 编写语法描述文件的“PARSER_BEGIN、PARSER_END”部分

1) 编写 PARSER_BEGIN 和 PARSER_END

如果我们要定义的解析器类的名称为Adder,那么如下编写PARSER_BEGINPARSER_END

PARSER_BEGIN(Adder)

PARSER_END(Adder)

2) 编写解析器类

编写的解析器类必须放到PARSER_BEGINPARSER_END之间。

a) 定义解析器类

public class Adder {

}

b) 编写解析器类的 main 函数

在这个例子中,解析器并不生成抽象语法树,而是直接计算表达式的结果。因此,为了能够运行,需要编写一个main函数。

main函数的代码如下:

static public void main(String[] args{
  for (String arg : args) {
    try {
      System.out.println(evaluate(arg));
    }
    catch (ParseException ex) {
      System.err.println(ex.getMessage());
    }
  }
}

上述代码的意义,遍历所有的命令行参数,每次迭代时将命令行参数以字符串的形式传递给evaluate函数,并打印其返回结果。

c) 编写解析器类的 evaluate 函数

evaluate函数的代码如下:

static public long evaluate(String src) throws ParseException {
  Reader reader = new StringReader(src);
  return new Adder(reader).expr();
}

JavaCC 5.0 生成的解析器中默认提供的构造函数之一为:Adder(java.io.Reader stream)。因此,我们才可以在evaluate函数中将Reader类型的实例作为解析器类Adder的参数。

此外,要运行 JavaCC 生成的解析器类,需要如下两个步骤:1)生成解析器类的对象实例;2)用生成的对象调用和需要解析的语句同名的方法。

上述代码中,语句new Adder(reader).expr();完成了这两个步骤。

d) 导入依赖

由于evaluate函数中使用了StringReader类。因此,需要导入如下依赖:

import java.io.*;

step 3: 编写语法描述文件的“扫描器的描述”部分

1) 忽略空格、制表符和换行符(optional)

SKIP: { <[" ","\t","\r","\n"]> }

上述描述中的SKIP是 JavaCC 中用于省略单词的命令,SKIP: { <[" ","\t","\r","\n"]> }表示跳过空格、制表符和换行符。如果不加这部分,也是可以的。但输入的表达式中不能出现空格等字符,比如表达式"1 + 2"是无法被识别的。

2) 识别正整数

TOKEN: {
  <INTERGR: (["0"-"9"])+>
}

上述描述中的TOKEN命令是 JavaCC 中用于描述token的,INTERGR为自定义的token名称,(["0"-"9"])+表示该token需要满足的正则表达式。

上述正则表达式的含义为:由 1 个或多个 0 到 9 数字组成的单词。

step 4: 编写语法描述文件的“解析器的描述”部分

解析器的描述如下:

long expr():
{
  Token x, y;
}
{
  x=<INTERGR> "+" y=<INTERGR> <EOF>
  {
    return Long.parseLong(x.image) + Long.parseLong(y.image);
  }
}

查看 JavaCC 根据上述描述所生成的expr函数:

final public long expr() throws ParseException {
Token x, y;
  x = jj_consume_token(INTERGR);
  jj_consume_token(3);
  y = jj_consume_token(INTERGR);
  jj_consume_token(0);
  {if (truereturn Long.parseLong(x.image) + Long.parseLong(y.image);}
  throw new Error("Missing return statement in function");
}

从上面的代码可以看出:

step 5: 完整的语法描述文件

语法描述文件,sumi.jj:

options {
  STATIC = false;
}

PARSER_BEGIN(Adder)

import java.io.*;

public class Adder {
  static public void main(String[] args) {
    for (String arg : args) {
      try {
        System.out.println(evaluate(arg));
      }
      catch (ParseException ex) {
        System.err.println(ex.getMessage());
      }
    }
  }

  static public long evaluate(String src) throws ParseException {
    Reader reader = new StringReader(src);
    return new Adder(reader).expr();
  }
}

PARSER_END(Adder)

SKIP: { <[" ","\t","\r","\n"]> }

TOKEN: {
  <INTERGR: (["0"-"9"])+>
}

long expr():
{
  Token x, y;
}
{
  x=<INTERGR> "+" y=<INTERGR> <EOF>
  {
    return Long.parseLong(x.image) + Long.parseLong(y.image);
  }
}

基于 JavaCC 实现正整数加法运算的解析

step 1: 安装 javac

安装 Java 编译器——javac 的命令如下:

$ sudo apt install default-jdk

step 2: 使用 JavaCC 生成解析器

$ javacc sumi.jj

注:JavaCC 的安装和用法,可参考笔者的另一篇文章:《解析器生成器之 JavaCC(1):零基础入门 JavaCC》。

step 3: 编译 JavaCC 所生成的解析器

$ javac Adder.java

查看编译后的文件——Adder.class:

$ file Adder.class 
Adder.class: compiled Java class dataversion 55.0

step 4: 运行 JavaCC 所生成的解析器

$ java Adder "1+2" "1 + 3"

运行结果如下:

3
4

References


下一篇:自制编译器篇(3):自顶向下描述 Cb 语言的语法

上一篇:自制编译器篇(1):Cb 编译器的安装和使用

首页