Ipinakita namin sa iyong atensyon ang pagsasalin ng isang maikling gabay sa mga regular na expression sa Java, na isinulat ni Jeff Friesen para sa website ng javaworld. Para sa kadalian ng pagbabasa, hinati namin ang artikulo sa ilang bahagi. Ang bahaging ito ay ang pangwakas. Mga Regular na Expression sa Java, Part 1 Regular Expression sa Java, Part 2 Regular Expression sa Java, Part 3 Regular Expression sa Java, Part 4
Ang mga regular na expression ay mas mahusay kaysa sa state-based lexical analyzer, na dapat na nakasulat sa pamamagitan ng kamay at sa pangkalahatan ay hindi na magagamit muli. Ang isang halimbawa ng isang regular na expression-based lexical analyzer ay ang JLex , isang lexical generator para sa wikang Java na gumagamit ng mga regular na expression upang tukuyin ang mga panuntunan para sa paghahati-hati ng input data stream sa mga token. Isa pang halimbawa ay si Lexan.
Paggamit ng mga regular na expression para sa lexical analysis
Ang isang mas kapaki-pakinabang na aplikasyon ng mga regular na expression ay isang library ng reusable code para sa pagsasagawa ng lexical analysis, isang pangunahing bahagi ng anumang compiler o assembler. Sa kasong ito, ang input stream ng mga character ay pinagsama-sama sa isang output stream ng mga token - mga pangalan para sa mga pagkakasunud-sunod ng mga character na may karaniwang kahulugan. Halimbawa, kapag nakatagpo ng pagkakasunud-sunod ng mga characterc
, o
, u
, n
, t
, e
, r
, sa input stream, ang lexical analyzer ay maaaring mag-output ng token ID
(identifier). Ang pagkakasunud-sunod ng mga character na nauugnay sa token ay tinatawag na isang lexeme.
Higit pa tungkol sa mga marker at lexemes |
---|
Ang mga token gaya ng ID ay maaaring tumugma sa maraming sequence ng character. Sa kaso ng mga naturang token, ang aktwal na token na tumutugma sa token ay kailangan din ng compiler, assembler, o iba pang utility na nangangailangan ng lexical analysis. Para sa mga token na kumakatawan sa isang partikular na pagkakasunud-sunod ng mga character, tulad ng token PLUS na tumutugma lamang sa character + , ang aktwal na token ay hindi kinakailangan dahil maaari itong [natatanging] tinutukoy ng token. |
Kilalanin si Lexan
Ang Lexan ay isang reusable na Java library para sa lexical analysis. Ito ay batay sa code mula sa blog post series na Writing a Parser in Java sa website ng Cogito Learning . Binubuo ang library ng mga sumusunod na klase, na nasa packageca.javajeff.lexan
na kasama sa nada-download na code para sa artikulong ito:
Lexan
: lexical analyzer;LexException
: exception na itinapon kung ang maling syntax ay nakita sa panahon ng lexical analysis;Token
: pangalan na may katangian ng regular na expression;TokLex
: pares ng token/token.
LexanException
: pagbubukod na itinapon sa tagabuo ng klaseLexan;
Lexan(java.lang.Class tokensClass)
создает новый лексический анализатор. Для него требуется один аргумент в виде an object класса java.lang.Class
, соответствующий классу констант типа static Token
. При помощи API Reflection, конструктор читает все константы Token
в массив значений Token[]
. Если констант Token
нет, генерируется исключение LexanException
. Класс Lexan
также предоставляет следующие два метода:
- Метод
List
возвращает списокgetTokLexes() Token
этого лексического анализатора; Метод void lex(String str)
выполняет лексический анализ входной строки [с помещением результата] в список значений типаTokLex
. В случае обнаружения символа, не соответствующего ни одному из шаблонов массиваToken[]
, генерируется исключениеLexException
.
LexanException
методов нет, он использует для возврата messages исключения унаследованный метод getMessage()
. В отличие от него, класс LexException
предоставляет следующие методы:
- Метод
int getBadCharIndex()
возвращает позицию символа, не соответствующего ни одному из шаблонов маркеров. - Метод
String getText()
возвращает анализировавшийся при генерации исключения текст.
Token
переопределяет метод toString()
для возврата названия маркера. Он также предоставляет метод String getPattern()
, возвращающий атрибут регулярного выражения маркера. Класс TokLex
предоставляет метод Token getToken()
, который возвращает его маркер. Он также предоставляет метод String getLexeme()
, который возвращает его лексему.
Демонстрация работы библиотеки Lexan
Для демонстрации работы библиотекиLexan
я написал приложение LexanDemo
. Оно состоит из классов LexanDemo
, BinTokens
, MathTokens
и NoTokens
. Исходный code applications 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();
}
}
Метод main()
из листинга 2 вызывает утorту lex()
для демонстрации лексического анализа при помощи Lexan. При каждом вызове этому методу передается класс маркеров в an objectе Class
и строка, анализ которой необходимо выполнить. Метод lex()
сначала создает an object класса Lexan
, передавая an object Class
конструктору класса Lexan
. А затем он вызывает метод lex()
класса Lexan
для этой строки. В случае успешного выполнения лексического анализа, с целью возврата списка an objectов TokLex
вызывается метод getTokLexes()
класса Lexan
. Для каждого из этих an objectов вызывается его метод getToken()
класса TokLex
для возврата маркера и его метод getLexeme()
для возврата лексемы. Оба значения выводятся в стандартный поток вывода. В случае неудачи лексического анализа генерируется и обрабатывается соответствующим образом одно из исключений LexanException
or LexException
. Для краткости, рассмотрим, из составляющих это приложение, только класс MathTokens
. В листинге 3 показан его исходный code. Листинг 3. Описание набора маркеров для небольшого математического языка
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
описывает последовательность констант типа Token
. Каждой из них присваивается meaning an object Token
. Конструктор этого an object получает строку-название маркера, вместе с регулярным выражением, описывающим все строки символов, относящихся к этому маркеру. Для ясности, желательно, чтобы строковое название маркера совпадало с названием константы, но это не обязательно. Позиция константы Token
в списке маркеров важна. Расположенные выше в списке константы Token
имеют приоритет перед расположенными ниже. Например, встретив sin
, Lexan выбирает маркер FUNC
instead of ID
. Если бы маркер ID
предшествовал маркеру FUNC
, то был бы выбран он.
Компиляция и запуск applications LexanDemo
Загружаемый code для данной статьи включает архивlexan.zip
, содержащий все файлы дистрибутива Lexan. Распакуйте этот архив и перейдите в подкаталог demos
корневого каталога lexan
. Если вы используете Windows, выполните следующую команду для компиляции файлов исходного codeа демо-applications:
javac -cp ..\library\lexan.jar *.java
В случае успешной компиляции выполните следующую команду для запуска демо-applications:
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
со meaningм 2
в качестве регулярного выражения. Обратите внимание на то, что обработчик исключений вывел полученную в результате лексического анализа текста позицию неподходящего символа. Сообщение маркеры отсутствуют получается в результате генерации исключения LexException
, вызванного тем, что в классе NoTokens
не описано ниHowих констант Token
.
За кулисами
Lexan
использует в качестве своего "движка" класс Lexan. Взгляните на реализацию этого класса в листинге 4 и отметьте вклад регулярных выражений в обеспечение возможности повторного использования "движка". Листинг 4. Creation архитектуры лексического анализатора на основе регулярных выражений
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);
}
}
}
Код метода lex()
основан на codeе, приведенном в сообщении блога "Пишем синтаксический анализатор на языке Java: Генератор маркеров" веб-сайта Cogito Learning. Прочитайте это сообщение, чтобы узнать больше о том, How Lexan использует API Regex для компиляции codeа.
Заключение
Регулярные выражения – полезный инструмент, который может пригодиться любому разработчику. API Regex языка программирования Java упрощает их использование в applicationsх и библиотеках. Теперь, когда у вас уже есть базовое представление о регулярных выражениях и этом API, загляните в documentацию SDKjava.util.regex
, чтобы узнать еще больше о регулярных выражениях и дополнительных методах API Regex.
GO TO FULL VERSION