使用Regex API简化通用编码任务
本教程的前半部分介绍了正则表达式和正则表达式API。您了解了Pattern该类,然后通过演示正则表达式的示例,从基本模式匹配文字字符串到更复杂的匹配使用范围,边界匹配器和量词。
在第2部分,我们将拿起我们离开的地方,探索与相关联的方法Pattern,Matcher以及PatternSyntaxException类。您还将介绍两种使用正则表达式来简化常见编码任务的工具。第一个提取代码中的注释用于文档目的。第二个是用于执行词法分析的可重用库,它是汇编器,编译器和类似软件的重要组成部分。
探索正则表达式API
Pattern,Matcher并且PatternSyntaxException是构成正则表达式API的三个类。每个类提供可用于将正则表达式集成到代码中的方法。
图案方法
Pattern该类的实例描述了一个编译的正则表达式,也称为模式。编译正则表达式以增加模式匹配操作期间的性能。以下static方法支持编译。
lPattern compile(String regex)将regex内容编译成存储在新Pattern对象中的中间表示。该方法或者在成功时返回对象的引用,或者PatternSyntaxException如果检测到无效的语法,则抛出该方法regex。此Matcher对象使用或返回的任何对象都Pattern遵守各种默认设置,例如区分大小写的搜索。作为示例,Pattern p = Pattern.compile("(?m)^\\.");创建一个Pattern存储正则表达式的编译表示的对象,以匹配以句点字符开头的所有行。
lPattern compile(String regex, int flags)完成与...相同的任务Pattern compile(String regex),但可以解释为flags:一个有点包含OROR的标志常量位值集合。Pattern声明CANON_EQ,CASE_INSENSITIVE,COMMENTS,DOTALL,LITERAL,MULTILINE,UNICODE_CASE,UNICODE_CHARACTER_CLASS,和UNIX_LINES可以按位或运算在一起(例如,常数CASE_INSENSITIVE | DOTALL),并传递给flags。
除CANON_EQ,LITERAL和UNICODE_CHARACTER_CLASS,这些常数是嵌入标志表达式替代,其被证明在第1部分所述的Pattern compile(String regex, int flags)方法引发java.lang.IllegalArgumentException,当它检测到一个标志常数比由下式定义的其它Pattern常数。例如,Pattern p = Pattern.compile("^\\.", Pattern.MULTILINE);等同于前面的例子,其中Pattern.MULTILINE常量和(?m)嵌入式标志表达式完成相同的任务。
有时您将需要获得已编译到Pattern对象中的原始正则表达式字符串的副本,以及它正在使用的标志。您可以通过调用以下方法来执行此操作:
lString pattern()返回已编译到Pattern对象中的原始正则表达式字符串。
lint flags()返回Pattern对象的标志。
获取Pattern对象后,通常会使用它来获取Matcher对象,以便您可以执行模式匹配操作。在Matcher matcher(Charsequence input)创建Matcher该匹配提供的对象input文本对给定Pattern对象的已编译正则表达式。当被调用时,它返回对该Matcher对象的引用。例如,为变量引用的对象Matcher m = p.matcher(args[1]);返回一个。MatcherPatternp
一次性比赛
Pattern的static boolean matches(String regex, CharSequence input)方法保存你的麻烦创建的Pattern和Matcher一次性的模式匹配对象。该input匹配时返回true regex; 否则返回false。如果regex包含语法错误,该方法将抛出一个PatternSyntaxException。例如,System.out.println(Pattern.matches("[a-z[\\s]]*", "all lowercase letters and whitespace only"));输出true,表示仅显示空格字符和小写字母all lowercase letters and whitespace only。
分割文字
大多数开发人员已经编写代码来将输入文本分解成其组成部分,例如将基于文本的员工记录转换为一组字段。Pattern通过一种文本分割方法,可以更快地处理这个问题:
lString[] split(CharSequence text, int limit)分割对象模式的text匹配,Pattern并将结果返回到数组中。每个条目通过模式匹配(或文本结尾)指定与下一个文本序列分离的文本序列。所有数组条目都按照它们出现的顺序存储text。
在这种方法中,数组条目的数量取决于limit哪个,它还控制发生的匹配次数:
?正值意味着最多limit - 1匹配被考虑,并且数组的长度不大于limit条目。
?负值表示考虑所有可能的匹配,并且数组可以是任何长度。
?零表示考虑所有可能的匹配,数组可以有任何长度,并且尾随的空字符串被丢弃。
lString[] split(CharSequence text) 调用以前的方法为零作为限制,并返回方法调用的结果。
以下是如何split(CharSequence text)处理将员工记录分割成名称,年龄,街道地址和工资的字段组件的任务:
Pattern p = Pattern.compile(",\\s");
String[] fields = p.split("John Doe, 47, Hillsboro Road, 32000");
for (int i = 0; i < fields.length; i++)
System.out.println(fields[i]);
上面的代码指定一个正则表达式,匹配一个逗号字符,紧跟着一个单一空格字符。以下是输出:
John Doe
47
Hillsboro Road
32000
模式谓词和Streams API
Java 8引入了Predicate<String> asPredicate()方法Pattern。此方法创建用于模式匹配的谓词(布尔值函数)。下面的代码演示asPredicate():
List<String> progLangs = Arrays.asList("apl", "basic", "c", "c++", "c#", "cobol","java", "javascript", "perl", "python", "scala");
Pattern p = Pattern.compile("^c");
progLangs.stream().filter(p.asPredicate()).forEach(System.out::println);
此代码创建一个编程语言名称列表,然后编译一个模式,以匹配所有以小写字母开头的名称c。上面的最后一行获得了一个以列表为源的顺序流。它安装一个使用asPredicate()布尔函数的过滤器,当一个名称开头时返回true c,并且迭代流,将匹配的名称输出到标准输出。
最后一行相当于以下传统循环,您可能会从第1部分的RegexDemo应用程序中记住:
for (String progLang: progLangs)
if (p.matcher(progLang).find())
System.out.println(progLang);
匹配方法
Matcher该类的实例描述了通过解释Pattern编译的正则表达式来对字符序列执行匹配操作的引擎。Matcher对象支持不同种类的模式匹配操作:
lboolean find()扫描下一个匹配的输入文本。该方法可以在给定文本的开始处或者在前一个匹配之后的第一个字符处开始扫描。后一个选项只有在以前的方法调用返回true并且匹配器未被重置时才可能。在任何一种情况下,当发现匹配时返回布尔值true。你可以RegexDemo从第1部分中找到这个方法的例子。
lboolean find(int start)重置匹配器并扫描下一个匹配的文本。扫描从指定的索引开始start。发现匹配时返回布尔值true。例如,m.find(1);从索引开始扫描文本1。(索引0被忽略)如果start包含负值或超过匹配器文本长度的值,则此方法将抛出java.lang.IndexOutOfBoundsException。
lboolean matches()尝试将整个文本与模式进行匹配。当整个文本匹配时,此方法返回true。例如,Pattern p = Pattern.compile("\\w*"); Matcher m = p.matcher("abc!"); System.out.println(p.matches());输出,false因为!符号不是字符。
lboolean lookingAt()尝试将给定文本与模式进行匹配。当任何文本匹配时,此方法返回true。不同的是matches(),整个文本不需要匹配。例如,Pattern p = Pattern.compile("\\w*"); Matcher m = p.matcher("abc!"); System.out.println(p.lookingAt());输出,true因为abc!文本的开头仅由字符字符组成。
与Pattern对象不同,Matcher对象记录状态信息。偶尔,您可能需要重新设置匹配器以在执行模式匹配后清除该信息。以下方法重置匹配器:
lMatcher reset()重置匹配器的状态,包括匹配器的附加位置(清除为零)。下一个模式匹配操作从匹配器文本的开头开始。Matcher返回对当前对象的引用。例如,m.reset();重置引用的匹配器m。
lMatcher reset(CharSequence text)重置匹配器的状态并将匹配器的文本设置为text。下一个模式匹配操作从匹配器新文本的开始开始。Matcher返回对当前对象的引用。例如,m.reset("new text");重置m参考匹配器,并将其指定new text为匹配器的新文本。
附加文字
匹配器的追加位置标识匹配器文本的起始位置java.lang.StringBuffer object。以下方法使用追加位置:
lMatcher appendReplacement(StringBuffer sb, String replacement)读取匹配器的文本字符并将其附加到被sb引用的StringBuffer对象。该方法在上一个模式匹配之前的最后一个字符之后停止读取。接下来,该方法将replacement参考String对象中的字符附加到StringBuffer对象。(该replacement字符串可能包含对上一个匹配期间捕获的文本序列的引用,通过美元符号($)和捕获组号。)最后,该方法将匹配器的追加位置设置为最后一个匹配字符的索引加1,然后返回对当前匹配器的引用。
该Matcher appendReplacement(StringBuffer sb, String replacement)方法java.lang.IllegalStateException在匹配器尚未匹配时或当前一个匹配尝试失败时抛出。IndexOutOfBoundsExceptionreplacement
lStringBuffer appendTail(StringBuffer sb)将所有文本附加到StringBuffer对象并返回该对象的引用。在对该appendReplacement(StringBuffer sb, String replacement)方法进行最后调用之后,调用appendTail(StringBuffer sb)将剩余文本复制到StringBuffer对象。
捕获组
从第1部分回想一个捕获组是由括号metacharacters(( ))包围的字符序列。这个构造的目的是保存一个匹配的字符,以便在模式匹配期间进行以后的调用。捕捉组中的所有字符在模式匹配期间被视为单个单元。
下面的代码调用appendReplacement(StringBuffer sb, String replacement),并appendTail(StringBuffer sb)更换所有出现cat与caterpillar所提供的文字:
Pattern p = Pattern.compile("(cat)");
Matcher m = p.matcher("one cat, two cats, or three cats on a fence");
StringBuffer sb = new StringBuffer();
while (m.find())
m.appendReplacement(sb, "$1erpillar");
m.appendTail(sb);
System.out.println(sb);
在替换文本中放置捕获组和对捕获组的引用,指示程序erpillar在每次cat匹配之后插入。上述代码产生以下输出:
one caterpillar, two caterpillars, or three caterpillars on a fence
替换文字
Matcher提供了一对补充的文本替换方法appendReplacement(StringBuffer sb, String replacement)。这些方法可以让您替换第一场比赛或所有比赛:
lString replaceFirst(String replacement)重置匹配器,创建一个新String对象,将所有匹配器的文本字符(最多匹配到第一个匹配项)replacement复制到字符串,将字符附加到字符串,将剩余字符复制到字符串,并返回String对象。(replacement字符串可能包含对上一个匹配期间捕获的文本序列的引用,通过美元符号和捕获组号)。
lString replaceAll(String replacement)以类似的方式操作replaceFirst(String replacement),但是替换所有与replacement字符的匹配。
该\s+正则表达式的检测在输入文本中的空白字符的一个或多个事件。下面我们使用这个正则表达式,并调用该replaceAll(String replacement)方法来删除重复的空格:
Pattern p = Pattern.compile("\\s+");
Matcher m = p.matcher("Remove the \t\t duplicate whitespace. ");
System.out.println(m.replaceAll(" "));
这是输出:
Remove the duplicate whitespace.
捕捉面向群体的方法
RegexDemo应用程序的源代码包括m.group()方法调用。该group()方法是几种捕获面向群体的Matcher方法之一:
lint groupCount()以匹配器模式返回捕获组的数量。该计数不包括表示整个模式的特殊捕获组号码0。
lString group()返回上一个匹配的字符。此方法返回一个空字符串,以指示与空字符串成功匹配。IllegalStateException当匹配器尚未尝试匹配或之前的匹配操作失败时抛出。
lString group(int group)类似于以前的方法,除了它返回由group指定的捕获组号记录的先前匹配的字符。注意group(0)相当于group()。如果在模式中没有存在具有指定组号的捕获组,则该方法抛出IndexOutOfBoundsException。IllegalStateException当匹配器尚未尝试匹配或之前的匹配操作失败时,它会抛出。
lString group(String name)返回由named捕获组记录的先前匹配的字符。如果在给定的模式中没有捕获组name,IllegalArgumentException则抛出。IllegalStateException当匹配器尚未尝试匹配或之前的匹配操作失败时抛出。
以下示例演示了groupCount()和group(int group)方法:
Pattern p = Pattern.compile("(.(.(.)))");
Matcher m = p.matcher("abc");
m.find();
System.out.println(m.groupCount());
for (int i = 0; i <= m.groupCount(); i++)
System.out.println(i + ": " + m.group(i));
它产生以下输出:
3
0: abc
1: abc
2: bc
3: c
匹配位置法
Matcher 提供了几种返回匹配的开始和结束索引的方法:
lint start()返回上一个比赛的开始索引。IllegalStateException当匹配器尚未尝试匹配或之前的匹配操作失败时抛出。
lint start(int group)类似于之前的方法,除了它返回与group指定的捕获组相关联的上一个匹配的开始索引。如果没有指定捕获组号的捕获组存在于模式中,IndexOutOfBoundsException则抛出。IllegalStateException当匹配器尚未尝试匹配或之前的匹配操作失败时抛出。
lint start(String name)类似于之前的方法,除了它返回与name指定的捕获组相关联的上一个匹配的开始索引。如果没有指定的捕获组name在模式中存在,IllegalArgumentException则抛出。IllegalStateException当匹配器尚未尝试匹配或之前的匹配操作失败时抛出。
lint end()返回上一个匹配字符的索引加上一个匹配的匹配。IllegalStateException当匹配器尚未尝试匹配或之前的匹配操作失败时抛出。
lint end(int group)类似于之前的方法,除了它返回与group指定的捕获组相关联的上一个匹配的结束索引。如果与指定的组数没有捕获组在图案存在,IndexOutOfBoundsException被抛出。IllegalStateException当匹配器尚未尝试匹配或之前的匹配操作失败时抛出。
lint end(String name)类似于之前的方法,除了它返回与name指定的捕获组相关联的上一个匹配的结束索引。如果没有指定的捕获组name在模式中存在,IllegalArgumentException则抛出。IllegalStateException当匹配器尚未尝试匹配或之前的匹配操作失败时抛出。
以下示例演示了两种匹配位置方法来报告捕获组号2的开始/结束匹配位置:
Pattern p = Pattern.compile("(.(.(.)))");
Matcher m = p.matcher("abcabcabc");
while (m.find())
{
System.out.println("Found " + m.group(2));
System.out.println(" starting at index " + m.start(2) +
" and ending at index " + (m.end(2) - 1));
System.out.println();
}
此示例生成以下输出:
Found bc
starting at index 1 and ending at index 2
Found bc
starting at index 4 and ending at index 5
Found bc
starting at index 7 and ending at index 8
PatternSyntaxException方法
PatternSyntaxException该类的实例描述了正则表达式中的语法错误。此异常从抛出Pattern的compile()和matches()方法,以及通过下面的构造被构造:
PatternSyntaxException(String desc, String regex, int index)
构造函数存储指定description,regex和index其中在发生语法错误regex。将index被设置为-1当语法错误的位置是未知的。
虽然您可能永远不需要实例化PatternSyntaxException,但在创建格式化的错误消息时,您将需要提取上述值。调用以下方法来完成此任务:
lString getDescription() 返回语法错误的描述。
int getIndex() 返回发生语法错误的近似索引(在正则表达式中),或者索引未知时返回-1。
lString getPattern() 返回错误的正则表达式。
此外,inherited String getMessage()方法返回一个多行字符串,其中包含从上述方法返回的值以及模式中语法错误位置的可视指示。
什么构成语法错误?以下是一个例子:
java RegexDemo (?itree Treehouse
在这种情况下,我们未能)在嵌入式标志表达式中指定圆括号metacharacter()。该错误导致以下输出:
regex = (?itree
input = Treehouse
Bad regex: Unknown inline modifier near index 3
(?itree
^
Description: Unknown inline modifier
Index: 3
Incorrect pattern: (?itree
使用Regex API构建有用的正则表达式应用程序
正则表达式可让您创建强大的文本处理应用程序。本节介绍一些有用的应用程序,邀请您进一步探索Regex API中的类和方法。第二个应用程序还介绍了Lexan:一个用于执行词法分析的可重用库。
用于文档的正则表达式
文档是开发专业质量软件的必要任务之一。幸运的是,正则表达式可以帮助文档的许多方面。清单1中的代码将包含单行和多行C语言注释的行从一个源文件中提取到另一个源文件。注释必须位于单行上才能使代码生效:
清单1.提取注释
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
public class ExtCmnt
{
public static void main(String[] args)
{
if (args.length != 2)
{
System.err.println("usage: java ExtCmnt infile outfile");
return;
}
Pattern p;
try
{
// The following pattern defines multiline comments that appear on the
// same line (e.g., /* same line */) and single-line comments (e.g., //
// some line). The comment may appear anywhere on the line.
p = Pattern.compile(".*/\\*.*\\*/|.*//.*$");
}
catch (PatternSyntaxException pse)
{
System.err.printf("Regex syntax error: %s%n", pse.getMessage());
System.err.printf("Error description: %s%n", pse.getDescription());
System.err.printf("Error index: %s%n", pse.getIndex());
System.err.printf("Erroneous pattern: %s%n", pse.getPattern());
return;
}
try (FileReader fr = new FileReader(args[0]);
BufferedReader br = new BufferedReader(fr);
FileWriter fw = new FileWriter(args[1]);
BufferedWriter bw = new BufferedWriter(fw))
{
Matcher m = p.matcher("");
String line;
while ((line = br.readLine()) != null)
{
m.reset(line);
if (m.matches()) /* entire line must match */
{
bw.write(line);
bw.newLine();
}
}
}
catch (IOException ioe)
{
System.err.println(ioe.getMessage());
return;
}
}
}
清单1的main()方法首先验证其命令行,然后编译正则表达式以将单行和多行注释定位到Pattern对象中。假设没有PatternSyntaxException出现,main()打开源文件并创建目标文件,获取匹配器以匹配每个读取行与模式,并逐行读取源文件的内容。对于每一行,匹配器尝试将该行与注释模式相匹配。如果有匹配,main()将该行(后跟新行)写入目标文件。(我们将在未来的Java 101教程中探索文件I / O逻辑。)
编译清单1如下:
javac ExtCmnt.java
运行应用程序ExtCmnt.java:
java ExtCmnt ExtCmnt.java out
您应该在out文件中观察以下输出:
// The following pattern defines multiline comments that appear on the
// same line (e.g., /* same line */) and single-line comments (e.g., //
// some line). The comment may appear anywhere on the line.
p = Pattern.compile(".*/\\*.*\\*/|.*//.*$");
if (m.matches()) /* entire line must match */
在".*/\\*.*\\*/|.*//.*$"模式字符串中,垂直条元字符(|)作为逻辑“或”运算符,告诉匹配器使用该运算符的左正则表达式构造操作数来定位匹配器文本中的匹配。如果不存在匹配,则匹配器将在另一匹配尝试中使用该运算符的正则表达式构造操作数。(捕获组中的括号中的元字符形成另一个逻辑运算符。)
用于词法分析的正则表达式
正则表达式更有用的应用是一个可重用的库,用于执行词法分析,这是任何代码编译器或汇编器的关键组件。在这种情况下,输入的字符流被分组成令牌的输出流,其是表示具有集体含义的字符序列的名称。例如,在遇到字母序列c,o,u,n,t,e,r在输入流,词法分析器可能输出令牌ID(标识符)。与令牌相关联的字符序列称为词法。
更多关于令牌和词汇
一个令牌,如ID表示许多字符序列。对于这样的令牌,令牌表示的实际词汇也是依赖于词法分析的编译器,汇编器或其他工具所需要的。对于表示单个字符序列的令牌,例如PLUS仅表示+字符,实际的词法不需要,因为它是由令牌所暗示的。
正则表达式比基于状态的词法分析器更有效率,这些分析器必须用手编写,通常不可重用。基于正则表达式的词法分析器的一个例子是JLex,它是Java的词法生成器,它依赖于正则表达式来指定将输入流分解成令牌的规则。另一个例子是Lexan。
了解Lexan
Lexan是用于词法分析的可重用Java库。它是基于代码中的我思学习网站的使用Java语言编写一个解析器博客系列。该图书馆由以下课程组成,您可以在ca.javajeff.lexan本文的源代码下载包中找到这些课程:
lLexan:词汇分析器
lLexanException:由Lexan构造函数引起的异常
lLexException:在词法分析期间由于语法错误引起的异常
lToken:具有正则表达式属性的名称
lTokLex:一个令牌/ lexeme对
该Lexan(java.lang.Class<?> tokensClass)构造函数创建一个新的词法分析器。它需要一个单一的java.lang.Class对象参数来表示一类static Token常量。使用Reflection API,构造函数将每个Token常量读入Token[]值数组。如果没有Token常数存在,LexanException则抛出。
Lexan 还提供了以下一对方法:
lList<TokLex> getTokLexes()返回这个词汇分析器的TokLexes 列表。
lvoid lex(String str)将输入字符串列入TokLexes 列表。LexException如果遇到不符合任何Token[]数组模式的字符,则会抛出此异常。
LexanException没有提供任何方法,但依赖于其继承getMessage()方法来返回异常的消息。相反,LexException还提供了以下方法:
lint getBadCharIndex() 返回与任何令牌模式不匹配的字符的索引。
lString getText() 返回发生异常时被词汇的文本。
Token覆盖toString()返回令牌名称的方法。它还提供了String getPattern()一种返回令牌的正则表达式属性的方法。
TokLex提供一种Token getToken()返回其令牌的方法。它还提供一种String getLexeme()返回其词法的方法。
示范Lexan
我创建了一个LexanDemo演示库的应用程序。该应用程序包括LexanDemo,BinTokens,MathTokens,和NoTokens类。清单2显示了LexanDemo源代码。
清单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();
}
}
清单2的main()方法调用lex()实用程序方法来通过Lexan来演示词法分析。对这个方法的每个调用都会传递一个Class用于一类令牌和一个字符串进行分析的对象。
该lex()方法首先实例化Lexan类,将Class对象传递给Lexan构造函数。然后它调用字符串Lexan的lex()方法。
如果词法分析成功,Lexan的getTokLexes()方法被调用返回的列表TokLex对象。对于每个对象,TokLex的getToken()方法被调用,返回令牌及其getLexeme()方法被调用返回语义。输出两个值。如果词法分析失败,LexanException或者LexException被抛出并妥善处理。
为简洁起见,我们仅考虑MathTokens组成本应用程序的其余课程。清单3显示了这个类的源代码。
清单3.描述一组小数学语言的令牌
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对象。该对象的构造函数接收一个命名标记的字符串,以及描述属于该标记的所有字符串的正则表达式。基于字符串的令牌名称应该与常量的名称相匹配(为了清楚起见),但这不是强制性的。
s Token列表中Token的常量的位置很重要。Token列表中更高的常量优先于较低的常量。例如,sin遇到时,Lexan选择FUNC而不是ID令牌。如果ID出现之前FUNC,ID将被选中。
编译并运行LexanDemo
本文的源代码下载包含lexan.zip归档,其中包含Lexan的所有发行文件。解压缩此归档,并将当前目录设置为主lexan目录的demos子目录。
如果您使用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
--^
Unexpected character in input: 20
no tokens
该Unexpected character in input: 20消息源于抛出的LexanException,这是由于BinTokens不定义一个Token常量2作为其正则表达式引起的。请注意异常处理程序输出的文字被引用和令人反感的字符的位置。所述no tokens消息源于抛出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;
/**
* A lexical analyzer. You can use this class to transform an input stream of
* characters into an output stream of tokens.
*
* @author Jeff Friesen
*/
public final class Lexan
{
private List<TokLex> tokLexes;
private Token[] values;
/**
* Initialize a lexical analyzer to a set of Token objects.
*
* @param tokensClass the Class object of a class containing a set of Token
* objects
*
* @throws LexanException unable to construct a Lexan object, possibly
* because there are no Token objects in the class
*/
public Lexan(Class<?> tokensClass) throws LexanException
{
try
{
tokLexes = new ArrayList<>();
List<Token> _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("no tokens");
}
catch (IllegalAccessException iae)
{
throw new LexanException(iae.getMessage());
}
/**
* Get this lexical analyzer's list of toklexes.
*
* @return list of toklexes
*/
public List<TokLex> getTokLexes()
{
return tokLexes;
}
/**
* Lex an input string into a list of toklexes.
*
* @param str the string being lexed
*
* @throws LexException unexpected character found in input
*/
public void lex(String str) throws LexException
{
String s = new String(str).trim(); // remove leading whitespace
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(); // remove leading whitespace
index += (t.length() - s.length());
break;
}
}
if (!match)
throw new LexException("Unexpected character in input: " + s, str,
index);
}
}
}
该lex()方法中的代码基于Cogito学习博客“ 写一个Java中的解析器:令牌 ”中提供的代码。查看该帖子,了解Lexan如何利用Regex API进行代码编译。
结论是
正则表达式是每个开发人员需要理解的有用工具。Java的Regex API可以轻松将其集成到应用程序和库中。现在,您已经对正则表达式和这个API有了基本的了解,学习java.util.regexSDK文档可以进一步了解正则表达式和其他API方法。
本文暂时没有评论,来添加一个吧(●'◡'●)