我们向您展示 Java 正则表达式简短指南的翻译,该指南由 Jeff Friesen 为 javaworld 网站编写。为了方便阅读,我们将文章分为几个部分。这部分是最后一部分。 Java 中的正则表达式,第 1 部分 Java 中的正则表达式,第 2 部分 Java 中的正则表达式,第 3 部分 Java 中的正则表达式,第 4 部分
正则表达式比基于状态的词法分析器效率高得多,基于状态的词法分析器必须手工编写,并且通常无法重用。基于正则表达式的词法分析器的一个示例是JLex,它是 Java 语言的词法生成器,它使用正则表达式来定义将输入数据流分解为标记的规则。另一个例子是莱克桑。
使用正则表达式进行词法分析
正则表达式的一个更有用的应用是用于执行词法分析的可重用代码库,它是任何编译器或汇编器的关键组件。在这种情况下,字符输入流被分组为标记输出流 - 具有共同含义的字符序列的名称。例如,在输入流中遇到字符序列c
, o
, u
, n
, t
, e
, , 时,词法分析器可以输出一个标记(标识符)。与标记相对应的字符序列称为词位。 r
ID
有关标记和词位的更多信息 |
---|
ID 等标记可以匹配许多字符序列。在这种标记的情况下,编译器、汇编器或其他需要词法分析的实用程序也需要与该标记相对应的实际标记。对于表示一个特定字符序列的标记,例如PLUS 仅对应于字符 的标记+ ,不需要实际标记,因为它可以由标记[唯一]确定。 |
了解莱克森
Lexan 是一个用于词法分析的可重用 Java 库。它基于Cogito Learning网站上的博客文章系列“用 Java 编写解析器”中的代码。该库由以下类组成,这些类位于本文的可下载代码中包含的包中:ca.javajeff.lexan
Lexan
:词法分析器;LexException
:词法分析过程中如果检测到错误语法则抛出异常;Token
:带有正则表达式属性的名称;TokLex
:令牌/令牌对。
LexanException
: 类构造函数中抛出异常Lexan;
Lexan(java.lang.Class tokensClass)
创建一个新的词法分析器。java.lang.Class
它需要一个与类型常量 class 相对应的类对象形式的参数static Token
。使用 Reflection API,构造函数将所有常量读取Token
到值数组中Token[]
。如果Token
没有常量,则会抛出异常LexanException
。 该类Lexan
还提供了以下两个方法:
- 该方法返回此词法分析器的列表;
List
getTokLexes() Token
Метод void lex(String str)
对输入字符串进行词法分析[并将结果放入]类型为 的值列表中TokLex
。如果遇到与任何数组模式都不匹配的字符Token[]
,则会引发异常LexException
。
LexanException
没有方法;它使用继承的方法返回异常消息getMessage()
。相比之下,该类LexException
提供了以下方法:
- 该方法
int getBadCharIndex()
返回与任何标记模式都不匹配的字符的位置。 - 该方法
String getText()
返回生成异常时分析的文本。
Token
重写该方法toString()
以返回标记的名称。它还提供了一个String getPattern()
返回标记的正则表达式属性的方法。该类提供了一个返回其标记的TokLex
方法。Token getToken()
它还提供了一个String getLexeme()
返回其令牌的方法。
Lexan库的演示
为了演示该库的工作原理,Lexan
我编写了一个应用程序LexanDemo
。它由类LexanDemo
、BinTokens
和组成MathTokens
。NoTokens
该应用程序的源代码LexanDemo
如清单 2 所示。 清单 2. Lexan 库的实际演示
import ca.javajeff.lexan.Lexan;
import ca.javajeff.lexan.LexanException;
import ca.javajeff.lexan.LexException;
import ca.javajeff.lexan.TokLex;
public final class LexanDemo
{
public static void main(String[] args)
{
lex(MathTokens.class, " sin(x) * (1 + var_12) ");
lex(BinTokens.class, " 1 0 1 0 1");
lex(BinTokens.class, "110");
lex(BinTokens.class, "1 20");
lex(NoTokens.class, "");
}
private static void lex(Class tokensClass, String text)
{
try
{
Lexan lexan = new Lexan(tokensClass);
lexan.lex(text);
for (TokLex tokLex: lexan.getTokLexes())
System.out.printf("%s: %s%n", tokLex.getToken(),
tokLex.getLexeme());
}
catch (LexanException le)
{
System.err.println(le.getMessage());
}
catch (LexException le)
{
System.err.println(le.getText());
for (int i = 0; i < le.getBadCharIndex(); i++)
System.err.print("-");
System.err.println("^");
System.err.println(le.getMessage());
}
System.out.println();
}
}
main()
清单 2 中的 方法调用一个实用程序lex()
来演示使用 Lexan 的词法分析。每次调用此方法时都会传递对象中标记的类Class
和要解析的字符串。该方法首先通过将对象传递给类构造函数来 lex()
创建该类的对象。然后它调用该字符串的类方法。如果词法分析成功,则调用类方法返回对象列表。对于每个对象,调用其类方法以返回令牌,并调用其类方法以返回令牌。这两个值都打印到标准输出。如果词法分析失败,则抛出异常之一并进行相应处理。为了简洁起见,我们只考虑组成该应用程序的类。清单 3 显示了其源代码。 清单 3. 小型数学语言的一组标记的描述Lexan
Class
Lexan
lex()
Lexan
TokLex
getTokLexes()
Lexan
getToken()
TokLex
getLexeme()
LexanException
LexException
MathTokens
import ca.javajeff.lexan.Token;
public final class MathTokens
{
public final static Token FUNC = new Token("FUNC", "sin|cos|exp|ln|sqrt");
public final static Token LPAREN = new Token("LPAREN", "\\(");
public final static Token RPAREN = new Token("RPAREN", "\\)");
public final static Token PLUSMIN = new Token("PLUSMIN", "[+-]");
public final static Token TIMESDIV = new Token("TIMESDIV", "[*/]");
public final static Token CARET = new Token("CARET", "\\^");
public final static Token INTEGER = new Token("INTEGER", "[0-9]+");
public final static Token ID = new Token("ID", "[a-zA-Z][a-zA-Z0-9_]*");
}
清单 3 显示该类MathTokens
描述了类型为 的常量序列Token
。他们每个人都被分配了一个对象的值Token
。该对象的构造函数接收一个作为标记名称的字符串,以及一个描述与该标记关联的所有字符串的正则表达式。为了清楚起见,标记的字符串名称最好与常量的名称相同,但这不是必需的。 常量在标记列表中的位置很重要。Token
位于列表上方的常量Token
优先于位于下方的常量。例如,当遇到 时sin
,Lexan 选择标记FUNC
而不是ID
。如果该标记ID
位于 标记 之前FUNC
,则该标记将被选中。
编译并运行 LexanDemo 应用程序
本文的可下载代码包括一个lexan.zip
包含 Lexan 发行版的所有文件的存档。解压缩该存档并转到demos
根目录的子目录lexan
。 如果您使用的是Windows,请运行以下命令来编译演示应用程序源代码文件:
javac -cp ..\library\lexan.jar *.java
如果编译成功,请运行以下命令来运行演示应用程序:
java -cp ..\library\lexan.jar;. LexanDemo
您应该看到以下结果:
FUNC: sin
LPAREN: (
ID: x
RPAREN: )
TIMESDIV: *
LPAREN: (
INTEGER: 1
PLUSMIN: +
ID: var_12
RPAREN: )
ONE: 1
ZERO: 0
ONE: 1
ZERO: 0
ONE: 1
ONE: 1
ONE: 1
ZERO: 0
1 20
--^
Неожиданный символ во входном тексте: 20
该消息是由于类未将值声明为正则表达式的常量Неожиданный символ во входном тексте: 20
而引发异常的结果。请注意,异常处理程序输出从文本的词法分析中获得的不适当字符的位置。令牌丢失消息是由于类中没有声明常量而引发异常的结果。 LexanException
BinTokens
Token
2
LexException
NoTokens
Token
幕后花絮
Lexan
使用 Lexan 类作为其引擎。查看清单 4 中此类的实现,并注意正则表达式对使引擎可重用的贡献。 清单 4. 创建基于正则表达式的词法分析器架构
package ca.javajeff.lexan;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
/**
* Лексический анализатор. Этот класс можно использовать для
* преобразования входного потока символов в выходной поток маркеров.
*
* @Author Джефф Фризен
*/
public final class Lexan
{
private List tokLexes;
private Token[] values;
/**
* Инициализируем лексический анализатор набором an objectов Token.
*
* @параметры tokensClass – an object Class класса, содержащего
* набор an objectов Token
*
* @генерирует исключение LexanException в случае невозможности
* формирования an object Lexan, возможно, из-за отсутствия an objectов
* Token в классе
*/
public Lexan(Class tokensClass) throws LexanException
{
try
{
tokLexes = new ArrayList<>();
List _values = new ArrayList<>();
Field[] fields = tokensClass.getDeclaredFields();
for (Field field: fields)
if (field.getType().getName().equals("ca.javajeff.lexan.Token"))
_values.add((Token) field.get(null));
values = _values.toArray(new Token[0]);
if (values.length == 0)
throw new LexanException("маркеры отсутствуют");
}
catch (IllegalAccessException iae)
{
throw new LexanException(iae.getMessage());
}
/**
* Получаем список TokLex'ов этого лексического анализатора.
*
* @возвращает список TokLex'ов
*/
public List getTokLexes()
{
return tokLexes;
}
/** * Выполняет лексический анализ входной строки [с помещением * результата] в список TokLex'ов. * * @параметры str – строка, подвергаемая лексическому анализу * * @генерирует исключение LexException: во входных данных обнаружен * неожиданный символ */
public void lex(String str) throws LexException
{
String s = new String(str).trim(); // удалить ведущие пробелы
int index = (str.length() - s.length());
tokLexes.clear();
while (!s.equals(""))
{
boolean match = false;
for (int i = 0; i < values.length; i++)
{
Token token = values[i];
Matcher m = token.getPattern().matcher(s);
if (m.find())
{
match = true;
tokLexes.add(new TokLex(token, m.group().trim()));
String t = s;
s = m.replaceFirst("").trim(); // удалить ведущие пробелы
index += (t.length() - s.length());
break;
}
}
if (!match)
throw new LexException("Неожиданный символ во входном тексте: "
+ s, str, index);
}
}
}
该方法代码lex()
基于Cogito Learning 网站上的博客文章“用 Java 编写解析器:令牌生成器”中提供的代码。阅读这篇文章,了解有关 Lexan 如何使用 Regex API 编译代码的更多信息。
结论
正则表达式是一个对任何开发人员都有用的有用工具。Java 编程语言的 Regex API 使它们易于在应用程序和库中使用。现在您已经对正则表达式和此 API 有了基本的了解,请查看 SDK 文档java.util.regex
以了解有关正则表达式和其他 Regex API 方法的更多信息。
GO TO FULL VERSION