前言
其实一开始我看到 Jfelx 是有点懵的,因为老师只提供了一个 jfelx-full-1.8.2.jar
的 jar 文件。这显然是一个 java 包,所以我不知道应该怎么用。然后我双击了一下,出现了这个画面:
我就更懵了。想想,还是得看看官方文档,顺便写一篇记录一下,也算是给后来人一篇入门的文章吧。
下载和安装
第一步 下载
从 JFlex - JFlex The Fast Scanner Generator for Java 可以下载该工具。当前的最新稳定版本是 2020 年 5 月发布的 JFlex 1.8.2。该网站提 供的压缩文件中已包含了你在本实验中所需的各类资源,包括该工具的 Java 源代码、支持运行的库文 件与脚本文件、用户文档、输入源文件例子等。
解压后可以发现,这是一个 java 工程标准的文件组织形式。
第二步 修改配置
根据你自己的安装配置,修改 JFlex 安装目录下脚本文件 bin\jflex.bat 中的环境变量 JFLEX_HOME 的设置。默认设置是 C:\Jflex,修改为下载 Jflex 的目录位置。同时需要添加 JAVA_HOME。
@echo off
REM Please adjust JFLEX_HOME and JFLEX_VERSION to suit your needs
REM (please do not add a trailing backslash)
set JFLEX_HOME=D:\software\jflex\jflex-1.8.2
set JFLEX_VERSION=1.8.2
set JAVA_HOME=D:\software\jdk
java -Xmx128m -jar "%JFLEX_HOME%\lib\jflex-full-%JFLEX_VERSION%.jar" %*
第三步 添加环境变量
将 \jflex-1.8.2\bin
添加进环境变量。
第四步 测试
在命令行中运行 jflex --version
查看是否安装成功。如果没有成功,可以尝试重启。
使用
通过看 README 文件
该目录包含JFlex,一个用于Java的快速扫描生成器。
要运行JFlex,请从命令行运行“bin/JFlex”,或双击JFlex-full-1.8.2。’lib/’目录中的jar文件。
参见“doc/”中的手册或网站http://jflex.de了解更多信息以及如何开始。
我们发现双击 jfelx-full-1.8.2.jar
确实是一个运行的方式。所以当务之急是研究 JFlex 的工作原理。
JFlex 的目的是创建一个词法分析器,提供了输入检查,并确定所有的字符归类是有效的。我们需要给 JFlex 提供词法分析说明文件,该文件分为三个部分,每个部分由 %%
分开。
用户代码段
%%
参数设置和声明段
%%
词法规则段
用户代码段
这个段中的所有内容将被拷贝到生成的词法类的类声明之前。从下面的示例可以看出,这个部分提供了词法分析器所需要的外部文件和需要调用的库,一般是一些 import
和 package
。
参数设置和声明段
这个段含有参数,词法状态和宏定义。
设置参数将包含额外的代码,它们将被包括在产生的扫描器类。参必须另开新行,以 % 开头。可以包含的参数很多。在随JFlex 来的手册中可以得到一个可以包含的参数列表。详见 doc\manual.pdf
。简单介绍一些:
%public
:使生成的类公开(默认情况下,该类只能在其自己的包中访问)。%class "classname"
:告诉 JFlex 把生成的类命名为classname
并把代码写到名为classname.java
的文件。%cupsym "classname"
:自定义包含终端令牌名称的 CUP 生成的类/接口的名称。默认为 sym。该指令不应在 %cup 之后使用,只能在之前使用。%type "typename"
:使扫描方法被声明为指定类型的返回值。然后,规范中的操作可以将typename
的值作为标记返回。此设置下的默认文件结尾值为空。如果typename
不是java.lang.Object
的子类,则应使用%eofval{ ... %eofval}
指令或<<EOF>>
规则指定文件的另一端值。%type
指令覆盖%cup
开关的设置。%cup
:切换到 CUP 兼容模式以与 CUP 生成的解析器交互。%unicode
:定义扫描仪将处理的字符集。对于扫描文本文件,应始终使用 %unicode。可以指定 Unicode 版本,例如%unicode 4.1。如果未指定版本,将使用最新支持的 Unicode 版本 - 在 JFlex 1.8.2 中,这是 Unicode 12.1。ignorecase
:此选项使 JFlex 处理规范中的所有字符和字符串,就好像它们以大写和小写形式指定一样。这样可以轻松地为具有不区分大小写的关键字的语言指定扫描程序。%eofval{ ... %eofval}
:其中包含的代码将被逐字复制到扫描方法中,并在每次到达文件末尾时执行(在结束后再次调用扫描方法时可能不止一次)文件已到达)。代码应将指示文件结尾的值返回给解析器。%yylexthrow{ ... %yylexthrow}
:其中列出的异常将在扫描方法的 throws 子句中声明。如果规范中有多个 %yylexthrow{ … %yylexthrow} 子句,将声明所有指定的异常。%line
:打开行计数(当前行号可以通过变量 yyline 访问)。%column
:打开列计数(当前列号可以通过变量 yycolumn 访问)。
在词法状态部分中,可以声明扫描器用到的成员变量和函数。可以加入的代码是Java代码,并放在 %{
和 }%
之间。它们将被拷贝到生成的词法类源代码中。在我们的词法说明中,声明了两个成员函数。这些函数创建 java_cup.runtime.Symbol
对象。第一个仅仅记录当前记号的位置信息。第二个还包含了记号的值。以下是到这个声明的连
接。
宏定义用作正则表达式的缩写。一个宏定义包含一个宏标识符,其后为一个=
,然后是宏要代表的正则表达式。如下是一个我们的词法说明中用到的宏定义的连接。还有一个连接包含了一个列表,列出了创建正则表达式可用的东西及每一项的含义。
词法分析段
词法分析说明的最后一段包含正则表达式和当扫描器匹配了相关的正则表达式后所要执行的动作。扫描器会激活具有最大匹配的表达式。所以如果存在两个正则表达式”to”和”too”,扫描器会匹配”too”,因为它是最长的。如果两个正则表达式完全相同,具有相同的长度,那么扫描器将匹配最先列在说明中的表达式。
每个正则表达式可以附带动作,这样当扫描器匹配了正则表达式后就可以激活它的动作。每个正则表达式的动作就是你可以写的Java代码片段。你想要的动作可以是打印出一些东西,或是返回扫描器发现的标识符给处理器。
JFlex 允许程序员定义特殊的词法状态(lexical states)用作开始条件来细化说明。YYINITIAL 是一个预定义的词法状态,是词法分析器初始扫描输入的状态。它是我们将用的唯一状态。所以,我们所有的正则表达式都将从这个词法状态开始识别。
示例
结合示例,更容易理解 JFlex 的使用。
import java.io.*;
import exceptions.*;
%%
%public
%class OberonScanner
%unicode
%ignorecase
%line
%column
%yylexthrow LexicalException
%type String
%eofval{
return "EOF";
%eofval}
ReservedWord = "module"|"begin"|"end"|"const"|"type"|"var"|"procedure"|"record"|"array"|"of"|"while"|"do"|"if"|"then"|"elsif"|"else"|"or"|"div"|"mod"
Keyword = "integer"|"boolean"|"read"|"write"|"writeln"
Opeartor = "="|"#"|"<"|"<="|">"|">="|"*"|"div"|"mod"|"+"|"-"|"&"|"or"|"~"|":="|":"|"("|")"|"["|"]"
Punctuation = ";"|","|"."
Comment = "(*"~"*)"
Identifier = [:jletter:]+[:jletterdigit:]*
Integer = 0[0-7]* | [1-9]+[0-9]*
IllegalOctal = 0[0-7]*[8|9|"."]+[0-9]*
IllegalInteger = {Integer}+{Identifier}+
MismatchedComment= "(*" ( [^\*] | "*"+[^\)] )* | ( [^\(] | "("+[^\*] )* "*)"
WhiteSpace = " "|\r|\n|\r\n|\t
%%
<YYINITIAL>
{
{ReservedWord} {return "ReservedWord";}
{Keyword} {return "Keyword";}
{Operator} {return "Operator";}
{Punctuation} {return "Punctuation";}
{Comment} {return "Comment";}
{Identifier} {
if (yylength() > 24)
throw new IllegalIdentifierLengthException();
else
return "Identifier";
}
{Integer} {
if (yylength() > 12)
throw new IllegalIntegerRangeException();
else
return "Integer";
}
{IllegalOctal} {throw new IllegalOctalException();}
{IllegalInteger} {throw new IllegalIntegerException();}
{MismatchedComment} {throw new MismatchedCommentException();}
{WhiteSpace} {}
. {throw new IllegalSymbolException();}
}