- •Лекция 5 Характеристика процесса сканирования
- •Построение недетерминированного конечного автомата по расширенному регулярному выражению
- •Преобразование недетерминированного конечного автомата в детерминированный
- •Замечание. Среди состояний могут оказаться недостижимые состояния . Состояние р называется достижимым , если существует такая цепочка w , что (q0, w) *(p , e ).
- •. Представление результатов сканирования
- •Методики конструирования сканеров
Лекция 5 Характеристика процесса сканирования
Лексический анализ ( или сканирование) образует первый этап процесса компиляции. На этом этапе символы, составляющие исходную программу, считываются и группируются в отдельные лексические элементы, называемые лексемами. Лексический анализ важен для процесса компиляции по следующим причинам:
- замена в программе идентификаторов и констант лексемами делает представление программы удобнее для дальнейшей обработки;
- уменьшается длина программы, т.к. из нее устраняются несущественные пробелы и комментарии.
С точки зрения реализации процесса сканирования различают два подхода - прямой и непрямой лексический анализ. При прямом лексическом анализе требуется найти одну из многих лексем, которые заданы в описании данного языка.
Моделью прямого лексического анализатора служит множество работающих параллельно конечных автоматов (КА), каждый из которых распознает лексемы заданного типа. Эти КА можно представить и реализовать как один конечный преобразователь, моделирующий работу всех КА и выдающий сигнал о том, какой из них распознал очередную лексему.
При непрямом лексическом анализе требуется, прочитав цепочку символов, определить, образует ли эта цепочка лексему некоторого конкретного типа. В этом случае сканер работает вместе с синтаксическим анализатором, как некоторая программная процедура SCAN
Синтаксический анализатор обращается к SCAN всякий раз, когда ему нужен новый символ при анализе текста программы и построения ее внутреннего представления. В ответ на вызов, SCAN распознает очередную лексему в исходной программе и передает ее анализатору через таблицу лексем.
Непрямой сканер более экономичен ( в смысле экономии памяти), т.к. он не создает полной таблицы лексем для всего исходного текста программы.
Рис.
Большинство лексем в языках программирования могут быть описаны в виде регулярных выражений, а так же соответствующих регулярных грамматик. Мы говорили о соответствии между регулярными грамматиками и КА. Практическое значение этого соответствия состоит в том, что для распознавания лексем, описываемых регулярными выражениями, можно использовать соответствующие КА.
Распознавание лексем выполняется следующим образом:
- входная цепочка считывается до тех пор, пока КА не достигнет заключительного состояния;
- по достижению заключительного состояния КА сигнализирует о нахождению лексемы данного типа и сканер заносит информацию о ней в таблицу имен (символов).
Таким образом, проблему построения непрямого лексического анализатора для данного типа лексем можно представить как проблему построения и реализации КА, который по достижению заключительного состояния, выдает на выходе лексему ( в этом смысле его можно рассматривать и как конечный преобразователь). В общем случае, такой КА является недетерминированным (НКА), однако, ,НКА можно преобразовать в эквивалентный ему детерминированный КА.
Рассмотрим способы описания лексем.
Построение недетерминированного конечного автомата по расширенному регулярному выражению
Рассмотрим теперь метод получения КА, распознающих лексемы языка, заданные регулярными выражениями.
Вход А1 : расширенное регулярное выражение R в алфавите , не содержащее символа и операций « » « ».
Выход А1 : недетерминированный КА (НКА) - М, для которого L(М)=R.
Описание А1: Получим автомат М0 такой, что L(М0)= R 0 , выполняя рекурсивно следующие действия:
1. Если R0=е, тогда М0=( {q} , , q, {q} ), где q - новое состояние;
2. Если R 0 =а, где a , тогда М0= ( {q1,q2}, ,0 , q1, {q2} ), где 0 (q1,a) = {q2}, в остальных случаях 0 не определена; q1 и q2- новые состояния;
3. Если R0=R1| R2, тогда применяем весь алгоритм к R1 и R2 и получаем соответственно М1= ( Q 1,, 1, q1, F1 ) и
М2=( Q2 , , 2, q2 , F2 ) , где Q1 и Q2 не пересекаются, а затем построим М0=(Q1Q2 {q0}, , 0 ,q0 ,F0), где
а) q0-новый символ;
б) 0 включает 1 и 2, т.е. 0 (q0 ,a)= 1 (q1 ,a) 2 (q2 ,a);
в) F0=F1F2 , если q1 F1 и q2 F2 , в противном случае F0 =F1F2 { q0 }.
4. Если R0=R1 R2 , то применим весь алгоритм к R1 и R2 и получим М1 и М2 , как в п.3 .Построим М0=(Q1 Q2 , , 0 , q1, F0 ) , где
а) 0 включает 2 ; 0 (q ,a)= 1 (q ,a) для всех q Q и a , если q F1, и 0 (q ,a)= 1 (q ,a) 2 (q2 ,a) в противном случае;
б) F0=F2 , если q2 F2 , и F0=F1 F2 в противном случае.
5. Если R0=R1* , то применим весь алгоритм к R1 и получим М1=( Q1 ,, 1 , q1 , F1).
Построим М0=( Q1 {q0}, , 0 , q0 , F1 {q0}), где q0 - новый символ и 0 определяется соотношениями:
а) 0 (q0 ,a)=1 (q1 , a);
б) если q F1 , то 0 (q ,a)=1 (q ,a);
в) если q F1 , то 0 (q ,a)=1 (q,a) 1 (q1 , a).
6. Если R0=R1+ , то применим весь алгоритм к R1 и получим М1, как в п.5. Построим М0=( Q1, , 0 , q1 ,F1), где 0 (q,a)=1(q,a), если q F1 , и 0 (q ,a)=1 (q ,a) 1 (q1 ,a), если q F1.
7. Если R0=R1*n, то применим весь алгоритм к R1 и получим М1 , как в п.5. Построим М0 =(Q1 {1,..., n}, , 0 , [q1 ,1], F0), где
а) если q F1 или i=n, то 0 ([q ,i], a )={[p , i] |1 (q ,a) содержит p};
б) если q F1 и i<n, то 0 ( [ q , i ] , a)={ [p , i] | 1( q , a) содержит p}U{ [p ,i +1] | 1(q1 ,a) содержит p }
в) F0={ [q , i ] | q F1 , 1 i n } U { [q1 ,1] }.
8. Если R0=R1+n , выполним то же , что и в (7) , но пункт (7,в) заменим на F0={ [q , i ] | q F1 , 1 i n }.
Пример . Пусть R = (0 | 1) , преобразуем его в НКА :
1. R можно представить как R = (R1 | R2) , где R1 = 0 , R2 .
2. Применяя п. 2 для R1 и R2 , получаем :
3. Применяя п. 3 к R = (R1 | R2), объединяем состояния автоматов М1 , М2 и получаем результирующий автомат М
Пример . Пусть R = (0 | 1)* , преобразуем его в НКА :
1. Используя результаты , полученные в примере 4.2, и применяя п.5 для R , получим автомат М:
2. Согласно п. 5 , объединяем q1 и q2 и получаем
Пример . Пусть R = (a | b) (a | b |0 | 1)* , выполним его преобразование в НКА :
1. Представим r как R = R1 R2 ,
где R1 = (R3 | R4) , R2 = ( )* , R3 = a , R4 = b ;
= (R5 | R6 | R7 | R8) , R5 = a , R6 = b , R7 = 0 , R8 = 1 ;
2. Автоматы , соответствующие выражениям R1 и R2 легко получить , используя результаты примеров 4.2 , 4.3. Для R1 получим
для R2
3. Применяем п. 4 к выражению R = R1 R2 и получаем результирующий автомат М:
Пример Пусть R = (00 | 11)* , преобразуем его в НКА :
1. Представим R следующим образом :
-
R = R1* , R1 = R2 | R3
R2 = R4 R5 , R3 = R6 R7 ,
R4 = 0 , R5 = 0, R6 = 1 , R7 = 1
2. Для R2 и R3 применяем пп. 2 и 4 ( можно использовать результаты предыдущих примеров ) и получаем автоматы М2 и М3 :
3. Применяя п. 3 к R1 = R2 | R3 , получаем из М2 и М3 результирующий автомат М1 :
4. Применяя п. 5 к R = R1* , получаем из М1 автомат М
Для того, чтобы лучше усвоить и закрепить навыки преобразований, полученные в этих примерах, и использовать их в дальнейшем, приведём соответствия между некоторыми простыми регулярными выражениями и КА: