Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
1_Пример проектирования лексического анализатора.doc
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
306.18 Кб
Скачать

11

Пример проектирования лексического анализатора

Пусть {Li | Li A* , i [1,3]} – семейство из трех регулярных языков над входным алфавитом A, где Li – класс лексем языка Си или его ограничение (подмножество) для всех i [1, 3].

Язык L1 – множество идентификаторов языка Си; L2 - множество целочисленных констант языка Си в системе счисления с основанием 8; L3 – множество «пробельных» лексем, интерпретируемых как непустые слова над алфавитом {s, t}. Символы s и t условно обозначают «пробел» и «знак табуляции» соответственно.

Входной алфавит A представляют 256 символов в 8 – разрядной (байтовой) ASCII-кодировке. Числовой код символа «пробел»  0x20 и «знака табуляции»  0x09.

  1. Языки Li индуцируют разбиения i алфавита A, i [1,3]: 1 = {[_a-zA-Z], [0-9], [^_a-zA-Z]}; 2 = {[0], [1-7], [lL], [uU], [^01-7 lL uU]}, 3 = {[\x20\x09], [^\x20\x09]}. Абстрактные алфавиты, соответствующие разбиениям, следующие: B1 = {a, 9, ? }, B2 = {0, 7, l, u, ? }, B3 = {s, ? }, Регулярные выражения еi представляют языки Li в соответствующих абстрактных алфавитах Bi , i [1,3] : e1 = а(а | 9)*, e2 = 0(0 | 7)* (λ|u|l|ul|lu), e3 = ss *.

  2. Эквивалентные λ-диаграммы Di для еi , i [1,3] :

e1 = а (а | 9)*;

а:

(а | 9):

( а | 9)*:

e1 = а (а | 9)*:

λ-диаграммa D1

а

λ

λ

а|9

e2 = 0(0 | 7)* (λ|u|l|ul|lu):

λ-диаграммa D2

e3 = s s*:

λ-диаграммa D3

  1. Детерминированные конечные автоматы Mi, допускающие языки l(Di), I [1,3]. Символом ‘*’ помечены финальные состояния, символом   состояние “ошибки”. M1

Замыкание

q\a

a

9

?

[0]= {0}

0

1

[1]={1, 2, 3} *

1

3

3

[]=

2

[{2,3}]={2,3} *

3

3

3

M2

Замыкание

q\a

0

7

l

u

?

[0]= {0}

0

1

[1]={1,2,3,6} *

1

3

3

4

5

[] =

2

[2]={2,3, 6} *

3

3

3

4

5

[{4,6}]={4,6} *

4

6

[{5,6}]={5,6} *

5

5

[6]= {6} *

6

M3

Замыкание

q\a

s

?

[0]= 0

0

1

2

[1]={1, 2, 3} *

1

3

[] =

2

[{2,3}]={2,3} *

3

3

Языки Li – не пусты, поскольку не пусты множества заключительных состояний автоматов Mi для всех i [1,3] , и λ-свободны, так как начальные состояния не являются заключительными;

  1. Общий алфавит B = {0, 7, 9, a, l, u, s, ?}. Соответствующее разбиение алфавита A   = {[0], [1-7], [89], [_a-km-tv-zA-KM-TV-Z], [lL], [uU], [\x20\x09], [^0-9_a-zA-Z\x20\x09]}.

Элементы алфавитов связаны следующими соотношениями:

элементы B1 = {a, 9, ?} – a = а | l | u,

9 = 0 | 7 | 9,

? = s | ?;

элементы B2 = {0, 7, l, u, ?} – 0 = 0,

7 = 7,

l = l,

u = u,

? = 9 | a | s | ?;

элементы B3 = {s, ?} –

s = s, t = t, ? = 0 | 7 | 9 | а | l | u | ?.

Применяя соотношения как правила подстановки к исходным выражениям, получаем регулярные выражения над общим алфавитом B:

e1 = (а | l | u)(а | l | u | 0 | 7 | 9)* ,

e2 = 0(0 | 7)* (λ|u|l|ul|lu),

e3 = ss*. Чтобы получить регулярные выражения, определяющие языки в исходном алфавите A, необходимо заменить вхождения символов алфавита B в регулярные выражения на выражения, представляющие соответствующие классы разбиения . Например, в языке C# выражения имеют следующие представления в формате строковых констант (литералов): e1  @”([_a-km-tv-zA-KM-TV-Z] | [lL] | [uU])”+ @”( [_a-km-tv-zA-KM-TV-Z] | [lL] | [uU] | [0] | [1-7] | [89])*” ; e2  @”[0]([0] | [1-7])* ( [uU] | [lL] | [uU] [lL] | [lL] [uU])?”;

e3  @” [\x20\x09] [\x20\x09]*”.

Диаграммы Di в алфавитах Bi также просто преобразуются в соответствующие диаграммы над общим алфавитом B: достаточно каждую a-дугу заменить параллельными b-дугами. Например, в диаграмме D1 каждая дуга (i, a, j) заменяется на (i, а | l | u, j), дуга (i, 9, j) – на (i, 0 | 7 | 9, j) и (i, ?, j) – на (i, s | t |?, j).

Приведенные к общему алфавиту B λ-диаграммы Di, i [1,3]:

e1 = (а | l | u)(а| l | u | 0 | 7 | 9)* ;

e2 = 0(0 | 7)* (λ|u|l|ul|lu):

λ-диаграммa D2

e3 = ss* :

λ-диаграммa D3

Детерминированные конечные автоматы Mi , приведенные к общему алфавиту B, i [1,3] . M1

Замыкание

q\a

0

7

9

a

l

u

s

?

[0]= {0}

0

1

1

1

[1]={1, 2, 3 } *

1

3

3

3

3

3

3

[] =

2

[{2,3}]={2,3} *

3

3

3

3

3

3

3

M2

Замыкание

q\a

0

7

9

a

l

u

s

?

[0]= {0}

0

1

[1]={1,2,3,6} *

1

3

3

4

5

[] =

2

[2]={2,3,6} *

3

3

3

4

5

[{4,6}]= {4,6} *

4

6

[{5,6}]= {5,6} *

5

6

[6]= {6} *

6

M3

Замыкание

q\a

0

7

9

a

l

u

s

?

[0]= 0

0

1

[1]={1, 2, 3} *

1

3

[] =

2

[{2,3}]={2,3} *

3

3

Языки Li над общим алфавитом B так же не пусты, поскольку не пусты множества заключительных состояний автоматов Mi для всех i [1,3] , и λ-свободны, так как начальные состояния не являются заключительными.

Декартово произведение двух детерминированных конечных автоматов aвтомат M1,2= M1 x M2 , допускающий объединение исходных языков: L1 L2 . M1,2

Вектор

q\a

0

7

9

a

l

u

s

?

(0,0)

0

1

3

3

3

(2,1) 2

1

4

4

5

6

(2,2)= 

2

(1,2) 1

3

7

7

7

7

7

7

(2,3) 2

4

4

4

5

6

(2,4) 2

5

8

(2,5) 2

6

8

(3,2) 1

7

7

7

7

7

7

7

(2,6) 2

8

  1. Декартово произведение трех детерминированных конечных автоматов aвтомат M= M1 x M2 x M3 , допускающий объединение исходных языков: L1 L2 L3. M

Вектор

q\a

0

7

9

a

l

u

s

?

(0,0,0)

0

1

3

3

3

4

(2,1,2) 2

1

5

5

6

7

(2,2,2)= 

2

(1,2,2) 1

3

8

8

8

8

8

8

(2,2,1) 3

4

9

(2,3,2) 2

5

5

5

6

7

(2,4,2) 2

6

10

(2,5,2) 2

7

10

(3,2,2) 1

8

8

8

8

8

8

8

(2,2,3) 3

9

9

(2,6,2) 2

10

Пусть M =(Q, B , g, q0, F), где F= F1 F2 F3 , F1 = {3, 8}, F2 = {1, 5, 6, 7, 10}, F3 = {4, 8}. Соответствующие автоматы (Q, B , g, q0, Fi) допускают языки Li , i [1,3] .

Cемейство подмножеств {F1, F2, F3} состоит из попарно не пересекающихся множеств, следовательно семейство языков {L1, L2, L3} состоит из трех попарно не пересекающихся регулярных языков;

Автомат M , построенный методом «слияния диаграмм переходов» автоматов M1,2 и M3. Автоматы M и M изоморфны, поскольку их диаграммы переходов изоморфны. M

F

q\a

0

7

9

a

l

u

s

?

0

1

3

3

3

9

2

1

4

4

5

6

2

1

3

7

7

7

7

7

7

2

4

4

4

5

6

2

5

8

2

6

8

1

7

7

7

7

7

7

7

2

8

3

9

10

3

10

10

  1. Классификация состояний и переходов. По построению все состояния достижимы из начального состояния q0.

Множество «тупиковых» или состояний, сигнализирующих о лексической ошибке,  Error ={q| g*(q, x) F}.

В автомате M= M1 x M2 x M3 множество Error = {2}, так как из q2 не достижимо ни одно финальное состояние qF.

Множество активных состояний  Active = Q\ Error. Множество активных переходов  ActiveTransition = {(q, a)| q Active и g(q, a)  Active, a B }. Множество переходов, определяющих условие распознавания лексемы класса Li ,  EndLi = {(q, a)| q Fi и g(q, a)  Error, a B }.

Символ a такой, что (q, a)  EndLi определяет правый контекст лексемы xLi. Слово x переводит автомат из начального состояния в финальное состояние qFi, причем q =g*(q0, x).

Переходы множества ErrorL = {(q, a)| q Active \ F и g(q, a)  Error, a B } определяют условия обнаружения лексической ошибки. Если (q, a)  ErrorL, тогда слово x такое, что q =g*(q0, x) переводит автомат из начального состояния в активное не финальное состояние q, определяет допустимый собственный префикс некоторой лексемы, но

слово xa – не допустимый префикс, так как не существует слова y B*, что xaуL.

Очевидно, ActiveTransition, EndL1 , EndL2 , EndL3 , ErrorL, попарно не пересекающиеся множества переходов.

Семантические процедуры

Определить (классифицировать) множество P «действий» (семантических процедур) значит определить функцию выхода f : QBP, где B= B{#}. Если f(q,a)=p P, тогда p – действие, выполняемое лексическим анализатором в состоянии qQ при входном символе aB. Формально символ # B служит признаком конца входного потока. Фактически результатом проектирования является разработка модели детерминированного конечного преобразователя M = (Q, B, P, f, g) простого типа.

Пусть P = { pActiveTransition, pEndL1, … , pEndLn, pErrorL}. Определим очевидным образом функцию выхода:

f(q,a)=pActiveTransition, если (q,a)ActiveTransition;

если (q,a) EndLi , то (q,#) EndLi , и f(q,a)=pEndLi, если (q,a) EndLi , i [1, n];

если (q,a) ErrorL, то (q,#) ErrorL, и

f(q,a)=pErrorL, если (q,a) ErrorL.

Тогда программная реализация (интерпретация) преобразователя M = (Q, B, P, f, g) определяет основные действия (функции) лексического анализатора при сканировании входного потока символов алфавита B.

Ниже на псевдоязыке приводится в обобщенной форме одна из возможных программных интерпретаций.

Интерпретация символов выходного алфавита P.

pActiveTransition:

s = s + a; //добавить входной символ, расширить префикс некоторой //лексемы

// q1 = <q>

q = g[q,b]; // b = [a] – класс входного символа а  #

// q2 = <q> и (q1, b, q2) in ActiveTransition`

return;

pEndLi , i [1, n]:

//(q, b) in EndLi , i [1, n]

//<s> = x – лексема, b = [a] и <a> = a – правый контекст

tokenStream.WriteLine(“<L”, i, “>”, s);

if (b == [#])

{

//выход

return;

}

else

{

inputStream.UnGetC(a); // вернуть символ <a> = a во входной поток q = 0; // переключить автомат в начальное состояние

s=””; // очистить буфер для накопления символов очередной лексемы

return;

}

pErrorL:

s = s + a;

tokenStream.WriteLine(“<ErrorL>”, s); //s=xa

if (b == [#])

{

//выход

return;

}

else

{

//skip входной символ а, доставивший лексическую ошибку q = 0;

s=””;

return;

}