Название «Сканер» немного вводит в заблуждение, потому что слово часто используется для обозначения лексического анализатора, а это не то, для чего предназначен Сканер. Все это является заменой функции scanf()
, которую вы найдете в C, Perl, и др. . Как и StringTokenizer и split()
, он предназначен для сканирования вперед до тех пор, пока не найдет совпадение с данным шаблоном, а все, что он пропустил по пути, будет возвращено в качестве токена.
Лексический анализатор, с другой стороны, должен исследовать и классифицировать каждый символ, даже если он только решает, может ли он безопасно их игнорировать. Это означает, что после каждого совпадения он может применять несколько шаблонов, пока не найдет тот, который соответствует , начиная с этой точки . В противном случае он может найти последовательность «//» и подумать, что нашел начало комментария, когда он действительно находится внутри строкового литерала и просто не заметил начальную кавычку.
Конечно, на самом деле все гораздо сложнее, но я просто иллюстрирую, почему встроенные инструменты, такие как StringTokenizer, split()
и Scanner, не подходят для такого рода задач. Однако можно использовать классы регулярных выражений Java для ограниченной формы лексического анализа. Фактически, добавление класса Scanner сделало его намного проще благодаря новому API Matcher, который был добавлен для его поддержки, то есть регионам и методу usePattern()
. Вот пример элементарного сканера, построенного на основе классов регулярных выражений Java.
import java.util.*;
import java.util.regex.*;
public class RETokenizer
{
static List<Token> tokenize(String source, List<Rule> rules)
{
List<Token> tokens = new ArrayList<Token>();
int pos = 0;
final int end = source.length();
Matcher m = Pattern.compile("dummy").matcher(source);
m.useTransparentBounds(true).useAnchoringBounds(false);
while (pos < end)
{
m.region(pos, end);
for (Rule r : rules)
{
if (m.usePattern(r.pattern).lookingAt())
{
tokens.add(new Token(r.name, m.start(), m.end()));
pos = m.end();
break;
}
}
pos++; // bump-along, in case no rule matched
}
return tokens;
}
static class Rule
{
final String name;
final Pattern pattern;
Rule(String name, String regex)
{
this.name = name;
pattern = Pattern.compile(regex);
}
}
static class Token
{
final String name;
final int startPos;
final int endPos;
Token(String name, int startPos, int endPos)
{
this.name = name;
this.startPos = startPos;
this.endPos = endPos;
}
@Override
public String toString()
{
return String.format("Token [%2d, %2d, %s]", startPos, endPos, name);
}
}
public static void main(String[] args) throws Exception
{
List<Rule> rules = new ArrayList<Rule>();
rules.add(new Rule("WORD", "[A-Za-z]+"));
rules.add(new Rule("QUOTED", "\"[^\"]*+\""));
rules.add(new Rule("COMMENT", "//.*"));
rules.add(new Rule("WHITESPACE", "\\s+"));
String str = "foo //in \"comment\"\nbar \"no //comment\" end";
List<Token> result = RETokenizer.tokenize(str, rules);
for (Token t : result)
{
System.out.println(t);
}
}
}
Кстати, это единственное хорошее применение, которое я когда-либо нашел для метода lookingAt()
. : D