Jeff Friesen が javaworld Web サイト用に書いた、Java の正規表現に関する短いガイドの翻訳を紹介します。読みやすくするために、記事をいくつかの部分に分割しました。この部分は最後の部分です。 Java の正規表現、パート 1 Java の正規表現、パート 2 Java の正規表現、パート 3 Java の正規表現、パート 4
正規表現は、手動で記述する必要があり、通常は再利用できない状態ベースの字句アナライザーよりもはるかに効率的です。正規表現ベースの字句アナライザーの例としては、正規表現を使用して入力データ ストリームをトークンに分割するルールを定義する Java 言語の字句ジェネレーターであるJLexがあります。別の例はレキサンです。
字句解析に正規表現を使用する
正規表現のさらに便利なアプリケーションは、コンパイラやアセンブラの重要なコンポーネントである字句解析を実行するための再利用可能なコードのライブラリです。この場合、文字の入力ストリームは、トークン (共通の意味を持つ文字シーケンスの名前) の出力ストリームにグループ化されます。たとえば、入力ストリーム内で文字シーケンスc
、o
、u
、n
、t
、e
、を見つけた場合r
、字句解析プログラムはトークンID
(識別子) を出力できます。トークンに対応する文字のシーケンスは語彙素と呼ばれます。
マーカーと語彙素の詳細 |
---|
ID などのトークンは、多くの文字シーケンスと一致します。このようなトークンの場合、そのトークンに対応する実際のトークンも、字句解析を必要とするコンパイラ、アセンブラ、またはその他のユーティリティで必要になります。PLUS 文字 のみに対応するトークンなど、1 つの特定の文字シーケンスを表すトークンの場合+ 、トークンによって [一意に] 決定できるため、実際のトークンは必要ありません。 |
レキサンを知る
Lexan は、字句解析用の再利用可能な Java ライブラリです。これは、 Cogito Learning Web サイトのブログ投稿シリーズ「Writing a Parser in Java」のコードに基づいています。ライブラリは次のクラスで構成されており、これらはこの記事のダウンロード可能なコードに含まれるパッケージに含まれています。ca.javajeff.lexan
Lexan
: 字句解析器;LexException
: 字句解析中に不正な構文が検出された場合にスローされる例外。Token
: 正規表現属性を持つ名前。TokLex
: トークン/トークンのペア。
LexanException
: クラス コンストラクターで例外がスローされましたLexan;
Lexan(java.lang.Class tokensClass)
新しい字句アナライザーを作成します。java.lang.Class
型定数 class に対応するクラス オブジェクトの形式の引数が 1 つ必要ですstatic Token
。Reflection API を使用して、コンストラクターはすべての定数をToken
値の配列に読み取りますToken[]
。Token
定数がない場合は、例外がスローされますLexanException
。 このクラスは、Lexan
次の 2 つのメソッドも提供します。
- このメソッドは、このレクサーのリストを返します。
List
getTokLexes() Token
Метод void lex(String str)
[結果が配置された] 入力文字列の字句解析を実行して、 type の値のリストを作成しますTokLex
。配列パターンのいずれにも一致しない文字が見つかった場合はToken[]
、例外がスローされますLexException
。
LexanException
メソッドはなく、継承されたメソッドを使用して例外メッセージを返しますgetMessage()
。対照的に、このクラスはLexException
次のメソッドを提供します。
- このメソッドは、
int getBadCharIndex()
どのマーカー パターンにも一致しない文字の位置を返します。 - このメソッドは、
String getText()
例外の生成時に分析されたテキストを返します。
Token
メソッドをオーバーライドしてtoString()
マーカーの名前を返します。String getPattern()
また、トークンの正規表現属性を返すメソッドも提供します。このクラスは、トークンを返すTokLex
メソッドを提供します。また、トークンを返す Token getToken()
メソッドも提供します。String getLexeme()
Lexan ライブラリのデモンストレーション
ライブラリがどのように機能するかを示すために、Lexan
アプリケーションを作成しましたLexanDemo
。LexanDemo
これは、BinTokens
、 、MathTokens
および のクラスで構成されますNoTokens
。アプリケーションのソース コードをLexanDemo
リスト 2 に示します。 リスト 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 のメソッドは、 Lexan を使用した字句解析を示すmain()
ユーティリティを呼び出しますlex()
。このメソッドへの各呼び出しには、オブジェクト内のトークンのクラスClass
と解析する文字列が渡されます。このメソッドは lex()
まず、Lexan
オブジェクトをクラス コンストラクターに渡して、Class
クラスのオブジェクトを作成しますLexan
。そして、その文字列のlex()
クラスメソッドを呼び出しますLexan
。字句解析が成功すると、クラスTokLex
メソッドが呼び出され、オブジェクトのリストが返されます。これらの各オブジェクトについて、そのクラスメソッドが呼び出されてトークンが返され、そのクラス メソッドが呼び出されてトークンが返されます。両方の値が標準出力に出力されます。字句解析が失敗した場合、例外またはのいずれかがスローされ、それに応じて処理されます。簡潔にするために、このアプリケーションを構成するクラスのみを考えてみましょう。リスト 3 にそのソース コードを示します。 リスト 3. 小さな数学言語のトークンのセットの説明getTokLexes()
Lexan
getToken()
TokLex
getLexeme()
LexanException
LexException
MathTokens
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
type の定数のシーケンスを記述していることを示していますToken
。それぞれにオブジェクトの値が割り当てられますToken
。このオブジェクトのコンストラクターは、マーカーの名前である文字列と、そのマーカーに関連付けられたすべての文字列を記述する正規表現を受け取ります。わかりやすくするために、マーカーの文字列名は定数の名前と同じであることが望ましいですが、これは必須ではありません。 マーカーのリスト内の定数の位置は重要です。Token
リストの上位にある定数は、Token
下位にある定数よりも優先されます。たとえば、 に遭遇した場合、Lexan はの代わりにsin
トークンを選択します。マーカーがマーカー の前にあった場合は、そのマーカーが選択されます。 FUNC
ID
ID
FUNC
LexanDemo アプリケーションのコンパイルと実行
この記事のダウンロード可能なコードには、lexan.zip
Lexan ディストリビューションのすべてのファイルを含むアーカイブが含まれています。このアーカイブを解凍し、demos
ルート ディレクトリのサブディレクトリに移動しますlexan
。 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
--^
Неожиданный символ во входном тексте: 20
このメッセージは、クラスが正規表現として値を持つ定数を宣言していないためにНеожиданный символ во входном тексте: 20
例外がスローされた結果として発生します。なお、例外ハンドラはテキストの字句解析から得られた不適切な文字の位置を出力します。トークンが見つからないというメッセージは、クラスで定数が宣言されていないために例外がスローされた結果として表示されます。 LexanException
BinTokens
Token
2
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;
/**
* Лексический анализатор. Этот класс можно использовать для
* преобразования входного потока символов в выходной поток маркеров.
*
* @Author Джефф Фризен
*/
public final class Lexan
{
private List tokLexes;
private Token[] values;
/**
* Инициализируем лексический анализатор набором an objectов Token.
*
* @параметры tokensClass – an object Class класса, содержащего
* набор an objectов Token
*
* @генерирует исключение LexanException в случае невозможности
* формирования an object Lexan, возможно, из-за отсутствия an objectов
* Token в классе
*/
public Lexan(Class tokensClass) throws LexanException
{
try
{
tokLexes = new ArrayList<>();
List _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("маркеры отсутствуют");
}
catch (IllegalAccessException iae)
{
throw new LexanException(iae.getMessage());
}
/**
* Получаем список TokLex'ов этого лексического анализатора.
*
* @возвращает список TokLex'ов
*/
public List getTokLexes()
{
return tokLexes;
}
/** * Выполняет лексический анализ входной строки [с помещением * результата] в список TokLex'ов. * * @параметры str – строка, подвергаемая лексическому анализу * * @генерирует исключение LexException: во входных данных обнаружен * неожиданный символ */
public void lex(String str) throws LexException
{
String s = new String(str).trim(); // удалить ведущие пробелы
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(); // удалить ведущие пробелы
index += (t.length() - s.length());
break;
}
}
if (!match)
throw new LexException("Неожиданный символ во входном тексте: "
+ s, str, index);
}
}
}
メソッド コードは、 Cogito Learning Web サイトのブログ投稿「Java でのパーサーの作成: トークン ジェネレーター」lex()
で提供されているコードに基づいています。Lexan が Regex API を使用してコードをコンパイルする方法について詳しくは、この投稿をお読みください。
結論
正規表現は、あらゆる開発者にとって役立つ便利なツールです。Java プログラミング言語の Regex API を使用すると、アプリケーションやライブラリで簡単に使用できるようになります。正規表現とこの API についてはすでに基本を理解しているので、SDK ドキュメントを参照して、java.util.regex
正規表現と追加の Regex API メソッドについてさらに詳しく学習してください。
GO TO FULL VERSION