Przedstawiamy Państwu tłumaczenie krótkiego przewodnika po wyrażeniach regularnych w Javie, napisanego przez Jeffa Friesena dla serwisu javaworld. Dla ułatwienia czytania artykuł podzieliliśmy na kilka części. Ta część jest ostateczna. Wyrażenia regularne w Javie, część 1 Wyrażenia regularne w Javie, część 2 Wyrażenia regularne w Javie, część 3 Wyrażenia regularne w Javie, część 4
Wyrażenia regularne są znacznie wydajniejsze niż analizatory leksykalne oparte na stanach, które należy pisać ręcznie i zazwyczaj nie można ich ponownie wykorzystać. Przykładem analizatora leksykalnego opartego na wyrażeniach regularnych jest JLex , generator leksykalny dla języka Java, który używa wyrażeń regularnych do definiowania reguł dzielenia wejściowego strumienia danych na tokeny. Innym przykładem jest Lexan.
Wykorzystanie wyrażeń regularnych do analizy leksykalnej
Jeszcze bardziej użytecznym zastosowaniem wyrażeń regularnych jest biblioteka kodu wielokrotnego użytku do przeprowadzania analizy leksykalnej, kluczowego elementu każdego kompilatora lub asemblera. W tym przypadku wejściowy strumień znaków grupowany jest w wyjściowy strumień tokenów – nazw ciągów znaków mających wspólne znaczenie. Na przykład, po natknięciu się na sekwencję znakówc
, o
, u
, n
, t
, e
, r
, w strumieniu wejściowym, analizator leksykalny może wyprowadzić token ID
(identyfikator). Ciąg znaków odpowiadający tokenowi nazywany jest leksemem.
Więcej o markerach i leksemach |
---|
Tokeny takie jak ID mogą pasować do wielu sekwencji znaków. W przypadku takich tokenów rzeczywisty token odpowiadający tokenowi jest również potrzebny kompilatorowi, asemblerowi lub innemu narzędziu wymagającemu analizy leksykalnej. W przypadku tokenów, które reprezentują jedną konkretną sekwencję znaków, np. tokenu PLUS odpowiadającego tylko znakowi + , rzeczywisty token nie jest wymagany, ponieważ może być [jednoznacznie] określony przez token. |
Poznajemy Lexana
Lexan to biblioteka Java wielokrotnego użytku do analizy leksykalnej. Opiera się na kodzie z serii wpisów na blogu Pisanie parsera w Javie na stronie Cogito Learning . Biblioteka składa się z następujących klas, które znajdują się w pakiecieca.javajeff.lexan
zawartym w kodzie do pobrania dla tego artykułu:
Lexan
: analizator leksykalny;LexException
: wyjątek zgłoszony w przypadku wykrycia nieprawidłowej składni podczas analizy leksykalnej;Token
: nazwa z atrybutem wyrażenia regularnego;TokLex
: token/para tokenów.
LexanException
: wyjątek zgłoszony w konstruktorze klasyLexan;
Lexan(java.lang.Class tokensClass)
tworzy nowy analizator leksykalny. Wymaga jednego argumentu w postaci obiektu klasy java.lang.Class
odpowiadającego typowi stałej class static Token
. Korzystając z interfejsu API Reflection, konstruktor wczytuje wszystkie stałe Token
do tablicy wartości Token[]
. Jeśli Token
nie ma stałych, zgłaszany jest wyjątek LexanException
. Klasa Lexan
udostępnia także dwie następujące metody:
- Metoda zwraca listę tego leksykonu;
List
getTokLexes() Token
Метод void lex(String str)
przeprowadza analizę leksykalną ciągu wejściowego [z wynikiem umieszczonym] na liście wartości typuTokLex
. Jeśli napotkany zostanie znak, który nie pasuje do żadnego ze wzorców tablicyToken[]
, zgłaszany jest wyjątekLexException
.
LexanException
nie ma metod; używa metody dziedziczonej w celu zwrócenia komunikatu o wyjątku getMessage()
. Natomiast klasa LexException
udostępnia następujące metody:
- Metoda
int getBadCharIndex()
zwraca pozycję znaku, który nie pasuje do żadnego ze wzorców znaczników. - Metoda
String getText()
zwraca tekst, który był analizowany podczas generowania wyjątku.
Token
zastępuje metodę toString()
, aby zwrócić nazwę znacznika. Udostępnia również metodę String getPattern()
zwracającą atrybut wyrażenia regularnego tokenu. Klasa TokLex
udostępnia metodę Token getToken()
zwracającą swój token. Zapewnia również metodę String getLexeme()
zwracającą swój token.
Demonstracja biblioteki Lexan
Aby zademonstrować działanie biblioteki,Lexan
napisałem aplikację LexanDemo
. Składa się z klas LexanDemo
, BinTokens
i MathTokens
. NoTokens
Kod źródłowy aplikacji LexanDemo
pokazano na Listingu 2. Listing 2. Demonstracja biblioteki Lexan w akcji
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();
}
}
Metoda main()
z Listingu 2 wywołuje narzędzie lex()
demonstrujące analizę leksykalną przy użyciu Lexana. Do każdego wywołania tej metody przekazywana jest klasa tokenów w obiekcie Class
i ciąg znaków do przeanalizowania. Metoda lex()
najpierw tworzy obiekt klasy, Lexan
przekazując obiekt Class
do konstruktora klasy Lexan
. A następnie wywołuje metodę lex()
klasy Lexan
na tym ciągu. Jeśli analiza leksykalna zakończy się pomyślnie, TokLex
wywoływana jest metoda getTokLexes()
klasowa w celu zwrócenia listy obiektów Lexan
. Dla każdego z tych obiektów wywoływana jest jego metoda getToken()
klasowa TokLex
w celu zwrócenia tokenu oraz metoda klasowa getLexeme()
w celu zwrócenia tokenu. Obie wartości są wypisywane na standardowe wyjście. Jeśli analiza leksykalna zakończy się niepowodzeniem, zostanie zgłoszony jeden z wyjątków LexanException
lub i odpowiednio obsłużony LexException
. Dla zwięzłości rozważmy tylko klasę tworzącą tę aplikację MathTokens
. Listing 3 pokazuje jego kod źródłowy. Listing 3. Opis zestawu tokenów dla małego języka matematycznego
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_]*");
}
Listing 3 pokazuje, że klasa MathTokens
opisuje sekwencję stałych typu Token
. Każdemu z nich przypisana jest wartość obiektu Token
. Konstruktor tego obiektu otrzymuje ciąg znaków będący nazwą znacznika wraz z wyrażeniem regularnym opisującym wszystkie ciągi znaków powiązane z tym znacznikiem. Dla przejrzystości pożądane jest, aby nazwa ciągu znacznika była taka sama jak nazwa stałej, ale nie jest to wymagane. Ważna jest pozycja stałej Token
na liście znaczników. Stałe znajdujące się wyżej na liście Token
mają pierwszeństwo przed stałymi znajdującymi się poniżej. Na przykład, podczas spotkania sin
Lexan wybiera żeton FUNC
zamiast ID
. Gdyby znacznik ID
znajdował się przed znacznikiem FUNC
, zostałby wybrany.
Kompilowanie i uruchamianie aplikacji LexanDemo
Kod tego artykułu do pobrania zawiera archiwumlexan.zip
zawierające wszystkie pliki dystrybucji Lexan. Rozpakuj to archiwum i przejdź do podkatalogu demos
katalogu głównego lexan
. Jeśli używasz systemu Windows, uruchom następujące polecenie, aby skompilować pliki kodu źródłowego aplikacji demonstracyjnej:
javac -cp ..\library\lexan.jar *.java
Jeśli kompilacja zakończy się pomyślnie, uruchom następującą komendę, aby uruchomić aplikację demonstracyjną:
java -cp ..\library\lexan.jar;. LexanDemo
Powinieneś zobaczyć następujące wyniki:
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
Komunikat Неожиданный символ во входном тексте: 20
pojawia się w wyniku zgłoszenia wyjątku LexanException
w związku z tym, że klasa BinTokens
nie deklaruje stałej Token
z wartością 2
w postaci wyrażenia regularnego. Należy zauważyć, że procedura obsługi wyjątków wyświetla pozycję niewłaściwego znaku uzyskaną z analizy leksykalnej tekstu. Komunikat o braku tokenów jest wynikiem zgłoszenia wyjątku, LexException
ponieważ w klasie NoTokens
nie zadeklarowano żadnych stałych Token
.
Za kulisami
Lexan
wykorzystuje klasę Lexan jako swój silnik. Przyjrzyj się implementacji tej klasy na Listingu 4 i zwróć uwagę na wkład wyrażeń regularnych w umożliwienie ponownego użycia silnika. Listing 4. Tworzenie architektury analizatora leksykalnego w oparciu o wyrażenia regularne
package ca.javajeff.lexan;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
/**
* Лексический анализатор. Этот класс можно использовать для
* преобразования входного потока символов в выходной поток маркеров.
*
* @Autor Джефф Фризен
*/
public final class Lexan
{
private List tokLexes;
private Token[] values;
/**
* Инициализируем лексический анализатор набором obiektов Token.
*
* @параметры tokensClass – obiekt Class класса, содержащего
* набор obiektов Token
*
* @генерирует исключение LexanException в случае невозможности
* формирования obiektа Lexan, возможно, из-за отсутствия obiektов
* 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);
}
}
}
Kod metody lex()
opiera się na kodzie podanym w poście na blogu „Pisanie parsera w Javie: generator tokenów” na stronie Cogito Learning. Przeczytaj ten post, aby dowiedzieć się więcej o tym, jak Lexan używa API Regex do kompilowania kodu.
Wniosek
Wyrażenia regularne są użytecznym narzędziem, które może przydać się każdemu programiście. Regex API języka programowania Java ułatwia ich użycie w aplikacjach i bibliotekach. Teraz, gdy masz już podstawową wiedzę na temat wyrażeń regularnych i tego interfejsu API, przejrzyj dokumentację zestawu SDK,java.util.regex
aby dowiedzieć się jeszcze więcej o wyrażeniach regularnych i dodatkowych metodach API Regex.
GO TO FULL VERSION