
- •Семестр 1. Операционные системы
- •Введение
- •История развития ОС
- •Классификация ресурсов
- •Понятие операционной системы
- •Понятие процесса и потока
- •Дескриптор процесса
- •Диспетчеризация процессов
- •Механизмы взаимного исключения
- •Алгоритм Деккера
- •Типовые механизмы синхронизации
- •Поддержка механизмов синхронизации в UNIX
- •Механизмы межпроцессного взаимодействия
- •Барьеры
- •Обработка тупиковых ситуаций
- •Предотвращение взаимоблокировок
- •Управление памятью
- •Инвертированные таблицы страниц
- •Алгоритмы замещения страниц
- •Подсистемы ввода-вывода
- •Загрузка ОС
- •Загрузка ОС
- •Понятие файловой системы
- •Реализация каталогов
- •Совместно используемые файлы
- •Непротиворечивость файловой системы
- •Реализации файловых систем
- •HPFS
- •NTFS
- •Семестр 2. Теория компиляторов
- •Современные системы программирования
- •Литература
- •Формальные языки и грамматики
- •Форма Бэкуса-Наура
- •Классификация грамматик
- •Лексический анализ и регулярные грамматики
- •Лексический анализ
- •Конечные автоматы
- •Минимизация КА
- •КС-грамматики. Приведение КС-грамматик
- •НФ Хомского
- •НФ Грейбах
- •Алгоритмы построения синтаксических анализаторов
- •Синтаксический распознаватель с возвратом
- •Распознаватели без возвратов
- •Формальное определение
- •Ситуация
- •Грамматики предшествования
- •Построение матрицы предшествования
- •Построение матрицы предшествования
- •Генерация кода. Распределение памяти
- •Распределение памяти
- •Дисплей памяти процедуры
- •Синтаксическое дерево
- •Верификация и оптимизация кода
- •Ассемблеры и компоновщики

Лексический анализ и регулярные грамматики
Принципы построения лексических анализаторов. Регулярные множества и регулярные выражения. Свойства регулярных языков. Способы задания регулярных языков. Свойства регулярных языков. Теорема Клини. Лемма о разрастании языка. Преобразование регулярной грамматики к автоматному виду. Преобразование регулярной грамматики к регулярному выражению
Лексический анализатор –часть компилятора, которая читает исходную программу и выделяет в ее тексте лексемы входного языка. Это необязательная часть компилятора, поскольку все его функции могут быть выполнены на этапе синтаксического анализа. Однако практически все компиляторы имеют в своем составе лексический анализатор по следующим причинам:
-лексический анализатор структурирует поступающий исходный текст программы и устраняет избыточную, ненужную информацию, что упрощает структуру синтаксического анализатора;
-для выделения и разбора лексем возможно использовать простую, эффективную и теоретически хорошо проработанную технику анализа, тогда как на этапе синтаксического анализа используются более сложные алгоритмы разбора;
-лексический анализатор позволяет отстранить синтаксический анализатор от работы с исходным кодом, и при модифи-
кации лексики входного языка позволяет достаточно быстро перенастроить компилятор заменив лексический анализатор и не трогая синтаксический А.
Какие конкретно функции выполняет лексический анализатор и какие типы лексем он должен выделять, решают разработчики компилятора. Как правило это: устранение комментариев, незначащих пробелов и иных незначащих символов, выделение лексем следующих типов: идентификаторы, константы, ключевые слова, знаки операций и разделители.
Лексический анализ – это процесс предварительной обработки исходной программы, на котором основные лексические единицы программы – лексемы – приводятся к единому формату и заменяются условными кодами или ссылками на соответствующие таблицы.
Результат работы ЛА – поток образов лексем-дескрипторов и таблицы, в которых хранятся значения выделенных в программе лексем. Дескриптор – это пара вида (<тип лексемы>,<указатель>), где тип лексемы – это как правило числовой код класса лексемы, а указатель – это либо начальный адрес области памяти, в которой хранится адрес этой лексемы, либо число, адресующее элемент таблицы, в которой хранится значение лексемы. В общем случае все выделяемые классы являются либо конечными – ключевые слова, разделители, - это классы фиксированных слов для данного ЯП, либо же бесконечными (или очень большими) – идентификаторы, константы, метки - это классы переменных слов для данного ЯП. Коды дескрипторов из конечных классов всегда одни и те же для данного компилятора независимо от исходной программы. Коды дескрипторов из бесконечных классов различны для разных программ. В процессе ЛА значения лексем из бесконечных классов помещаются в таблицы соответствующих классов. Числовые константы перед их помещением в таблицы могут переводиться из символьного во внутреннее машинное представление. Пример:
|
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,n≥0} регулярный, а язык L={0n1n | n≥1} нерегулярный.
Доказано, что для регулярных языков разрешимы следующие проблемы:
-проблема эквивалентности. Даны два регулярных языка 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’ {Ak→Ak-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’ {Ak→Ak-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, и B→C | B→Ca | B→a | B→λ, A,B,C N’, a T’ P’=P’ {A→C}|{A→Ca}|{A→a}|{A→λ} соответст-
венно. P’=P’\{А→B}. Т.е. правило заменяется “синонимами” и удаляется.
8)pi P’: А→λ, и B→A | B→Aa, A,B, N’, A≠S, a T’ P’=P’ {B→λ}|{B→a} соответственно. 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’ {Ak→Ak-1ak, k=1,2,…,n, где An=A, A0=B}.
4.1. S → C*/ : N’=N’ {A1}, Ak→Ak-1ak, An=A, A0=B : A1→A0*, A2→A1/ A1→C*, S→A1/ P’=P’ { A1→C*, S→A1/}. 4.2. S → K↓← : N’=N’ {A2}, P’=P’ { A2→K↓, S→A2←}.
4.3. C → C↓← : N’=N’ {A3}, P’=P’ { A3→C↓, C→A3←}.
5. А→a1a2…an, N’=N’ {A1,A2,…,An-1}, P’=P’ {Ak→Ak-1ak, k=1,2,…,n, где An=A, A0=λ}.
5.1.C → /* : N’=N’ {A4}, Ak→Ak-1ak, An=A, A0=λ : A1→A0/, A2→A1* A4→/, C→A4* P’=P’ { A4→/, C→A4*}.
5.2.K → // : N’=N’ {A5}, P’=P’ { A5→/, K→A5/}.
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, j≥0, 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<k≤n, в уравнениях для Xk выполняется подстановка вида Xi → βiαii*.
3.5.Если i<n, перейти к шагу 3.2.
3.6.i--
3.7.k | i<k≤n, в уравнениях для 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)*].