Скачиваний:
273
Добавлен:
15.06.2014
Размер:
3.05 Mб
Скачать

Лексический анализ и регулярные грамматики

Принципы построения лексических анализаторов. Регулярные множества и регулярные выражения. Свойства регулярных языков. Способы задания регулярных языков. Свойства регулярных языков. Теорема Клини. Лемма о разрастании языка. Преобразование регулярной грамматики к автоматному виду. Преобразование регулярной грамматики к регулярному выражению

Лексический анализатор –часть компилятора, которая читает исходную программу и выделяет в ее тексте лексемы входного языка. Это необязательная часть компилятора, поскольку все его функции могут быть выполнены на этапе синтаксического анализа. Однако практически все компиляторы имеют в своем составе лексический анализатор по следующим причинам:

-лексический анализатор структурирует поступающий исходный текст программы и устраняет избыточную, ненужную информацию, что упрощает структуру синтаксического анализатора;

-для выделения и разбора лексем возможно использовать простую, эффективную и теоретически хорошо проработанную технику анализа, тогда как на этапе синтаксического анализа используются более сложные алгоритмы разбора;

-лексический анализатор позволяет отстранить синтаксический анализатор от работы с исходным кодом, и при модифи-

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

Какие конкретно функции выполняет лексический анализатор и какие типы лексем он должен выделять, решают разработчики компилятора. Как правило это: устранение комментариев, незначащих пробелов и иных незначащих символов, выделение лексем следующих типов: идентификаторы, константы, ключевые слова, знаки операций и разделители.

Лексический анализ – это процесс предварительной обработки исходной программы, на котором основные лексические единицы программы – лексемы – приводятся к единому формату и заменяются условными кодами или ссылками на соответствующие таблицы.

Результат работы ЛА – поток образов лексем-дескрипторов и таблицы, в которых хранятся значения выделенных в программе лексем. Дескриптор – это пара вида (<тип лексемы>,<указатель>), где тип лексемы – это как правило числовой код класса лексемы, а указатель – это либо начальный адрес области памяти, в которой хранится адрес этой лексемы, либо число, адресующее элемент таблицы, в которой хранится значение лексемы. В общем случае все выделяемые классы являются либо конечными – ключевые слова, разделители, - это классы фиксированных слов для данного ЯП, либо же бесконечными (или очень большими) – идентификаторы, константы, метки - это классы переменных слов для данного ЯП. Коды дескрипторов из конечных классов всегда одни и те же для данного компилятора независимо от исходной программы. Коды дескрипторов из бесконечных классов различны для разных программ. В процессе ЛА значения лексем из бесконечных классов помещаются в таблицы соответствующих классов. Числовые константы перед их помещением в таблицы могут переводиться из символьного во внутреннее машинное представление. Пример:

 

long factorial (long x)

 

 

 

 

 

 

 

{if(x==1) return x;

 

 

 

 

 

 

 

return factorial(x-1)*x;}

 

 

 

 

 

таблицы конечных классов:

 

 

 

таблицы бесконечных классов

(внутренние таблицы ЛА)

 

 

 

(формируемые таблицы ЛА)

01. Ключевые слова

 

02. Разделители

 

03. Идентификаторы

 

№ п/п

 

Ключевое слово

 

№ п/п

Разделитель

 

№ п/п

Идентификатор

 

1

 

long

 

1

(

 

1

factorial

 

2

 

if

 

2

)

 

2

x

 

3

 

return

 

3

{

 

 

 

 

 

 

4

==

 

04. Константы

 

 

 

 

 

5

;

 

№ п/п

Значение

 

 

 

 

 

6

-

 

1

1

 

 

 

 

 

7

*

 

 

 

 

 

 

 

 

8

}

 

 

 

 

 

 

 

 

 

 

 

 

Выходной поток дескрипторов (таблица лексем): (01,1)(03,1)(02,1)(01,1)(03,2)(02,2) (02,3)(01,2)(02,1)(03,2)(02,4)(04,1)(02,2)(01,3)(03,2)(02,5) (01,3)(03,1)(02,1)(03,2)(02,6)(04,1)(02,2)(02,7)(03,2)(02,5)(02,8)

Язык лексем может быть описан с помощью регулярных грамматик, распознавателями для которых являются конечные автоматы. КА определяет, принадлежит ли заданная входная цепочка символов языку, определяемому автоматом. Помимо этой задачи, ЛА должен уметь определять границы лексем, которые в тексте явно не указаны, и должен сохранять информацию об обнаруженной лексеме, т.е. запись найденной лексемы в таблицу лексем, поиск найденной лексемы в таблице идентификаторов и запись в нее. Определение границ лексем – не такая тривиальная задача, например, выражение x+++y можно трактовать двояко: (х++)+у либо х+(++у).

Выходной поток с ЛА поступает на вход СА. Имеется два варианта организации связи ЛА и СА:

-раздельный (последовательный), когда выходной поток ЛА формируется полностью и затем передается СА;

-нераздельный (параллельный), когда СА вызывает ЛА если ему требуется очередной образ лексемы.

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

ЛА выделяет очередную лексему и передает ее СА. Тот проводит разбор очередной конструкции языка, подтверждает правильность выделенной лексемы и просит следующую. Если же разбор СА оказался ошибочен, то он сообщает ЛА о необходимости повторить выделение лексемы и дополнительно указывает, какую лексему следует ожидать. Так может быть перебрано несколько вариантов выделения лексемы, и только если ни один из них не подошел, генерируется ошибка. С целью упрощения СА и ЛА разработчики компиляторов сознательно отсекают некоторые вполне допустимые но трудноанализируемые цепочки, что в ряде случаев позволяет использовать последовательную схему взаимодействия.

Регулярные множества.

Регулярные грамматики служат для формального определения регулярных языков: язык называется регулярным, если он может быть порожден регулярной грамматикой. Регулярный язык L в некотором алфавите А представляет собой регулярное множество строк.

Пусть дан алфавит А и P A*, Q A*. Тогда:

Конкатенацией PQ называется PQ={pq | p P, q Q};

Итерацией P* называется P*P ={p | p P};

Тогда для алфавита А регулярные множества определяются рекурсивно:

1.- РМ;

2.{λ} – РМ;

3.{a} – РМ a A;

4.если P и Q произвольные РМ, то множества P Q, PQ, P* - также являются РМ;

5.Ничто другое не является РМ.

Регулярные выражения.

РМ принято обозначать с помощью регулярных выражений. Это удобное средство формального определения регулярных языков. РМ – это множество цепочек, а РВ – это формула, схематично показывающая, как было построено соответствующее ей РМ с помощью допустимых операций. РВ вводятся следующим образом:

1.0 – РВ, обозначающее ;

2.λ - РВ, обозначающее {λ}

3.а – РВ, обозначающее {a} a A;

4.если p и q – РВ, обозначающие РМ P и Q, то p+q (p|q), pq, p* - РВ, обозначающие РМ P Q, PQ, P*P соответственно. Приоритеты операций: итерация, конкатенация (сцепление), альтернатива (или).

Два РВ α,β равны, α=β, если они обозначают одно и то же РМ. Каждое РВ обозначает только одно РМ, но для одного РМ может существовать сколь угодно много РВ. Пример РМ и РВ:

РМ

{01}

{0,1}

 

{1}*

{0,1}*

{0}{1}*

{0,{1}*}

{0,{1}{0}*}

{0,1}*{011}

{{a}*{b},{c}{a}*}

РВ

01

 

0|1

 

1*

(0|1)*

01*

0|1*

(0|(1(0*)))=0|10*

(0|1)*011

a*b|ca*

Пр.

01

 

0,1

 

1,111

0,1,010

0,01,011

0,1,1111

0,1,10,10000

011010011

b,ab,aaab,c,ca,caaa

Свойства РВ.

 

 

 

 

 

 

 

 

 

№ п/п

 

Свойство

 

 

 

№ п/п

Свойство

 

 

 

 

1

 

 

α|β=β|α

 

 

 

10

(α*)*=α*

 

 

 

 

2

 

 

0*=λ

 

 

 

11

α|α=α

 

 

 

 

3

 

 

α|(β|γ)=(α|β)|γ

 

 

12

α|0=α

 

 

 

 

4

 

 

α(βγ)=(αβ)γ

 

 

 

13

α*α*=α*

 

 

 

 

5

 

 

α(β|γ)=αβ|αγ

 

 

 

14

αα*=α*α

 

 

 

 

6

 

 

(α|β)γ=αγ|βγ

 

 

 

15

(α*|β*)*=(α*β*)*=(α|β)*

 

 

 

7

 

 

αλ=λα=α

 

 

 

16

(αβ)*α=α(βα)*

 

 

 

8

 

 

0α=α0=0

 

 

 

17

(α*β)*α*=(α|β)*

 

 

 

9

 

 

α*=α|α*=αα*|λ

 

 

18

(α*β)*=(α|β)*β|λ

 

 

 

На основе РВ можно построить уравнения с регулярными коэффициентами. Простейшие УРК будут выглядеть:

Х=αХ|β; Х=Хα|β; где α,β A*, Х A.

Решением таких уравнений будут РМ. Т.е., если взять РМ, являющееся решением уравнения, обозначить его в виде РВ и

подставить в уравнение, получим тождество. Решение 1 уравнения будет α*β: αХ|β=α(α*β)|β=(αα*)β|β=(αα*)β|λβ=(αα*|λ)β=α*β=X, решением 2 уравнения будет X=βα*. Для уравнений вида Х=αХ; Х=Хα

решением будет Х=α*, уравнение X=β само по себе является решением.

Некоторые свойства регулярных языков

Множество называется замкнутым относительно некоторой операции, если в результате выполнения этой операции над любыми элементами, принадлежащими данному множеству, получается новый элемент, принадлежащий тому же множеству. Регулярные множества замкнуты относительно операций: пересечения, объединения, дополнения, итерации, конкатенации, гомоморфизма (изменения имен символов и подстановки цепочек вместо символов). Т.е. если L1 и L2 – регулярные языки, то замыкание Клини L1*, сцепление L1L2 и объединение L1 L2 – тоже регулярные языки.

Теорема Клини. Каждому регулярному языку из А* соответствует регулярное выражение над множеством А.

Лемма о разрастании языка. (лемма о накачке) Пусть L – регулярный язык: α L, δ,β,γ V*, р Ν>0 | α=δβγ, |α|p, 0<|β|p, α’ = δβiγ, i Ν≥0, α’ L. В любой достаточно длинной строке регулярного языка всегда можно найти непустую

подстроку, повторение которой произвольное кол-во раз порождает новые строки того же языка. Если для данного языка выполняется эта лемма, то он регулярный, ели не выполняется, то он нерегулярный. Например, язык L={0m1n | m,n0} регулярный, а язык L={0n1n | n1} нерегулярный.

Доказано, что для регулярных языков разрешимы следующие проблемы:

-проблема эквивалентности. Даны два регулярных языка L1(A) и L2(A). Существует алгоритм проверки их на эквивалентность.

-Проблема принадлежности цепочки языку. Дан регулярный язык L(A) и цепочка α А*. Существует алгоритм проверки цепочки на принадлежность языку.

-Проблема пустоты языка. Дан регулярный язык L(A). Существует алгоритм проверки, является ли этот язык пустым, т.е.

существует ли хотя бы одна цепочка α≠λ, α L(A).

Регулярные (праволинейные и леволинейные) грамматики , конечные автоматы и регулярные множества (регулярные выражения) – это три способа задания регулярных языков.

Преобразование регулярной грамматики к автоматному виду.

Имеется регулярная грамматика G(T,N,P,S), надо преобразовать ее в почти эквивалентную автоматную грамматику G’(T’,N’,P’,S’). Будем использовать леволинейную грамматику. Алгоритм преобразования:

1)A N, N’=N’ {A}. Т.е. все нетерминальные символы грамматики G переносятся в новую грамматику G’.

2)a T, T’=T’ {a}. Т.е. все терминальные символы грамматики G переносятся в новую грамматику G’.

3)pi P: АВa1, либо Аa1, A,B N, a1 T P’=P’ {pi}. Т.е. правила этого вида переносятся в G’ без изменений.

4)pi P: АВa1a2…an, n>1, A,B N, ak T, k=1,2,…,n

N’=N’ {A1,A2,…,An-1}, P’=P’ {AkAk-1ak, k=1,2,…,n, где An=A, A0=B}. Добавляется n-1 нетерминал и n новых правил.

5)pi P: Аa1a2…an, n>1, A N, ak T, k=1,2,…,n

N’=N’ {A1,A2,…,An-1}, P’=P’ {AkAk-1ak, k=1,2,…,n, где An=A, A0=λ}. Добавляется n-1 нетерминал и n новых правил.

6)pi P: АB, либо А→λ, A,B N P’=P’ {pi}. Т.е. правила этого вида переносятся в G’ без изменений.

7)pi P’: АB, и BC | BCa | Ba | B→λ, A,B,C N’, a T’ P’=P’ {AC}|{ACa}|{Aa}|{A→λ} соответст-

венно. P’=P’\{АB}. Т.е. правило заменяется “синонимами” и удаляется.

8)pi P’: А→λ, и BA | BAa, A,B, N’, AS, a T’ P’=P’ {B→λ}|{Ba} соответственно. P’=P’\{А→λ}. Т.е. пра-

вило заменяется “синонимами” и удаляется.

9)Если шаги 7,8 были выполнены хотя бы для одного правила, то вернуться к шагу 7

10)S’=S. Т.е. аксиома сохраняется.

Если грамматика не содержит цепных правил вида АB, либо А→λ то шаги 6-9 не выполняются. Как правило, реальные регулярные грамматики не содержат цепных правил.

Пример. Грамматика описывает строковые выражения, соответствующие комментариям в С.

G({/,*,a,,},{S,C,K},P,{S}), здесь - символ перевода строки (chr(13)), - символ возврата каретки (chr(10)), a – любой символ, кроме /,*,,. Парой ,в текстовых файлах отмечается конец строки.

P:S C*/ | K↓←

C /* | C/ | C* | Ca | C↓← K // | K/ | K* | Ka

1. N’={S,C,K}

2. T’={/,*,a,,}

3. P’=P’ { C C/ | C* | Ca, K K/ | K* | Ka }

4. АВa1a2…an, N’=N’ {A1,A2,…,An-1}, P’=P’ {AkAk-1ak, k=1,2,…,n, где An=A, A0=B}.

4.1. S C*/ : N’=N’ {A1}, AkAk-1ak, An=A, A0=B : A1A0*, A2A1/ A1C*, SA1/ P’=P’ { A1C*, SA1/}. 4.2. S K↓← : N’=N’ {A2}, P’=P’ { A2K, SA2}.

4.3. C C↓← : N’=N’ {A3}, P’=P’ { A3C, CA3}.

5. Аa1a2…an, N’=N’ {A1,A2,…,An-1}, P’=P’ {AkAk-1ak, k=1,2,…,n, где An=A, A0=λ}.

5.1.C /* : N’=N’ {A4}, AkAk-1ak, An=A, A0=λ : A1A0/, A2A1* A4/, CA4* P’=P’ { A4/, CA4*}.

5.2.K // : N’=N’ {A5}, P’=P’ { A5/, KA5/}.

6-9. Правил вида АB, либо А→λ нет

10. S’={S}

G’=({/,*,a,,}, {S,C,K,A1,A2,A3,A4,A5}, P’, S)

G’=({/,*,a,,}, {S,C,K,A,B,D,E}, P’, S)

P’: S A1/ | A2

P’= S A/ | B

C C/ | C* | Ca | A3| A4*

C C/ | C* | Ca | D| E*

K K/ | K* | Ka | A5/

K K/ | K* | Ka | E/

A1 C*

A C*

A2

K

B K

A3

C

D C

A4

/

E /

A5

/

 

Построение регулярного выражения, соответствующего регулярной грамматике. В общем виде алгоритм состоит из двух частей: строится система уравнений с регулярными коэффициентами; решается полученная система, решение, соответствующее аксиоме грамматики и будет искомым регулярным выражением:

1.Переименовываются нетерминальные символы грамматики: N={X1, X2, … Xn}, соответственно правила грамматики переписываются в виде: Xi Xjγ, Xi → γ, Xi, Xj N, γ T*. Для праволинейной грамматики нетерминалы и терминальные цепочки в правой части меняются местами.

2.Строится система УРК:

X1 = α01 + X1α11 + X2α21 + … + Xnαn1

X2 = α02 + X1α12 + X2α22 + … + Xnαn2

Xn = α0n + Xnα1n + X2α2n + … + Xnαnn

Коэффициенты αji выбираются следующим образом: αji = (γ1 | γ2 | … | γm) | p P: Xi Xjγ1 | Xjγ2 | … | Xjγm , i>0, j0, X0 = λ. Если правил такого вида в грамматике не существует, то αji= . Для праволинейной грамматики нетерминалы и терминальные цепочки в правой части меняются местами.

3. Решается система уравнений:

3.1.i=0;

3.2.i++; Уравнение для Xi переписывается в виде Xi = Xiαii + βi , βi =αi0 + Xi+1αii+1 + … + Xnαin

3.3.Находится решение уравнения в виде Xi = βiαii* =(αi0 + Xi+1αii+1 + … + Xnαin)αii* , для праволинейной грамматики решение будет в виде Xi = αii*βi.Следует отметить, что если i=n, то решение для Xn будет конечным, т.е. αii и βi не будут содержать в

своем составе переменных Xm.

3.4.k | i<kn, в уравнениях для Xk выполняется подстановка вида Xi → βiαii*.

3.5.Если i<n, перейти к шагу 3.2.

3.6.i--

3.7.k | i<kn, в уравнениях для Xi выполняется подстановка окончательных решений для Xk .

3.8.Если i>1, перейти к шагу 3.6.

3.9.Решение для Xь, соответствующего аксиоме грамматики, и будет искомым регулярным выражением. Пример. Грамматика описывает объявление многомерных массивов в С.

G({[,],n,i,0},{R,L,K},P,{R}), здесь n – любой символ 1..9, 0 не включается, чтобы исключить объявления вида x[0], i – идентификатор.

P:R L]

L L0 | Ln | Kn K i[ | R[

1. R=X1, L=X2, K=X3; P: X1 X2]; X2 X20 | X2n | X3n; X3 i[ | X1[ .

2. X1 = X2]

=

+ X1 + X2]

+ X3

X2 = X20 + X2n + X3n

=

+ X1

+ X2(0+n)

+ X3n

X3 = i[ + X1[

= i[

+ X1[

+ X2

+ X3

3.2.i=1. X1 = X2]; α11= ; β1= X2];

3.3.Решение будет само уравнение X1 = X2].

3.4.В уравнении для Х2 подстановки не требуется. X3 = i[ + X1[ = i[ + X2][

3.2.i=2. X2 = X20 + X2n + X3n = X2(0+n) + X3n; α22=0+n; β2= X3n;

3.3.Решением будет X2 = β2α22* = X3n(0+n)*.

3.4.X3 = i[ + X2][ = i[ + X3n(0+n)* ][ .

3.2.i=3. X3 = i[ + X3n(0+n)* ][ = X3n(0+n)* ][ + i[ ; α33= n(0+n)* ][ ; β3= i[ .

3.3.Решением будет X3 = β3α33* = i[ (n(0+n)* ][)*.

*= i[ (n(0+n)* ][)*n(0+n)*3.7. i=2. X2 = X3n(0+n)

3.7. i=1. X1 = X2] = i[ (n(0+n)* ][)*n(0+n)*]

3.9. Аксиоме соответствует Х1. Следовательно грамматике соответствует регулярное выражение для Х1: = i[(n(0|n)*][)*n(0|n)*].