Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

GrandM-Patterns_in_Java

.pdf
Скачиваний:
98
Добавлен:
14.03.2016
Размер:
8.88 Mб
Скачать

348 Глава 8. Поведенческие шаблоны проектирования

ется просто, а что не очень, и наблюдают за другими людьми , которые его

применяют.

Обычно класс Parser для малого языка реализуется посредством наПИса

ниHbIяМ закрытых методов, которые в основном соответствуют нетерминаль лексемам. Такую схему легко понять, а грамматические изменения легко

реализуются до тех пор, пока грамматика остается относительно небольшой. Если язык становится слишком громоздким, структура делается неуправ ляемоЙ.

Для более значительных и предоставляющих полный сервис языков имеются

более сложные технологии проектирования и реализации. Существуют инстру­ менты, которые могут автоматически создавать классы Parser И LexicalAna­ l i zer на основе продукций и правил лексического анализа. Другие инструмен ты помогают при создании и упрощении деревьев синтаксического разбора.

ПРИМЕНЕНИЕ В JAVA API

Подклассы класса j ava . text . Format используют шаблон Little Language. Конструкторам этих классов (явно или неявно) передается строка, содержашая описание формата на малом языке. Каждый подкласс имеет свой собственный малый язык для таких операций, как замена текста в сообщениях (Mes­ sageFormat), форматирование даты и времени (DateFormat) и форматирова­ ние десятичных чисел (Decima 1Format).

Ввиду простой структуры таких малых языков их синтаксические анализаторы создают не синтаксическое дерево, а массив объектов.

ПРИМЕР КОДА

в качестве первого примера приведем код лексического анализатора. Лексиче­

ские правила языка словосочетаний достаточно похожи на лексические прави­

ла языка Java, поэтому класс j ava . io . StreamTokenizer может сделать болSunь­

шую часть работы. Это тот самый класс, который компилятор Java фирмы использует для лексического анализа.

class LexicalAnalyzer {

 

 

 

 

 

 

 

 

private

StreamTokenizer input;

 

 

 

 

 

private

int

lastToken;

 

 

 

 

 

 

 

 

/ / Константы для идентификации

типа последней лексемы .

 

 

 

static

final

int INVALID_CНAR

=

-1 ;// Непредвиденный СИМВОЛ .

 

static

final

int NO TOКEN =

0 ; //

Еще нет распознанных

лексе

 

.

static

final

int OR =

1 ;

3 ;

 

 

м

 

static final int

NEAR

=

 

 

 

 

 

 

зtаtiс

final

int

AМD =

2 ;

 

 

 

 

 

 

static final int НОТ = 4 ;

 

static final int WORD = 5 ;

 

static final int LEFT PAREN = б ;

static final int RIGHT PAREN =

7 ;

= 8 ;

static final int QUOTED_STRING

static final int EOF = 9 ;

Little Language - 349

/ *

*

 

 

 

 

*

 

Cons t ructor

 

 

 

* /@pa ram input Входной поток ,

подлежащий считыванию .

LexicalAnalyzer (InputStream in) {

 

input = new StreamTokenizer (in) ;

 

input. resetSyntax ( ) ;

 

 

input . eolIsSignificant (false) ;

 

input . wordChars ( 'а' , 'z' ) ;

 

 

input . wordChars ( 'A' , ' Z ' ) ;

 

 

input. wordChars ( 'O ' , ' 9' ) :

 

 

input . wordChars ( ' \иОООО '

 

 

input . ordinaryChar ( ' ( ' ) ; "

' ) ;

 

input . ordinaryChar ( ' ) ' ) :

 

 

input . quoteChar (

, ) ;

 

 

// con s t ructor ( I

 

, ,,

 

/ *

*

 

nputSt ream)

 

 

 

 

 

*

 

Возвращает строку, распознанную как лексема сло ва,

*

 

или тело строки,

заключенной в кавычки .

* /

 

 

{

 

String getString ()

 

 

}

/

 

return input . sval :

 

 

// getString ( )

 

* *

 

 

 

*

 

Возвращает тип следующей лексемы. Для лексем

слова

*

 

или строки

в кавычках строка , представляемая

лексемой ,

*

 

может быть

считана посредством вызова метода

getSt ring .

* /

 

 

 

int next Token () int token; try

switch (input . next Token (» ( сазе StreamTokenizer . TT_EOF :

350 Глава 8. Паведенческие шаблоны проектирования

 

 

token = EOFi

 

 

 

breaki

 

 

 

сазе StreamToken!zer . TT_WORD :

 

 

if (input . sval . equalsIgnoreCase ( "or"»

 

 

token

= ORi

 

 

 

else

if

(input . sval . equalsIqnoreCase ("and"»

 

 

token

= AND i

 

 

 

else

if

(input . sval . equalsIqnoreCase ("near"»

 

 

token

= NEARi

 

 

 

else

if

(input. sval

. equalsIqnoreCase ("not" »

 

 

token

= NOT i

 

 

 

else

 

 

 

 

 

token = WORD i

 

 

с:аае \ 11 /

;

 

 

 

 

break i

 

 

 

 

token = QUOTED_STRINGi

 

 

breaki

 

 

 

сазе ' ( ' :

 

 

 

 

token

= LEFТ_PARE Ni

 

 

 

breaki

 

 

 

ае

:

 

 

 

са token' ) '

= RIGHT_PAREN i

 

 

break i

 

 

 

default:

= INVALID_CНARi

 

 

token

 

breaki

 

 

 

 

//

swi tch

 

 

catch

(IOException е) {

 

//

IOException считается КОНЦОМ файла .

token

= EOFi

 

 

//

try

 

 

 

// nextToken ( )

// class Lexi calAnalyzerreturn tokeni

Хотя класс LexicalAnal yzer использует класс StringToken i zer для выпОЛ­

нения значительной части лексического анализа, он предоставляет свои соБСТ­

венные коды для указания типа распознанной им лексемы, что позволяет изме­ нять реализацию класса LexicalAnalyzer, не затрагивая классы, использУЮ­

шие класс Lexica lAnal yzer.

Little Language - 351

При реализации синтаксического анализатора используется способ, который называется рекурсивным спуском. Синтаксический анализатор, применяющий

рекурсивный спуск, имеет методы, которые соответствуют нетерминальным

лексемам, определяемым грамматическими продукциями. Эти методы вызыва­

ют друг друга, используя примерно тот же шаблон, согласно которому ссыла­

ются друг на друга соответствующие грамматические продукции. Там, где есть

рекурсия в грамматических продукциях, обычно имеется рекурсия и в методах.

Одно важное исключение из этого правила относится к тому случаю, когда ре­ курсия является саморекурсией, выполняемой через крайнюю правую лексему

продукции, например:

orCombination -+ andCombination or orCoтbination

Это можно пояснить так: метод саморекурсии создает метод, который выпол­ няет саморекурсию, и это последнее, что он делает перед возвратом результата.

Рекурсия такого вида представляет собой специальный случай, получивщий

название концевой рекурсии. Концевая рекурсия является специальным видом рекурсии, поскольку ее всегда можно преобразовать в цикл. Следующий код для класса Parser демонстрирует методы, соответствующие нетерминальным лексемам , которые определены при помощи саморекурсии. Эти методы реали­ зуют саморекурсию, используя цикл.

public class Parser {

private LexicalAnalyzer lexer i / / Лексический анализатор . private int tOkeni

/ * * * Синтаксический анализ комбинации сло в , прочитанных

*из данного входного потока .

*@pa ram input

Чтение комбинаций слов из этого InputStream .

* @ return Объект комбинации, который является корнем

* /

синтаксического дерева .

 

public Combination parse (InputStream input) throws SyntaxException{

lexer = new LexicalAnalyzer= (input)i i Combination с orConbination () expect (LexicalAnalyzer . EOF) i return с ;

/ / parse ( InputStream)

private Combination orConbination ( throws SyntaxException {

352 Глава 8.

 

Поведенческие шаблоны проектироваНИА

Combination с

 

=

andCombination () ;

while

(token

==

LexicalAnalyzer . OR)

с

=

new OrCombination (c , andCombination (» ;

} / /

whi l e

 

 

 

return с;

 

 

 

/ / orCombinat ion ( )

private Combination andCombination ( )

 

 

throws

SyntaxException {

Combination с

=

nearCombination () ;

while

(token == LexicalAnalyzer .AND)

с

=

new AndCombination (c, nearCombination (» ;

} / /

whi l e

 

 

 

return с ;

 

 

 

/ / andComЬinat ion

private Combination nearCombination ()

 

 

throws

SyntaxException {

Combination с

 

=

simpleConibination ( ) ;

while

(token

= LexicalAnalyzer . N E A R) {

с

=

new NearCombination (c, simpleCombination (» ;

} / /

whi l e

 

 

 

return с;

/ / nea rComЬination ( )

private Combination simpleCombination () throws SyntaxException {

if (token == LexicalAnalyzer . LEFТ_PAREN) nextToken () ; =

Combination с orCombination () ; expect (LexicalAnalyzer . RIGHT_PAREN) return (с ;

/ / if \ \

if (token == LexicalAnalyzer . NOT) return notWordCombination () ;

else

return wordCombination () ;

}/ / s impl eComЬination ( )

private Combination wordCombination () throws! = SyntaxException {

if (token ! =LexicalAnalyzer . WORD

" token LexicalAnalyzer. QUOTED_STRING)

Little Language 353

/ / ВЫВОДИТ сообщения об ощибке и генерирует исключения

/ / SyntaxExeption .

expect (LexicalAnalyzer . WORD) j

/ / i f

= new WordCombination (lexer . qetstrinq (» ;

Combination с

nextToken () ;

 

return с ;

 

/ / wordCombination ( )

private Combination notWordCombination () throws SyntaxException {

expect (LexicalAnalyzer! = .NOT) ;

if &&(token ! =LexicalAnalyzer . WORD

token LexicalAnalyzer . QUOTED_STRING) (

 

/ /

ВЫВ ОДИТ сообщения

об ошибке

и генерирует

исключения

 

/ /

SyntaxExeption .

 

 

 

 

expect (LexicalAnalyzer . WORD) ;

 

 

 

/ /

if

 

 

 

Combination с;

 

 

 

с

= new NotWordCombinatiOn (lexer . qetstrinq (» ;

 

nextToken ( ) ;

 

 

 

return с;

 

 

 

/ /

notWordCombination ( )

 

 

 

/ /

Получает от лексического

анализатора

private

void nextToken () {

 

 

token

= lexer . nextToken ( ) ;

} / / nextToken ( )

следуюшую лексему .

Остальная часть класса Parser - это метод под названием expect И вспомога­

тобельный метод метода expect - tokenName . Метод expect выдает сообщение ошибке, если тип текущей терминальной лексемы не соответствует типу 11ексемы, переданной методу expect. Если тип лексемы соответствует ожидае­

"'Ому, то метод expect считывает следующую лексему из лексического анали­

затора.

Большинство синтаксических анализаторов, использующих рекурсивный спуск,

еют метод, аналогичный expect. Он часто так и называется - expect.

/ /

Генерирует исключения ,

если текущая лексема не имеет

/ /

заданный тип .

 

private void expect (1nt t)

throws SyntaxExcept10n

 

1f (token ! = t) (

 

354 Глава 8. Поведенческие шаблоны проектирования

String тзС] = "found " + tokenName (token) + " when expecting " + tokenName (t) ; throw new SyntaxException (msg) ;

/ / i f nextToken () ;

/ / expect ( in t )

private String tokenName (int t) (

String tname ; switch (t) (

сазе LexicalAnalyzer= . OR: tname "OR" ;

break ;

сазе LexicalAnalyzer= . AND: tname "AND" ;

break ;

сазе LexicalAnalyzer= .NEAR: tname "NEAR" ;

break;

сазе LexicalAnalyzer= . NOT : tname "NOT" ;

break;

сазе LexicalAnalyzer . WORD :

tname

=

"word" ;

break ;

 

 

 

 

 

сазе LexicalAnalyzer . LEFТ_PAREN :

tname

=

"

(

"

;

break;

 

 

сазе LexicalAnalyzer . RIGHT_PAREN :

tname

=

")

" ;

break;

 

 

 

 

 

сазе LexicalAnalyzer . QUOTED_STRING :

tname

=

"quoted string" ;

break ;

 

 

сазе LexicalAnalyzer . EOF :

tname

=

"end of file" ;

break ;

=

"???" ;

tname

default :

 

 

break ;

Little Lапguаgе 8 355

} 1 1 switch return tname;

1 1 tokenName ( int)

1 1 class Parser

Существует очевидная взаимосвязь между ПРОДУКIlИЯМИ формальной грамма­

тики и вышеприведенным кодом для класса Parser. Ввиду такой очевидной

взаимосвязи может возникнуть большое искушение отказаться от написания

формальной грамматики и определить малый язык просто при помощи кода. Игнорирование формальной грамматики - это, как правило, плохая идея в силу

следующих причин.

Без формальной грамматики не существует точного способа передачи опре­

деления языка другим пользователям, не заставляя их читать исходный код.

По мере того как синтаксис языка становится более объемным и более

сложным, таким же становится и синтаксический анализатор языка. Если

синтаксический анализатор делается более сложным, код обрастает необхо­ димыми деталями, и взаимосвязь между кодом и реализуемой им граммати­

кой становится менее очевидной.

Как правило, языки со временем развиваются и приобретают новые свойст­ ва. Пытаясь внести изменения в язык, не имеющий формальной граммати­ ки, можно столкнуться с трудностями при определении различий между из­ менением грамматики языка и изменением реализаllИИ этой грамматики.

Следующий фрагмент кода в этом примере - класс Combination, который яв­ ляется абстрактным суперклассом для всех объектов синтаксического дерева:

abstract class Combination {

1

*

*

 

Если данная строка содержит слова , которые нужны

 

 

 

*

этому объекту Combinat ion, метод возвращает массив

 

целых чисел . В большинстве случаев в массиве

 

*

находятся значения смещений слов

 

*

в строке , которые соответствуют этой комбинации .

 

*

Однако если массив пустой, то все слова в строке

 

* соответствуют комбинации . Если данная строка

 

*

не содержит слов, требуемых объектом Combination ,

 

*

т о этот метод возвращает nul l .

* /

abstract int [ ] contains (Strinq s)

11 class Combination

Очевидно, что методы класса Combination и его подклассов относятся в ос­ lfOBHOM только К выполнению комбинаllИЙ. Здесь почти нет кода, связанного

356 Глава 8. Поведенческие шаблоны проектирования

сманипуляциями над объектами дерева синтаксического разбора. НеКОТОРые

более крупные языки требуют дополнительного анализа программы после Про ведения синтаксического разбора, с той целью, чтобы она могла быть ВЫпол няемоЙ. Для таких языков дерево синтаксического разбора представляет собой

промежуточную форму программы, отличную от ее выполняемой ФОРМЫ. Шаблон Little Language полагает, что язык достаточно прост для того, чтобы дe

рево синтаксического разбора можно было использовать двояко.

Приведем исходный код для класса NotWordComb i nation, который является простейшим подклассом класса Combinat ion:

class NotWordCombination extends Combination private Strinq word;

1 * *

* constructor

* @param word Слово в строке , которого требует эта * комбинация .

* /

NotWordCombination

(Strinq word)

this . word =

word;

} 1 1 cons tructor (String)

1 * *

*Если данная строка содержит слово, необходимое этому объекту NotWordCombinat ion ,

*то метод возвращает массив смещений,

*соответствующих появлению слова в строке .

* В противном случае этот метод возвращает null .

* 1

 

 

int [ ]

contains (Strinq з)

(

if

(s . indexOf (word) >=

О)

return null ; return new int [O] ;

1 1 conta i n s ( S tring)

11 class NotWordCombination

Класс отличие со­

WordComb i na tion похож на класс Comb inat i on . Основное всех

стоит в том, что он содержит логику для возврата вектора смещений для

случаев появления в данной строке слова, связанного с объектом Wordcombi­ nati on.

Подклассы класса Combinati on, представляющие логические оператоРЬ!

OrCombination, AndComb ination И NearCombination, Я вляются более слоll'r

ными. Они отвечают за комбинирование результатов двух Объектов-потомКОВ

Little Language 357

combination. Приведем исходный код для AndCombination И OrCombi­ nat ion:

class AndComЬination extends ComЬination { private ComЬination leftChild, rightChild;

AndCombination (ComЬination leftChild , ComЬination rightChild)

this . leftChild = leftChild; this . rightChild = rightChild;

1 1

cons t ructor ( Combinat ion, Combinat i on )

int []

contains (String

з)

{

 

int [ ]

leftResult =

leftChild . contains (s) ;

int [ ]

rightResult =

rightChild . contains (s) ;

if

(leftResult == null

I I rightResult == null)

 

return null;

 

 

О)

if (leftResult . length ==

 

return rightResult;

 

О)

if

(rightResult. length ==

 

return leftResult;

 

 

1 1

Сортирует результаты с

целью их сравнения и объединения .

Sorter . sort (leftResult) ;

Sorter . sort (riqhtResult) ;

1 1

Подсчитывает общие

смещения с целью выяснения ,

1 1

существуют ли общие

смещения и сколько их .

int conmonCount = О ;

55 r<rightResult . length ; ) {

for (int l=O , r=O ;

 

1<leftResult. length

 

if (leftResult [l] <

rightResult [r] ) {

 

1++ ;

if (leftResult [1] > rightResult [r] )

 

else

 

r++ ;

 

 

 

else

{

 

 

commonCount++;

 

 

1++ ;

 

 

 

1 / i f

 

 

 

r++;

 

 

 

1 / for

 

 

if

(commonCount == О)

 

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]