私自身の目的のためだけに、通常の文法を定義し、それに基づいて入力をトークン化できるトークナイザーを Java で構築しようとしています。StringTokenizer クラスは非推奨であり、何をしたいのかを示唆する関数が Scanner にいくつか見つかりましたが、まだ運がありません。これについて良い方法を知っている人はいますか?
4 に答える
「スキャナー」という名前は少し誤解を招く可能性があります。これは、この単語が字句アナライザーを意味するためによく使用されるためですが、スキャナーの目的ではありません。scanf()
それはすべて、C、Perlなどにある関数の代わりになります。StringTokenizerやと同様split()
に、特定のパターンに一致するものが見つかるまで先にスキャンするように設計されており、途中でスキップしたものはすべてトークンとして返されます。
一方、字句解析プログラムは、すべての文字を安全に無視できるかどうかを判断するだけの場合でも、すべての文字を調べて分類する必要があります。つまり、各一致の後に、その時点から一致するものが見つかるまで、いくつかのパターンを適用する場合があります。それ以外の場合は、シーケンス「//」が検出され、コメントの先頭が検出されたと見なされる可能性があります。これは、実際には文字列リテラル内にあり、開始引用符に気付かなかった場合です。
もちろん、実際にはそれよりもはるかに複雑ですが、StringTokenizersplit()
やScannerなどの組み込みツールがこの種のタスクに適していない理由を説明しているだけです。ただし、限定された形式の字句解析にJavaの正規表現クラスを使用することは可能です。実際、Scannerクラスの追加は、それをサポートするために追加された新しいMatcher API、つまりリージョンと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
ここでの回答のほとんどはすでに優れていますが、 ANTLRを指摘しなければ気が済まないでしょう 。この優れたツールを中心にコンパイラ全体を作成しました。バージョン 3 にはいくつかの素晴らしい機能があり、明確に定義された文法に基づいて入力を解析する必要があるプロジェクトには、バージョン 3 をお勧めします。
あなたの質問をよく理解している場合は、文字列をトークン化する 2 つの方法の例を次に示します。トークンを事前にキャストする場合、または配列を使用するよりも厳密に反復する場合にのみ、 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);
}
}
}
これが単純なプロジェクト (物事がどのように機能するかを学ぶため) 用である場合は、Balint Pato が言ったことに従ってください。
これが大規模なプロジェクトの場合は、代わりにJFlexなどのスキャナー ジェネレーターの使用を検討してください。やや複雑ですが、より高速で強力です。