Как я могу токенизировать ввод, используя класс сканера Java и регулярные выражения?
Просто для своих собственных целей я пытаюсь создать токенайзер в Java, где я могу определить обычную грамматику и сделать так, чтобы она основывалась на вводе токенов. Класс StringTokenizer устарел, и я нашел в Scanner несколько функций, которые подсказывают, что я хочу сделать, но пока не повезло. Кто-нибудь знает хороший способ обойти это?
4 ответа
Название "Сканер" немного вводит в заблуждение, потому что слово часто используется для обозначения лексического анализатора, а это не то, для чего предназначен Сканер. Все это является заменой scanf()
функция, которую вы найдете в C, Perl и соавт. Как StringTokenizer и split()
Он предназначен для сканирования вперед до тех пор, пока не найдет совпадение с заданным шаблоном, а все, что он пропустил по пути, будет возвращено в качестве токена.
Лексический анализатор, с другой стороны, должен исследовать и классифицировать каждый символ, даже если он только решает, может ли он безопасно их игнорировать. Это означает, что после каждого совпадения он может применять несколько шаблонов, пока не найдет тот, который соответствует, начиная с этой точки. В противном случае он может найти последовательность "//" и подумать, что нашел начало комментария, когда он действительно находится внутри строкового литерала и просто не заметил начальную кавычку.
Это на самом деле намного сложнее, чем это, конечно, но я просто иллюстрирую, почему встроенные инструменты, такие как StringTokenizer, split()
и сканер не подходят для такого рода задач. Однако можно использовать классы регулярных выражений 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
Если я хорошо понимаю ваш вопрос, то вот два примера методов токенизации строки. Вам даже не нужен класс Scanner, только если вы хотите предварительно отобрать токены или выполнить их более мягко, чем при использовании массива. Если массива достаточно, просто используйте String.split(), как указано ниже.
Пожалуйста, дайте больше требований, чтобы включить более точные ответы.
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
String textToTokenize = "This is a text that will be tokenized. I will use 1-2 methods.";
Scanner scanner = new Scanner(textToTokenize);
scanner.useDelimiter("i.");
while (scanner.hasNext()){
System.out.println(scanner.next());
}
System.out.println(" **************** ");
String[] sSplit = textToTokenize.split("i.");
for (String token: sSplit){
System.out.println(token);
}
}
}
Большинство ответов здесь уже превосходны, но я был бы упущен, если бы не указал ANTLR. Я создал целые компиляторы вокруг этого превосходного инструмента. Версия 3 имеет несколько удивительных функций, и я рекомендую ее для любого проекта, который требует анализа входных данных на основе четко определенной грамматики.
Если это для простого проекта (для изучения того, как все работает), то следуйте словам Балинта Пато.
Если это для более крупного проекта, рассмотрите возможность использования генератора сканера, такого как JFlex. Несколько сложнее, но быстрее и мощнее.