JavaのScannerクラスと正規表現を使用して入力をトークン化するにはどうすればよいですか?

StackOverflow https://stackoverflow.com/questions/244115

質問

私自身の目的のために、通常の文法を定義し、それに基づいて入力をトークン化することができるJavaでトークナイザーを構築しようとしています。 StringTokenizerクラスは非推奨になりました。Scannerには、やりたいことを示唆する関数がいくつか見つかりましたが、まだ運はありません。誰でもこれについて良い方法を知っていますか?

役に立ちましたか?

解決

名前「スキャナー」というのは、単語が字句解析器を意味するためによく使われ、それがScannerの目的ではないからです。 C、Perl、 et al にある scanf()関数の代わりになります。 StringTokenizerや split()と同様に、特定のパターンに一致するものが見つかるまで前方にスキャンするように設計されており、途中でスキップされたものはすべてトークンとして返されます。

一方、字句解析器は、すべての文字を安全に無視できるかどうかを判断するだけの場合でも、すべての文字を調べて分類する必要があります。つまり、一致するたびに、その時点から始まるに一致するものが見つかるまで、いくつかのパターンを適用できます。そうでない場合、シーケンス" //"が見つかる可能性があります。そして、それが実際に文字列リテラルの中にあり、開始引用符に気づかなかったときに、コメントの始まりを見つけたと思います。

もちろん、実際にはそれよりもはるかに複雑ですが、StringTokenizer、 split()、スキャナーなどの組み込みツールがこの種のタスクに適していない理由を示しているだけです。ただし、字句解析の限定された形式で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

他のヒント

質問をよく理解している場合、文字列をトークン化する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 のようなスキャナージェネレーターの使用を検討してください。やや複雑ですが、より高速で強力です。

ここでの回答のほとんどはすでに優れていますが、 ANTLR 。この優れたツールを中心にコンパイラ全体を作成しました。バージョン3にはいくつかの素晴らしい機能があり、明確に定義された文法に基づいて入力を解析する必要があるプロジェクトにはお勧めします。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top