Федеральное государственное бюджетное образовательное учреждение высшего профессионального образования “Кубанский государственный технологический университет” В.И. Ключко, А.В. Власенко, Н.В. Кушнир ТЕОРИЯ АВТОМАТОВ И ФОРМАЛЬНЫХ ЯЗЫКОВ Допущено учебно-методическим объединением вузов по университетскому политехническому образованию в качестве учебного пособия для студентов высших учебных заведений, обучающихся по направлениям подготовки бакалавров: 230100 Информатика и вычислительная техника; 231000 Программная инженерия Краснодар 2012 УДК 681.32 ББК 32.973.26 – 018.1 К-52 Ключко В.И., Власенко А.В., Кушнир Н.В. Теория автоматов и формальных языков: учеб. пособие/Кубан. гос. технол. ун-т.- Краснодар: Изд. ФГБОУ ВПО «КубГТУ», 2012.- 151 с. Изложены основные понятия теории автоматов и формальных языков; рассмотрены основы теории конечных автоматов, необходимые для синтеза технических средств вычислительных систем и проектирования трансляторов языков высокого уровня; в работе акцентируется внимание на теории формальных языков и грамматик, как математической основы синтаксического анализа программ; формулируются основные принципы построения трансляторов на базе нисходящих методов обработки языков высокого уровня и различных типов формальных грамматик. В приложениях приведены программы, разработанные в среде Microsoft Visual Studio 2010, C#. Учебное пособие предназначено для студентов высших учебных заведений, обучающихся по направлениям подготовки бакалавров: 230100 Информатика и вычислительная техника; 231000 Программная инженерия. Ил. 34. Табл. 56. Библиогр.: 8 назв. Прил. 6 Рецензенты: д-р техн. наук, проф., проф. кафедры ВТ и АСУ КубГТУ В.И. Лойко; д-р экон. наук, проф., зав. кафедрой СА и ОИ КГАУ Т.П. Барановская © ФГБОУ ВПО «КубГТУ», 2012 © Ключко В.И., Власенко А.В., Кушнир Н.В., 2012 ISBN 2 Содержание Предисловие……………………………………………………………...........6 Введение……………………………………………………………….............7 1 Автоматные модели…………………………………………………..........8 1.1 Определение конечных автоматов……………………………..........8 1.1.1 Формы задания конечных автоматов…………………………....8 1.1.2 Кодирование алфавитов автомата……………………….............9 1.1.3 Виды конечных автоматов……………………………….............9 1.2 Свойства конечных автоматов……………………………………..10 1.2.1 Автоматное отображение………………………………………10 1.2.2 Свойства автоматных отображений……………………............11 1.2.3 Свойства конечных автоматов…………………………………11 1.3 Минимизация конечных автоматов……………………………... ...12 1.3.1 Изоморфизм и эквивалентность автоматов………………… ...12 1.3.2 Недостижимые состояния КА……………………………...... ...13 1.3.3 Эквивалентные состояния КА……………………………….....14 1.4 Структурный синтез конечных автоматов………………………...16 1.4.1 Кодирование состояний конечного автомата………………. ...16 1.4.2 Построение таблицы канонических уравнений……………….17 1.5 Структурный анализ конечных автоматов……………………... ...17 2 Формальные языки и грамматики……………………………………..19 2.1 Формальная порождающая грамматика…………………………...19 2.2 Классификация формальных грамматик………………………......21 2.3 Контекстно-свободные грамматики………………………….........22 2.3.1 Связь КС-грамматик с формами Бэкуса - Наура (БНФ)…… ...22 2.3.2 Выводы………………………………………………………... ...23 2.3.3 Деревья вывода……………………………………………….. ...25 2.3.4 Грамматика для констант языка программирования………. ...26 2.4 Регулярные множества как контекстносвободные языки……... ...27 2.5 Праволинейные грамматики…………………………………….. ...28 2.5.1 Построение конечного распознавателя по заданной грамматике………………………………………………………...28 2.5.2 Праволинейные грамматики………………………………… ...29 2.5.3 Алгоритм преобразования праволинейной грамматики……...31 2.6 Минимизация грамматик………………………………………… ...31 2.6.1 Исключение правил, содержащих бесплодные нетерминалы………………………………………………………………. ...31 2.6.2 Исключение правил, содержащих недостижимые нетерминалы………………………………………………………………....32 3 3 Лексический анализ языков программирования……………………34 3.1 Принцип действия транслятора………………………………….....34 3.1.1 Упрощенная модель транслятора………………………………34 3.1.2 Блоки и проходы транслятора ………………………………….37 3.1.3 Организация рабочей программы……………………………. ...38 3.1.4 Математические модели перевода……………………………...38 3.2 Конечные распознаватели………………………………………... ...38 3.2.1 Определение конечного распознавателя…………………….....38 3.2.2 Концевые маркеры и выходы из распознавания……………. ...40 3.2.3 Получение минимального автомата…………………………. ...41 3.2.4 Недетерминированные автоматы……………………………. ...42 3.2.5 Эквивалентность недетерминированных и детерминированных КР……………………………………………………...43 3.2.6 Расширение КР до преобразователя………………………… ...43 3.3 Реализация конечных распознавателей…………………………. ...47 3.3.1 Введение……………………………………………………….. ...47 3.3.2 Представление входных символов……………………………..48 3.3.3 Представление состояний……………………………………....49 3.3.4 Выбор переходов………………………………………. ……….50 3.3.5 Автоматная идентификация слов……………………………. ...51 3.3.6 Идентификация слов методом индексов……………………. ...54 3.3.7 Идентификация слов методом линейного списка……………..54 3.3.8 Идентификация слов методом упорядоченного списка……. ...55 3.3.9 Идентификация слов методом расстановки (хеширования).....56 4 Синтаксический анализ языков программирования………………..57 4.1 Введение……………………………………………………………..57 4.2 Автоматы с магазинной памятью (МП - автоматы)………………58 4.2.1 Определение МП – автомата………………………………… ...58 4.2.2 Некоторые обозначения для множеств цепочек ……………...60 4.2.3 Распознавание нерегулярных множеств МПА…………….. ...61 4.2.4 Расширение операции под магазином………………………...62 4.2.5 Перевод с помощью МПА………………………………….. ...64 4.3 Транслирующие грамматики…………………………………… ...66 4.4 Синтаксически управляемый перевод…………………………. ...69 4.5. Атрибутные транслирующие грамматики……………………. ...70 4.5.1 Синтезируемые атрибуты…………………………………… ...70 4.5.2 Наследуемые атрибуты………………………………………...73 4.5.3 Перевод арифметических выражений……………………… ...74 5 Нисходящие методы обработки языков………………………………..77 5.1 Принципы нисходящей обработки……………………………... ...77 5.2 S – грамматики…………………………………………………... ...80 4 5.3 Нисходящая обработка для транслирующих грамматик…….......81 5.4 q – грамматики………………………………………………….... ...82 5.5 LL(1) – грамматики……………………………………………… ...84 5.6 Нахождение множеств выбора………………………………….....88 5.7 L - атрибутные грамматики……………………………………... ...91 5.7.1 Определение L-атрибутной грамматики…………………….. 91 5.7.2 Атрибутный МП-автомат……………………………………...92 6 Схемы программ…………………………………………………………100 6.1 Стандартные схемы программ (ССП)…………………………...100 6.1.1 Формы определения ССП……………………………………100 6.1.2 Интерпретация ССП………………………………………….103 6.1.3 Свойства и виды стандартных программ…………………...106 6.1.4 Свободные интерпретации (СИ)…………………………….108 6.1.5 Согласованные свободные интерпретации (ССИ)…………110 6.1.6 Логико-термальная эквивалентность………………………..111 6.1.7 Моделирование стандартных схем программ………………112 6.2 Рекурсивные схемы……………………………………………….117 6.2.1 Определение рекурсивной схемы…………………………...117 6.2.2 Трансляция схем программ………………………………….119 6.2.3 Схемы с процедурами………………………………………..120 6.3 Обогащённые и структурированные схемы……………………..121 6.3.1 Классы обогащённых схем…………………………………..121 6.3.2 Трансляция обогащённых схем……………………………...123 6.3.3 Структурированные схемы…………………………………..124 Заключение…………………………………………………………………126 Библиографический список ……………………………………………..127 Приложение А (обязательное) Синтез конечного автомата……………. .128 Приложение Б (обязательное) Программа работы КА…………………...134 Приложение В (обязательное) Автоматные отображения……………… .136 Приложение Г (обязательное) Транзитивное замыкание………………...139 Приложение Д (обязательное) Произведение матриц отношений……... .141 Приложение Е (обязательное) Синтаксический анализ………………… .144 5 Предисловие В первой главе учебного пособия рассмотрены автоматные модели, их свойства, минимизация, структурный синтез и анализ. Вторая глава посвящена определению формальных грамматик, их классификации и минимизации. В третьей главе рассматривается лексический анализ языков программирования, принцип действия транслятора и реализация конечных распознавателей при лексическом анализе. Четвертая глава содержит описание автоматов с магазинной памятью, транслирующих грамматик и атрибутных транслирующих грамматик. В пятой главе рассмотрены принципы нисходящей обработки языков программирования высокого уровня и некоторые типы формальных грамматик, наиболее пригодных для этих целей. Для овладения практическими навыками работы по применению автоматных моделей и формальных грамматик по описанию и синтезу трансляторов студенты проходят лабораторный практикум и выполняют курсовой проект. 6 Введение Теория автоматов и формальных языков базируется на теории конечных автоматов и теории формальных языков и грамматик. Изложение дисциплины начинается с рассмотрения "Автоматных моделей", где основы теории конечных автоматов представляются в более общей, алгоритмической интерпретации, что позволяет осуществлять их реализацию как аппаратно, так и программно. Именно так происходит использование данной теории на практике: при проектировании аппаратуры и при разработке лексического блока транслятора как программного продукта. Следующий важный раздел дисциплины - "Формальные языки и грамматики" закладывает необходимую теоретическую базу для перехода к изучению основ теории построения трансляторов. Этот раздел включает специальные виды формальных грамматик и нисходящие методы синтаксического анализа формальных языков, используемые при проектировании синтаксического анализатора транслятора. 7 1 Автоматные модели 1.1 Определение конечных автоматов 1.1.1 Формы задания конечных автоматов Конечным автоматом (КА) называется система S={ A, Q, V, f, g }, в которой: - A={ a1, a2,.. ., am }, Q={ q1,..., qn }, V={v1,…, vk } - конечные множества (алфавиты); - f - функция переходов; - g - функция выходов. A называется входным алфавитом, V-выходным, а Q - алфавитом состояний. Если, кроме того, в автомате S выделено одно состояние, называемое начальным (q1), то полученный автомат называется инициальным и обозначается (S, q). Поскольку функции f и g определены на конечных множествах, их можно задавать таблицами перехода (1.1) и выхода (1.2). Таблица 1.1 Таблица 1.2 Q\A a1 a2 Q \A a1 a2 q1 q2 q1 q1 v2 v1 q2 q2 q1 q2 v1 v2 Обычно две таблицы сводятся в одну таблицу, называемую автоматной таблицей (1.3). Таблица 1.3 Q\A a1 a2 a1,v2 q2 q1 q1 q2,v2 q1,v1 a2,v1 q2 q2,v1 q1,v2 a2,v2 a1,v1 Рисунок 1.1 Другой способ задания КА - ориентированный мультиграф, называемый графом переходов или диаграммой переходов (рис. 1.1). Вершины графа соответствуют состояниям. Если f(qi, aj) = qk и g(qi, aj) = vt, то из qi в qk ведет ребро, на котором написаны aj и vt. Кратные ребра не обязательны. При этом должны быть выполнены условия корректности: -для любой входной буквы aj имеется ребро, выходящее из qi, на котором написано aj (условие полноты); -любая буква aj встречается только на одном ребре, выходящем из qi (условие непротиворечивости или детерминированности). 8 1.1.2 Кодирование алфавитов автомата Пусть у автомата S={A, Q, V, f, g} алфавиты A, Q, V содержат соответственно по M, N, K символов. Выберем такие m, n, k, чтобы удовлетворялись соотношения (основание логарифма-2) logM < m < logM+1, logN < n < logN+1, logK < k < logK+1. Образуем M различных m-мерных двоичных наборов и установим взаимно-однозначное соответствие между этими двоичными наборами и символами из алфавита A. В результате каждому ai будет сопоставлен свой двоичный набор ai=(ai1,ai2,...,aim), где каждый разряд набора можно рассматривать как логическую переменную, принимающую значения {0, 1}.Поступая аналогичным образом с алфавитами Q и V, получаем q j= (qj1, qj2,..., qjn), vl = (vl1, vl2,..., vlk). Данный процесс сопоставления символов из алфавитов A, Q, V и двоичных наборов называется кодированием алфавитов автомата. Поскольку алфавиты рассмотренного примера (табл. 1.3) имеют по две буквы, то число разрядов каждого символа в двоичном алфавите будет 1. Обозначим первые буквы - 0, а вторые - 1. Таблица в этом случае примет следующий вид (табл. 1.4): Таблица 1.4 Q\A 0 0 1, 1 1 1, 0 Таблица 1.5 Q \A 0 0 1, 1 1 1, - 1 0, 0 0, 1 1 -, 0 0, 1 1.1.3 Виды конечных автоматов В зависимости от способа задания функции выхода g различают конечные автоматы следующего вида: 1) vt = g(qjt-1, ait) - автоматы 1-го рода (автоматы Мили); 2) vt = g(qjt, ait) - автоматы 2-го рода; 3) vt = g(qjt-1, qjt) - правильные автоматы; 4) vt = g(qjt-1) - правильные автоматы 1-го рода; 5) vt = g(qjt) - правильные автоматы 2-го рода (автоматы Мура); 6) vt = qjt-1 - абстрактные автоматы 1-го рода; 7) vt = qjt - абстрактные автоматы 2-го рода (автоматы Медведева); 8) vlt = g(ait) - автоматы без памяти. Самой общей моделью являются автоматы Мили, поскольку остальные автоматы либо являются их частным случаем, либо тривиально сводятся к ним при подстановке функции f в функцию g. 9 Если функции f и g определены всюду на множествах совокупностей значений своих аргументов, то автомат S называется вполне определенным. 1.2 Свойства конечных автоматов 1.2.1 Автоматное отображение Для КА его функции f и g могут быть определены не только на множестве А всех входных букв, но и на множестве всех входных слов. Для любого входного слова u=a1 a2...ak и начального состояния КА qt можно найти обобщенную функцию перехода F(qt,u), используя функцию перехода f qt+1 = f(qt, a1), qt+2 = f(qt+1, a2) = f(f(qt, a1), a2), qt+3 = f(qt+2, a3) = f(f(f(qt, a1), a2), a3), ……………………………………. qt+k = f(qt+k-1, ak) = f(f…(f(f(qt, a1), a2),...ak) = F(qt,u). Это традиционное определение обобщенной функции перехода. Возможна и другая индуктивная форма определения обобщенной функции перехода. Для любого слова u из множества U и любой буквы aj F(qt,uaj) = f(F(qt,u), aj). Таким образом, обобщенная функция перехода F(qt,u) определяет конечное состояние КА, в которое он перейдет под воздействием входного слова u. Аналогично определяется расширенная функция выхода G(qt,u) v1 = g(qt, a1), v2 = g(qt+1, a2) = g(f(qt, a1), a2), v3 = g(qt+2, a3) = g(f(f(qt, a1), a2), a3), ……………………………………. vk = g(qt+k-1, ak) = g(f…(f(f(qt, a1), a2),...ak) = G(qt,u). Таким образом, обобщенная функция выхода G(qt,u) определяет конечный символ vk выходного слова КА w = v1 v2 vk. Зафиксируем в автомате S начальное состояние qt и каждому входному слову u = a1 a2...ak поставим в соответствие слово w = v1 v2 vk в выходном алфавите. Это соответствие, отображающее входные слова в выходные слова, называется автоматным отображением, а также автоматным (или ограниченно детерминированным) оператором, реализуемым автоматом (S, qt). Иногда говорят кратко - оператор (S, qt) или оператор S (если автомат S -инициальный). Если результатом применения оператора к 10 слову u является выходное слово w, то это будем обозначать S(qt, u) = w или S(u) = w. Автоматное отображение также удобно определить индуктивно: S(qt, aj) = g(qt, aj), S(qt, uaj) = S(qt, u)g(F(qt, u), aj). 1.2.2 Свойства автоматных отображений Число букв в слове u называется длинной u и обозначается IuI или l(u). Автоматные отображения обладают двумя свойствами: - слова u и w = S(u) имеют одинаковую длину: IuI = IwI (свойство сохранения длины); - если u = u1u2 и S(u1u2) = w1w2, где IujI = IwjI, то S(u1) = w1; иначе говоря, образ отрезка длины i равен отрезку образа той же длины. Это свойство отражает тот факт, что автоматные операторы - это операторы без предвосхищения, т.е. операторы, которые "не заглядывают вперед", перерабатывая слово слева направо: i-я буква выходного слова зависит только от первых i букв входного слова. Введенные определения наглядно интерпретируются на графе переходов. Если зафиксирована вершина qt, то всякое слово u = a1 a2...ak однозначно определяет путь длины k из этой вершины (обозначим его (qt, u)), на k ребрах которого написаны соответственно буквы a 1 a2...ak . Поэтому F(qt, u) - это последняя вершина пути (qt, u); G(qt, u) - выходная буква, написанная на последнем ребре пути, а отображение S(qt, u) - слово, образованное k выходными буквами, написанными на k ребрах этого пути. Из диаграммы (рис. 1.1) следует, что f(q1, a2 a1) = f(q1, a1) = q2; f(q1, a1 a2 a2) = q1; g(q2, a1 a2) = v2 ; g(q1, a1 a1 a2 a2) = v1; S(q2, a1 a2 a1) = v1 v2 v2. 1.2.3 Свойства конечных автоматов Рассмотрим основные свойства конечных автоматов. Достижимость состояний КА Состояние qi называется достижимым из состояния qj, если существует входное слово u, такое, что f(qj, u) = qi. Из диаграммы на рисунке 1.1 следует, что состояние q2 является достижимым, так как f(q1, a1)=q2, т.е. автомат из состояния q1 под действием входного сигнала а1 переходит в состояние q2. Сильно связанный КА Автомат S называется сильно связанным, если из любого его состояния достижимо любое другое состояние. Автомат, изображенный на рисунке 1.1, является сильно связанным, так как он имеет два состояния и каждое из состояний достижимо из другого состояния. 11 Автономный КА Автомат называется автономным, если его входной алфавит состоит из одной буквы A={a}. Все входные слова КА имеют вид ааа...аааа. При этом достаточно длинное входное слово автономного автомата (АА) с n состояниями является периодическим (возможно с предпериодом), причем длины периода и предпериода не превосходят n. Поскольку число состояний КА (рисунок 1.1) равно двум, то и период выходной последовательности так же равен n=2. Частичный автомат Автомат S называется частичным, или не полностью определенным, если хотя бы одна из его двух функций не полностью определена, т.е. для некоторых пар (состояние - вход) значения функций f и g не определены. В автоматной таблице неполная определенность КА выражается в том, что некоторые ее клетки не заполнены - в них стоят прочерки. Функции f и g неравноправны. Если f не определена на слове u, то она не определена и на всех его продолжениях. Для g это не обязательно, поскольку в этом случае в выходном слове будет прочерк, но работа КА не остановится. Из таблицы 1.5 следует, что КА, находясь в состоянии q1, под действием сигнала а2=1 выдаст на выход сигнал v1=0 и не сможет перейти ни в одно из возможных состояний, т.е. автомат прекратит работу "зависнет". В состоянии q2=1 не определена функция выхода v=g(q2, a1) при действии входного сигнала а1=0. Работа КА не прекратится, но выходной сигнал при этом будет отсутствовать. 1.3 Минимизация конечных автоматов 1.3.1 Изоморфизм и эквивалентность автоматов Пусть S = { As, Qs, Vs, fs, gs } и T={ At, Qt, Vt, ft, gt } - два автомата. Тройка отображений: h1: AsAt; h2: QsQt; h3: VsVt, устанавливающая однозначное соответствие между алфавитами автоматов S и T определяет гомоморфизм автоматов, если выполняются условия ft(h1(as), h2(qs)) = h2(fs(as, qs)), gt(h1(as), h2(qs)) = h3(gs(as, qs)), т. е. операции отображения алфавитов автомата S в T и действия функций f и g коммутативны. Однако, установление гомоморфизма недостаточно для определения эквивалентности автоматов. Взаимно-однозначный гомоморфизм автоматов S и T называется изоморфизмом, и он устанавливается введением дополнительной тройки функций отображения: h'1: AtAs; h'2: QtQs; h'3: VtVs и выполнением дополнительных условий fs(h'1(at), h'2(qt)) = h'2(ft (at, qt)), 12 gs(h'1(at), h'2(qt)) = h'3(gt (at, qt)). При изоморфизме мощности алфавитов КА, S и T совпадают, что может не быть при гомоморфизме. Автоматы, для которых существует изоморфизм, называются изоморфными. При изоморфизме можно входы, состояния и выходы таблицы переходов автомата S переименовать так, что она (таблица) превратится в таблицу переходов автомата T. Пусть S и T - два автомата с одинаковыми входным и выходным алфавитами. Состояние q автомата S и состояние r автомата T называются неотличимыми, если для любого входного слова u S(q, u) = T(r, u). В частности, если T = S, то речь идет о неотличимых состояниях одного и того же автомата S. Автоматы S и T называются неотличимыми, если для любого состояния q автомата S найдется неотличимое от него состояние r автомата T и, наоборот, для любого r из T найдется неотличимое от него q из S. Неотличимость автоматов означает, что их возможности по реализации преобразований входной информации в выходную информацию совпадают. Неотличимость автоматов называют эквивалентностью. Переход от автомата S к эквивалентному автомату называется эквивалентным преобразованием автомата S. Эквивалентные преобразования используются при минимизации КА. Минимизация КА включает: - определение и исключение недостижимых состояний; - определение и исключение эквивалентных состояний. 1.3.2 Недостижимые состояния КА Определение множества недостижимых состояний КА основано на следующей аксиоме и свойстве. Аксиома: начальное состояние КА всегда достижимо. Свойство: если достижимо состояние, находящееся в левой части таблицы переходов (ТП), то достижимы и состояния находящиеся в правой части таблицы для данной строки. Алгоритм определения недостижимых состояний включает в себя следующие шаги: 1. Формируется множество достижимых состояний D, куда включается начальное состояние КА (на основании аксиомы). 2. Для каждого состояния из множества D выделяется строка ТП и осуществляется обновление множества D за счет элементов правой части выделенной строки. 13 3. Повторяется п. 2 до тех пор, пока не будут обработаны все состояния из множества D и пока происходит обновление множества D. 4. Определяется множество недостижимых состояний N как разность множества Q всех состояний КА и множества достижимых состояний N = Q – D. Рассмотрим работу алгоритма для КА, заданного таблицей 1.6: Таблица 1.6 Q\A 1. Формируется множество достижимых 1 2 состояний D, куда включается начальное q1 состояние КА (q2) q4 q3 D = { q2}. q2 q1 q4 2. Выделяется 2 строка и обновляется множество D за счет элементов правой части строки q3 q2 q1 D = D + {q1, q4}={ q2, q1, q4}. q4 3. Повторяется п. 2. для элемента q1 из D q3 q2 D = D + {q3} = {q2, q1, q4, q3}. q5 q3 q4 4. Повторяется п. 2. для элемента q4 из D (обновление D при этом не происходит) D = D + { } = {q2, q1, q4, q3}. 5. Повторяется п. 2. для элемента q3 из D (обновление D при этом не происходит) D = D + { } = {q2, q1, q4, q3}. 6. Так как все элементы из множества D обработаны и обновление D не происходит, то определяется множество недостижимых состояний N = Q – D = {q1, q2, q3, q4, q5} - {q2, q1, q4, q3} = {q5}. 1.3.3 Эквивалентные состояния КА Задача минимизации числа состояний автомата (минимизация автомата) формулируется следующим образом: среди автоматов, эквивалентных S, найти автомат с наименьшим числом состояний минимальный автомат. Наиболее известным алгоритмом нахождения эквивалентных состояний является алгоритм Мили. Его суть состоит в том, что состояния автомата на каждом шаге разбиваются на классы эквивалентности, причем разбиение на следующем шаге будет получаться расщеплением некоторых классов предыдущего разбиения. Отметим, что шаги алгоритма в данном описании вовсе не элементарны, это скорее блоки. Рассмотрим реализацию алгоритма Мили: 14 1. Необходимым условием эквивалентности состояний автомата является тот факт, что при воздействии одинаковых ai сигналов в этих состояниях на выходе автомата получаем также одинаковые сигналы v. В таблице 1.7 приведено описание КА. Таблица 1.7 Q\A a1 q1 q3, v2 q2 q4, v2 q3 q1, v2 q4 q3, v1 Таблица 1.8 Q \A a1 q1 q1, v2 q2 q3, v2 q3 q1, v1 a2 q1, v1 q1, v1 q3, v1 q4, v2 a2 q1, v1 q1, v1 q3, v2 Из таблицы следует, что при действии сигнала а1 в состояниях q1, q2, q3 на выходе будет одинаковый сигнал v2. Действие в этих же состояниях сигнала а2 приведет к появлению на выходе одинакового сигнала v1. Следовательно, на первом шаге выделяется класс эквивалентности, включающий первые три строки таблицы 1.6 (состояния q1, q2, q3). Этот класс отражен на рисунке 1.2 соответствующими ячейками, внутри которых отмечены состояния, в которые переходит КА из потенциально эквивалентных состояний. В тех случаях, когда КА переходит в состояния, аналогичные предыдущим, в ячейке запись отсутствует, что имеет место для состояний (q1 q3). Состояния (q1 q4),(q2 q4) и (q3 q4) не эквивалентны, так как для этих состояний для одинаковых входных сигналов имеют место различные выходные сигналы КА. Эти ячейки перечеркнуты (X1). q2 q3 q4 X2 q3 q1 q4 X2 q1 q3 q4 X1 X1 X1 q1 q2 q3 Рисунок 1.2 На втором шаге анализируется информация сформированной таблицы. Так как состояния (q3 q4) не эквивалентны, то ячейка (q1 q2) с записью этих состояний перечеркивается. Это же выполняется для ячейки (q2 q3) с записью не эквивалентных состояний (q1 q4) (X2). Таким образом, эквивалентными состояниями являются только (q1 q3). Это обусловлено тем, что только из состояний q1 и q3 автомат 15 переходит вновь в эквивалентные состояния, что не имеет места для любых других состояний. Таким образом, из класса эквивалентности, полученного на 2-м шаге для определения состояний эквивалентного автомата можно выбрать одну строку (1-ю или 3-ю). Выбираем 1-ю строку, добавляем к ней 2-ю и 4-ю строки и переобозначаем состояния. В результате получим КА, описываемый таблицей 1.8. 1.4 Структурный синтез конечных автоматов 1.4.1 Кодирование состояний конечного автомата После минимизации числа состояний автомата может быть определено число элементов памяти, необходимое для реализации состояний КА. Для двоичных элементов памяти их минимальное число kмин определяется выражением kмин log2 N , где N - число состояний (число строк минимизированной таблицы переходов). Кодирование внутренних состояний КА заключается в сопоставлении каждого состояния КА некоторой комбинации состояний выбранных элементов памяти, причем различным состояниям должны соответствовать различные комбинации состояний элементов памяти. SR\q 11 0 1 00 10 0 X 0 0 1 1 X 0 1 1 S T R q q q q Рисунок 1.3 Наиболее распространенным элементом памяти является RS триггер, элемент с двумя устойчивыми состояниями (рис. 1.3) и c соответствующей таблицей переходов (X - запрещенное состояние) Число состояний КА (рис. 1.1) N = 2, следовательно, kмин = 1, т.е. для кодирования состояний КА требуется один элемент памяти. Тогда состояние q1 закодируем кодом 0, а состояние q2 - кодом 1. 16 1.4.2 Построение таблицы канонических уравнений Преобразовываем и дополняем автоматную таблицу так, чтобы она могла задавать систему канонических уравнений для построения схемы КА. Заполняем каноническую таблицу, используя данные предыдущих таблиц. Каноническая таблица q qt-1 qt S R V 0 0 1 1 0 1 1 0 0 0 -(1) 0 0 1 1 -(1) 0 0 1 1 0 0 1 1 Доопределяем таблицу для позиций (-) так, чтобы не изменить смысл функционирования КА: во второй строке проставляем вместо (-) логическую 1. Это значит, что если на триггер, находящийся в "0" состоянии, на вход S подать 0, а на вход R - 1, то его состояние не изменится. На этом же основании доопределяем позицию (-) в третьей строке. Выделив конституенты единицы для сигналов S, R и V, получаем в дизъюнктивной нормальной форме (д.н.ф.) уравнения & 1 * 1 qt-1 S T 1 v & R 2 R Рисунок 1.4 S = а qt -1 + а qt-1, R = a qt -1 + a qt-1, V = а qt -1 + a qt-1. Упрощаем выражения S = а , R = a , V = а qt -1 + a qt-1. Полученные выражения описывают функциональную схему КА (рис. 1.4). 1.5 Структурный анализ конечных автоматов Задача анализа КА заключается в получении его таблицы переходоввыходов и графа переходов по функциональной схеме. В результате решения задачи анализа может определиться правильность построения схемы КА, соответствие его характеристик требуемым значениям. Исходными данными для анализа КА является его функциональная схема и таблицы переходов элементов памяти. 17 Задача анализа КА может быть решена в следующей последовательности: 1. Подготовка схемы к анализу. Выделение входных, выходных каналов, элементов памяти, схем возбуждения (управления памятью) и схем выходов. 2. Получение функций возбуждения памяти и функций выходов. 3. Построение таблицы возбуждений. 4. Построение структурной таблицы переходов по таблице возбуждений и таблицам переходов элементов памяти. 5. Построение структурной таблицы выходов по функциям выходов. 6. Построение абстрактной таблицы переходов-выходов и графа переходов КА. Выполняем функциональный анализ схемы КА (рис. 1.4). В КА используется RS-триггер (рис. 1.3) с соответствующей таблицей переходов. На рисунке 1.4 блок 1 изображает схему возбуждения (управления памятью), а блок 2 - схему выходов. Строим функции возбуждения памяти и функцию выходов S = а , R = a , V = а qt -1 + a qt-1. Строим таблицу возбуждений и, используя таблицу переходов RSтриггера, строим таблицу переходов КА. По функциям выхода получаем таблицу выходов. По таблицам переходов и выходов строим граф переходов КА. Таблица возбуждений Таблица переходов A/Q 0 1 A/Q 0 1 S R S R 0 1 0 1 0 0 1 1 1 0 1 0 1 1 0 0 Таблица выходов (0,1) a\q 0 1 0 1 0 0 1 0 1 (1,0) Контрольные вопросы 1.Определение конечных автоматов. 2.Виды конечных автоматов. 3.Свойства конечных автоматов. 4.Минимизация конечных автоматов. 5.Структурный синтез конечных автоматов. 18 1 (1,1) (0,0) 2 Формальные языки и грамматики 2.1 Формальная порождающая грамматика Под языком понимается всякое средство общения, состоящее из: знаковой системы, т.е. множества допустимых последовательностей знаков; - множества смысловых понятий системы; - соответствия между последовательностями знаков и смысловыми понятиями, делающего "осмысленным" допустимые последовательности знаков. Знаки: буквы алфавита, математические знаки, звуки, ритуальные действия и др. Наука об осмысленных знаковых системах называется семиотикой (используется в биологии, социологии и др.). Наиболее развитое направление семиотики, включающее в качестве знаков - алфавит, а в качестве последовательности знаков - текст, это языки программирования. Задача машинного перевода естественных языков привела к возникновению математической лингвистики, которая рассматривает языки как произвольные множества осмысленных текстов. Синтаксис языка - правила, определяющие множество текстов. Семантика языка - описание множества смыслов и соответствия между текстами и смыслами. Семантика языков зависит от предметной области. Синтаксис языков более независим от предметной области. Математическая лингвистика достигла больших успехов в изучении синтаксиса языков, где сложился специальный математический аппарат теория формальных грамматик (основные приложения: языки программирования, искусственный интеллект, машинный перевод). С точки зрения синтаксиса язык понимается уже не как средство обучения, а как множество формальных объектов - последовательностей символов алфавита. В данном случае такие последовательности называются не текстами, а словами. В лингвистике естественных языков "слова" и "текст" - разные понятия. Поэтому в математической лингвистике последовательность символов называется нейтрально - цепочкой (string), а язык, понимаемый как множество формальных цепочек - формальным языком. Пусть задан алфавит V и тем самым множество V* всех конечных слов, или цепочек, в алфавите V. Формальный язык (ФЯ) L в алфавите V - это произвольное подмножество L в V*. 19 Конструктивное описание ФЯ осуществляется с помощью формальных систем специального вида, называемых формальными порождающими грамматиками (ФГ). Формальная порождающая грамматика ФПГ G-в дальнейшем просто грамматика G - это формальная система, определяемая четверкой объектов G = <V, W, I, P>, где V - алфавит (словарь) терминальных (основных) символов; W - алфавит нетерминальных (неосновных, вспомогательных) символов; I - начальный символ (аксиома) грамматики; P - конечное множество правил вида , где и -цепочки в алфавите V U W . Приняты следующие обозначения : - терминальные символы обозначаются малыми латинскими буквами или словами из строчных букв, например, V={a, b, c, . . .z}, или V={принтер, текст, печатает, лазерный}; - нетерминальные символы обозначаются большими латинскими буквами в угловых скобках или словами в угловых скобках, например, W={<A>,<B>,<C>, …,<Z>} или W={<предложение>,<подлежащее>, <сказуемое>, <прилагательное>, <существительное>, <дополнение> и т.п.}; - цепочки из терминальных и нетерминальных символов обозначаются малыми греческими буквами (, , , и т.д.) Пример. Допустим, алфавиты V и W содержат символы, приведенные выше при описании обозначений, а множество правил P имеет следующий вид: 1) <предложение><определение><подлежащeе><сказуемое> <дополнение> 2) <подлежащее> <существительное> 3) <дополнение> <существительное> 4) <сказуемое> <глагол> 5) <определение> <прилагательное> 6) <глагол> печатает 7) <существительное> принтер 8) <существительное> текст 9) <прилагательное> лазерный Применим эту грамматику для порождения (или вывода) предложения. На первом шаге записываем правило 1. На втором шаге делаем подстановку правила 2 (П2) в П1, заменяя <подлежащее> на <существительное>. На третьем шаге подставляем П4 в предыдущий 20 вывод, заменяя <сказуемое> на <глагол>. На четвертом шаге применяем П5, заменяя <определение> на <прилагательное>. На пятом шаге используем П3, заменяя <дополнение> на <существительное>. На шестом шаге подставляем П7, заменяя первое <существительное> на принтер. На седьмом шаге вводим П6 в предыдущий вывод, заменяя <глагол> на печатает. На восьмом шаге применяем П8, заменяя второе <существительное> на текст. На девятом шаге применяем П9, заменяя <прилагательное> на лазерный. В результате получаем окончательный вывод лазерный принтер печатает текст. Введя формальные обозначения, вышеприведенные правила можно записать в следующем виде, где <S> - <предложение>, <А> <определение>, и т. д. 1. <S> <A><B><C><D> 2. <B> <E> 3. <D> <E> 4. <C> <F> 5. <A> < G > 6. <F> a 7. <E> b 8. <E> c 9. <G> d 10.<G> Обратим внимание на дополнительное правило 10, которое вводит пустую цепочку, т.е. допускает вывод: принтер печатает текст, расширяя тем самым исходную грамматику. С помощью данной грамматики можно также вывести другое предложение: лазерный текст печатает принтер. Мы обнаруживаем, что в этом выводе что-то не получилось, т.е. данная грамматика в одном случае действует точно, а в другом - допускает неточность. Из этого можно сделать вывод о том, что, по-видимому, существуют разные грамматики для разных предметных областей. 2.2 Классификация формальных грамматик Классификация ФГ осуществлена Н. Хомским. Она включает четыре типа грамматик: - грамматика типа 0, или грамматика произвольного вида, без ограничений на правила вывода , где и - цепочки, содержащие как терминальные, так и не терминальные символы; - грамматика типа 1, или контекстная грамматика, все правила которой имеют вид <S>, где , , - множество всех непустых цепочек, а , - это контекст нетерминала <S>; 21 - грамматика типа 2, или контекстно-свободная грамматика (безконтекстная) КС - грамматика. Это наиболее распространенный тип формaльной грамматики. Он предполагает все правила вывода типа <S>, где слева обязательно находится один нетерминальный символ, а справа - цепочка из терминальных и нетерминальных символов (в любой последовательности и количестве, в том числе и пустой символ ), примером этой грамматики является рассмотренная в примере; - грамматика типа 3, или регулярная грамматика (автоматная грамматика), все правила вывода которой имеют вид <S> a <B> или <S> a . Этот тип грамматики реализуется конечными автоматами, если положить в КА входные символы - терминальными (основными), а состояния КА - нетерминальные символы. Этот тип грамматики будет рассмотрен более подробно далее. Язык L называется языком типа i(i = 0, 1, 2, 3), если существует порождающая его грамматика типа i. 2.3 Контекстно-свободные грамматики 2.3.1 Связь КС-грамматик с формами Бэкуса - Наура (БНФ) Как было показано ранее, можно определять грамматику, задавая ее правила и начальный нетерминал. Нетерминальное и терминальное множества необходимо задавать в явном виде лишь тогда, когда они не совпадают с множествами, которые следуют из множества правил Р. КС языки, порождаемые КС-грамматиками наиболее хорошо изучены. Это объясняется тем, что с одной стороны, КС-грамматики оказались весьма подходящим аппаратом для описания языков программирования; с другой стороны, благодаря относительной простоте и содержательности структуры КС -языков и наличию удобных средств их описания исследования КС-языков представляет значительный теоретический интерес. Появление понятия КС-грамматики практически совпало с появлением метаязыка Бэкуса - Наура , или нормальной формы Бэкуса Наура (БНФ), который впервые был использован при описании языка программирования Алгол - 60 и быстро стал общепринятым средством формального описания языков программирования. Описание языка с помощью нормальных форм Бэкуса - Наура представляют собой совокупность " металингвистических формул " - выражений вида X:: = Y1 |. .. | Yn, где X - некоторый текст, заключенный в угловые скобки и называемый металингвистической переменной, а Y1. .. Yn последовательности металингвистических переменных и основных символов языка ( букв, цифр, разделителей, неделимых слов типа begin, end и др. 22 Знак :: = называется металингвистической связкой и эквивалентен применяемой нами стрелке () (читается как "есть", "это"). Знак | - это металингвистическая связка "или". Металингвистическая переменная представляет собой имя конструкции языка (эквивалентна нетерменальному символу КС - грамматики ). Металингвистическая формула в целом - это описание различных синтаксических вариантов строения конструкции Х (эквивалентна правилу вывода КС - грамматики). Используя БНФ, представим грамматику п.2.1 в виде: <S>:: = <A><B><C><D> -------<E>:: = b|c <G>:: = d| Таким образом, между БНФ и КС - грамматикой существует соответствие. БНФ возникла из практических целей, а КС - грамматика имеет теоретическое значение. Другая, более глубокая причина широкого использования КС грамматики для анализа языков программирования - это возможность описать грамматическую структуру текста языка (т.е. состав и связь языковых конструкций) в терминах вывода этого текста в заданной грамматике. 2.3.2 Выводы Правила грамматики используются для того, чтобы задавать способы подстановки или замены цепочек. Подстановка осуществляется путем замены некоторого нетерминала в какой-нибудь заданной цепочке терминалов и нетерминалов на правую часть правила, левой частью которого является этот нетерминал. Иногда будем говорить, что правило применяется к нетерминалу цепочки. Рассмотрим, например, такую грамматику с начальным нетерминалом <S>: 1.<S> a<A><B>c 2.<S> 3.<A> c<S><B> 4.<A> <A>b 5.<B> b<B> 6.<B> a Если дана цепочка a<A><B>c и мы хотим применить правило 5 к нетерминалу <B>, на который указывает стрелка, то результат соответствующей подстановки таков: a<A>b<B>c. Эту подстановку записываем так: a<A><B>c = >a<A>b<B>c ¦ 5 23 Вертикальная стрелка указывает здесь на заменяемый нетерминал, число под стрелкой указывает номер применяемого правила, а символ => отделяет цепочку "до" подстановки от цепочки, получающейся "после" подстановки. Можно писать и так: a<A><B>c=>a<A>b<B>c, однако такой записи иногда недостаточно для описания выполняемых подстановок, что следует из такой подстановки: a<A><B>c=>a<A>b<B>c . ¦ 4 Последовательность подстановок называется выводом. Цепочка acabac, например, может иметь следующий вывод: <S>=>a<A><B>c=>a<A>b<B>c=>ac<S><B>b<B>c=>ac<S>ab<B>c=>acab<B>c=>acabac. 1 4 3 6 2 6 Каждая цепочка терминалов и нетерминалов, встречаемая в выводе, называется промежуточной цепочкой этого вывода. Промежуточную цепочку, выводимую из начального символа, называют сентенциальной формой или выводимой цепочкой. Часто употребляют слово "вывод", не указывая начальной цепочки вывода. В таких случаях предполагается, что начальной цепочкой является начальный нетерминал. Существование вывода одной цепочки из другой обозначается с помощью символа =>*. Так, имея в виду, что существует вывод цепочки acabac из цепочки <S>, будем писать <S>=>*acabac. Для единообразия допускается нулевое число подстановок в выводе, например: b<A>c можно рассматривать как вывод нулевой длины, в котором b<A>c являются одновременно начальной и заключительной цепочкой, т.е. для любой цепочки можно писать a =>*a, так как a можно получить из себя самой с помощью последовательности подстановок нулевой длины. Если вывод больше, чем нулевая длина, и это необходимо отметить, то символ * заменяется на + (<S>=>+a c a b a c). Язык, задаваемый грамматикой, определим как множество терминальных цепочек, которые можно вывести из начального символа грамматики. Иногда говорят, что язык определяется грамматикой, порождается ею или выводится в ней. Язык, порождаемый КС-грамматикой, называется КС-языком. 24 2.3.3 Деревья вывода Можно вывести (построить) дерево вывода цепочки КС языка. Это легко сделать, интерпретируя подстановки как шаги построения дерева. Построение дерева легче продемонстрировать, чем описать (рис. 2.1). <S> -----+---- a <A> <B> c a --+- <A> b -+---- c <S><B> a Рисунок 2.1 На каждом шаге добавляются вершины дерева. Окончательный вариант дерева называется деревом вывода терминальной цепочки acabac. Однако дерево не несет информации о порядке применения правил, кроме одного очевидного соображения, что правила должны применяться к каждой вершине дерева раньше, чем к нетерминальным вершинам, расположенным ниже ее. Это определяет много выводов, соответствующих одному и тому же дереву вывода. Вывод называется левым (или левосторонним) выводом, когда на каждом шаге заменяется самый левый нетерминальный символ. Для каждого дерева существует единственный левый вывод, так как место подстановки в каждом случае осуществляется единственным образом. Для каждого дерева существует также единственный правый (правосторонний) вывод, который получается, если всегда заменять самый правый нетерминал. Многие методы обработки языков используют исключительно левые или правые выводы, так как они удобны для систематической обработки. В подобных случаях пишут a =>L b (левые подстановки). В тех случаях, 25 когда одинаковые нетерминалы определяются разными правилами (3 и 4; 5 и 6), одна и та же цепочка вывода может иметь несколько разных деревьев. В этом случае говорят, что соответствующая грамматика неоднозначна. 2.3.4 Грамматика для констант языка программирования Нередко бывает необходимо найти KC - грамматику для какого либо языка, заданного неформально. Продемонстрирует это. Существуют два вида констант: с символом E (плавающая точка) и без символа E (фиксированная точка). Константы без E выводятся с помощью правила 1. <константа> <десятичное число> , где <десятичное число> порождает последовательность цифр с возможной точкой. Константы с E выводятся по правилу 2. <константа> <десятичное число> E <целое> , где <десятичное число> тот же нетерминал, что и в правиле 1, а <целое>нетерминал, порождающий последовательность цифр, перед которой может стоять знак + или -. Продолжим построение KC - грамматики. 3. <целое> + <целое без знака>. 4. <целое> - <целое без знака>. 5. <целое> <целое без знака> , где <целое без знака> - нетерминал, порождающий последовательность цифр. Для <целое без знака> вводятся следующие правила: 6. <целое без знака> d <целое без знака> 7. <целое без знака> d , где d представляет любую цифру. Эти два правила порождают последовательность цифр. Правила для <десятичного числа> можно выразить с помощью нетерминала <целое без знака>. 8. <десятичное число> <целое без знака>. 9. <десятичное число> <целое без знака> . . 10.<десятичное число> <целое без знака>. . 11.<десятичное число> <целое без знака> <целое без знака>. Таким образом, все нетерминалы описаны и грамматика построена. Например, константу 3.1Е-21 можно вывести с помощью следующего левого вывода: <константа> => <десятичное число> E <целое 26 число> => <целое без знака> . <целое без знака> Е <целое> => 3 . <целое без знака> Е <целое>=> 3 . 1 Е <целое> => 3 . 1 Е - <целое без знака> => 3.1 Е - 2 <целое без знака>=> 3.1Е - 21 . 2.4 Регулярные множества как контекстносвободные языки Как отмечалось ранее, регулярные множества порождаются грамматиками типа 3 (автоматными грамматиками), правила вывода которых всегда определяются следующим образом : <A> x< B>. Класс КС-языков мощнее. Любое регулярное множество можно описать с помощью КС-грамматики. Другими словами, это означает, что регулярные множества являются КС-языками. Пусть задан конечный распознаватель для некоторого регулярного множества. КС-грамматику, порождающую это регулярное множество, можно получить следующим образом: 1. Терминальным множеством грамматики сделать входное множество автомата. 2. Нетерминальным множеством сделать множество состояний автомата, а начальным символом - его начальное состояние. 3. Если в автомате есть переход из состояния А в состояние В по входу Х, то в грамматику надо ввести правило <A> x <B>. 4. Если А - некоторое допустимое состояние автомата, то в грамматику необходимо ввести правило <А> . Чтобы убедиться, что действительно построена грамматика для множества цепочек, определяемого автоматом, дадим нетерминалу, который соответствует состоянию Z, такую интерпретацию: <цепочка, допускаемая автоматом, начавшим работать в состоянии Z >. Тогда правило <А>x<B>, построенное на шаге 3, интерпретируется следующим образом: цепочка, допускаемая автоматом, начавшим работать в состоянии А, может представлять собой символ Х с приписанной к нему цепочкой, которая допускается автоматом, начавшим работу в состоянии В. Правило <А> , построенное на шаге 4, интерпретируется так: цепочка, допускаемая автоматом, начавшим работу в (допускающем) состоянии А, может быть пустой. Таким образом правило грамматики отражает процесс работы конечного автомата. Благодаря такой интерпретации, можно установить однозначное соответствие между выводами и действиями конечного автомата. 27 В качестве примера на рисунке 2.2, а) показан автомат, а на рисунке 2.2, б) построенная по нему грамматика. Начальное состояние автомата S, допускающие состояния - S и В. Цепочка aba допускается автоматом, так как она вызывает такую последовательность переходов: Sa Ab A a B . S A B a A B S b B A A 1 0 1 1.SaA 2.AaB 3.BaS 4.SbB 5.AbA 6.BbA 7.S 8.B б) Рисунок 2.2 2.5 Праволинейные грамматики a) 2.5.1 Построение конечного распознавателя по заданной грамматике Пусть задана грамматика специального вида, которая содержит правила двух видов: <A> x<B> и <A> . Процедуру предыдущего раздела можно обратить, чтобы построить конечный автомат, который распознает язык, порождаемый грамматикой такого вида. Обратная процедура выглядит так: 1. Входным множеством автомата сделать терминальное множество грамматики. 2. В качестве множества состояний автомата использовать множество нетерминальных символов грамматики, а в качестве начального состояния - ее начальный нетерминал. 3. Если в грамматике имеется правило <A> x<B>, то ввести в автомат переход из состояния <A> в состояние <B> по входу x. 4. Если в грамматике есть правило <A> , то сделать состояние <A> допустимым. Результатом этого построения является детерминированный автомат с одним начальным состоянием. Применив эту процедуру к грамматике на рисунке 2.2, б), получаем автомат на рисунке 2.2, а). Знание того, что грамматики указанного вида (специальные) порождают регулярные множества, помогает описать задачи, решаемые КА, а потом по грамматикам получить автоматы с помощью данной процедуры, например, идентификаторы можно задавать следующей грамматикой с начальным нетерминалом <идентификатор> : - <идентификатор> l <буквы и цифры>; - <буквы и цифры> l <буквы и цифры>; - <буквы и цифры> d <буквы и цифры>; 28 - <буквы и цифры> , где l и d представляют соответственно букву и цифру. Так как грамматика имеет указанный специальный вид, ясно, что порождаемый ею язык можно распознавать КА. Применив процедуру, получаем автомат, изображенный в таблице. l d 0 <идентификатор> <буквы и цифры> 1 <буквы и цифры> <буквы и цифры> <буквы и цифры> Грамматики описанного вида - не единственный класс грамматик, порождающих регулярные множества. Еще один класс грамматик, порождающий регулярные множества, - это праволинейные грамматики. 2.5.2 Праволинейные грамматики Грамматика называется праволинейной, если правая часть ее правила содержит не более одного нетерминала, причем этот нетерминал является самым правым символом правой части. Другими словами, правила праволинейной грамматики могут иметь вид: <A> w <B> либо <A> w, где w - терминальная цепочка. Примером такой грамматики может быть: 1.<S> a<A>. 2.<S> bc. 3.<S> <A>. 4.<A> abb<S>. 5.<A> c<A>. 6.<A> . Праволинейные грамматики легко преобразуются к специальному виду, рассмотренному ранее. Продемонстрируем это. Правило 4 вместо одного терминала содержит три. Произведем преобразование и заменим правило 4 следующими тремя правилами, каждое из которых имеет нужный вид: - <A> a<bbS>; - <bbS> b<bS>; - <bS> b<S> . Правило 2 не имеет надлежащего вида прежде всего потому, что в его правой части после терминальных символов нет нетерминала. Чтобы исправить это, заменим его двумя правилами: - <S> bc<эпсилон>; - <эпсилон> . Первое правило для <S> надо преобразовать так же, как было сделано для правила 4: 29 - <S> b<c эпсилон>; - <c эпсилон> c <эпсилон>; - <эпсилон> . Наконец, правило 3 в правой части содержит только один нетерминал. Заменим его так: <S> правая часть правил для <A> для всех правил, содержащих <A> в левой части. В результате получаем: 1. <S> a<A>. 2. <S> b<c эпсилон>. 3. <c эпсилон> c<эпсилон>. 4. <эпсилон> . 5. <S> a<bbS>. 6. <S> c<A>. 7. <S> . 8. <A> a<bbS>. 9. <bbS> b<bS>. 10.<bS> b<S>. 11.<A> c<A>. 12.<A> . Преобразованную грамматику теперь можно использовать для построения конечного распознавателя. Таблица переходов КР представлена ниже. a b c <S> <A>,<bbS> <c эпсилон> <A> 1 <c эпсилон> < эпсилон> 0 < эпсилон> 1 <A> <bbS> <A> 1 <bbS> <bS> 0 <bS> <S> 0 Хотя преобразованная праволинейная грамматика идеально подходит для построения конечного распознавателя, исходная не преобразованная грамматика дает более естественное описание множества цепочек, так как в ней меньше правил и нетерминалов. Иногда после применения к грамматике преобразований грамматика содержит правила вида: <A> <A>. Они обозначают, что нетерминал переводится сам в себя. Эти правила бесполезны и их можно удалить из грамматики, не изменяя порождаемого ею языка. 30 2.5.3 Алгоритм преобразования праволинейной грамматики 1.Если имеются правила вида <A> w, где w - непустая терминальная цепочка, необходимо ввести новый нетерминал, например <эпсилон> и добавить правило <эпсилон> . Затем заменить каждое правило вида <A> w правилом <A> w <эпсилон>. 2.Заменить каждое правило вида <A> a1...an <B> на правила <A>a1<a2...an B>, вводя новые нетерминалы <ai...anB> ai<ai+1...anB> <anB> an <B> , где <ai...anB> для 1 < i < n - новые нетерминалы. 3.Если в грамматике есть такой нетерминал <B>, что имеются правила вида <В> <B>, то, во-первых, необходимо удалить эти правила. Во-вторых, заменить правила, вида <А> <B> правилами вида <A> y для всех <A> при условии, что имеются правила вида <B> y. Если останутся правила, правая часть которых состоит из одного нетерминала, то повторить пункт 3. Иногда в результате применения этих процедур правила дублируются. В этом случае дублирующие правила удаляются. Если существует процедура преобразования праволинейных грамматик в грамматики специального вида, то язык, порождаемый праволинейной грамматикой, является регулярным. 2.6 Минимизация грамматик 2.6.1 Исключение правил, содержащих бесплодные нетерминалы Нетерминалы, которые не порождают ни одной терминальной цепочки, называются бесплодными или мертвыми. Рассмотрим, например, такую грамматику: 1.<S> a<S>a. 2.<S> b<A>d. 3.<S> c. 4.<A> c<B>d. 5.<A> a<A>d. 6.<B> d<A>f . Бесплодными здесь являются нетерминалы <A> и <B>, так как применив правило 4 к <A>, получим цепочку c <B> и наоборот, т.е. цепочки с <A> и <B> всегда переводятся снова в цепочки с <B> и <A>. Поэтому правила, содержащие <A> и <B>, можно исключить из грамматики, оставив лишь правила 1 и 3. Грамматика, состоящая из 31 правил 1 и 3, порождает те же терминальные цепочки, что и грамматика с правилами 1 - 6. 2.6.2 Исключение правил, содержащих недостижимые нетерминалы Аналогичная ситуация возникает, когда в грамматике есть нетерминалы, которые невозможно достичь из начального состояния. Рассмотрим пример: 1.<S> a<S>b. 2.<S> c. 3.<A> b<S>. 4.<A> a . Правила 1, 2 с начальными символами <S> не содержат нетерминала <A>, следовательно, он не может участвовать в выводе терминальных цепочек из <S>, хотя сам по себе этот нетерминал не является бесплодным. Но он недостижимый из начального состояния <S>, поэтому все правила с его участием могут быть исключены. В грамматике остаются только правила 1 и 2. Нетерминалы, которые бесплодны или недостижимы, называются лишними (бесполезными). Описываем процедуру обнаружения лишних нетерминалов: - сначала удаляются бесплодные нетерминалы, так как при этом могут появиться недостижимые нетерминалы; - затем обнаруживаются и удаляются недостижимые нетерминалы. Процедура обнаружения бесплодных нетерминалов основана на следующем свойстве продуктивных (живых) символов: Свойство А: eсли все символы правой части правила продуктивны, то продуктивен и символ, стоящий в ее левой части. Основная идея процедуры состоит в том, что составляют список нетерминалов, которые являются "заведомо" продуктивными (т.е. порождают терминальные цепочки), а затем для обнаружения других продуктивных нетерминалов используется свойство A, и список пополняется. Шаги процедуры таковы: 1.Составить список нетерминалов, для которых найдется хотя бы одно правило, правая часть которого не содержит нетерминальных символов. 2.Если найдено такое правило, что все нетерминалы, стоящие в его правой части, уже занесены в список, то добавить в список нетерминал, стоящий в его левой части. 3.Повторить шаг 2, и если на шаге 2 список больше не пополняется новыми нетерминалами, то получим список всех продуктивных 32 нетерминалов грамматики, а все нетерминалы, не попавшие в него, являются бесплодными. Рассмотрим пример. Нетерминал <B> бесплодный, так как шаг 2 применить не удастся. Удалив все правила, связанные с <B>, получаем минимизированную грамматику. Процедура обнаружения недостижимых символов основана на следующем свойстве. Свойство В: если нетерминал в левой части правила является достижимым, то достижимы все символы правой части этого правила. Основная идея процедуры заключается в том, что список начинается нетерминалами, которые "заведомо достижимы", а затем для обнаружения других достижимых нетерминалов используется свойство В, и список дополняется. Исходная грамматика 1.<S> a<A><B><S> 2.<S> в<C><A><C>d 3.<A> в<A><B> 4.<A> c<S><A> 5.<A> c<C><C> 6.<B> в<A><B> 7.<B> c<S><B> 8.<C> c<S> 9.<C> c Шаги Применяемые правила Продуктивные нетерминалы III 2.<S> в<C><A><C>d <S> V 4.<A> c<S><A> II 5.<A> c<C><C> IV 8.<C> c<S> I 9.<C> c <A> <C> Шаги процедуры таковы: 1.Образовать одноэлементный список, состоящий из начального нетерминала. 2.Если найдено правило, левая часть которого уже есть в списке, то включить в список все нетерминалы, содержащиеся в правой части. 3.Повторить шаг 2, и если на шаге 2 новые нетерминалы в список больше не поступают, то получен список всех достижимых нетерминалов, а нетерминалы, не попавшие в список, являются недостижимыми. 33 Рассмотрим пример: Исходная грамматика 1.<S> <A><B>c 2.<S> 3.<A> <S><B>d 4.<B> a<B><A> 5.<B> c<S><B> 6.<C> d<S><B> Шаг Применяемые правила и I 1.<S> <A><B>c II III IV Достижимые нетерминалы <S> <A><B> 3.<A> <S><B>d 4.<B> a<B><A> 5.<B> c<S><B> Нетерминал <С> - недостижимый и данное правило исключается. Контрольные вопросы 1. Классификация формальных грамматик. 2. Дать определение контекстно-свободных грамматик. 3. Дать определение праволинейных грамматик. 4. Дать определение регулярных КС-грамматик. 5. Минимизация грамматик. 3 Лексический анализ языков программирования 3.1 Принцип действия транслятора 3.1.1 Упрощенная модель транслятора Транслятор - это программа, которая допускает в качестве входа программу на исходном языке высокого уровня, а в качестве выхода выдает другую версию этой программы, написанную на другом языке, который называется объектным языком. Объектный язык обычно является машинным языком некоторой вычислительной машины, причем программу в этом случае можно сразу же выполнять. Транслятор можно представить как сложный вычислительный процесс, включающий отдельные более простые блоки. На рисунке 3.1 Лексический блок Синтаксический блок Таблицы Рисунок 3.1 34 Генератор кода представлена упрощенная схема транслятора, который содержит: - лексический блок; - синтаксический блок; - генератор кода; - таблицы. Лексический блок. Входом транслятора служит набор двоичных символов (цепочка). Лексический блок предназначен для того, чтобы разбивать цепочку символов на слова, из которых она состоит, например, цепочка символов может быть такой: ifb1=10thengoto5. Лексический блок устанавливает, что цепочка представляет слово if, за которым следует переменная b1, знак равенства, число 10, слова: then, goto и метка 5. Таким образом 16 символов преобразуются в 7 новых единиц. Эти единицы называются лексемами. Каждая лексема состоит из двух частей: класса и значения. Первая часть означает, что лексема принадлежит одному из конечного множества классов и указывает характер информации, включенной в значение лексемы. Так b1 принадлежит классу "переменная" и имеет значение, которое служит указателем на элемент таблицы имен для b1. Этот указатель на таблицу имен фактически является внутренним именем переменной b1. Лексема 10 принадлежит классу "константа" и имеет в качестве значения набор битов, изображающих число 10. Знак равенства относится к классу "знак отношения", а его значение указывает - какое именно отношение. Лексема if принадлежит классу "if" (служебное слово), и информирования о ее значении не требуется. Если рассматривать таблицу имен как словарь, то лексическая обработка аналогична группировке букв в слова и нахождению этих слов в словаре. Синтаксический блок. Этот блок переводит последовательность лексем, построенную лексическим блоком, в другую последовательность, которая более непосредственно отражает порядок, в котором должны выполняться операции в программе, например, если в программе написано a+b*c - это значит, что числа, представляемые идентификаторами b, c будут перемножены и к результату будет прибавлено число, представляемое идентификатором а. Указанные выражения можно перевести так: УМНОЖИТЬ (b, c, r1), СЛОЖИТЬ (a, r1, r2), 35 где первое выражение идентифицируется как "умножить b на с и заслать результат в r1", а второе - "сложить а и r1 и заслать результат в r2". Таким образом, пять лексем, выданных лексическим блоком, преобразуются в две новые единицы, которые описывают также действия. Эти новые единицы называются атомами и образуют выход синтаксического блока. Преимуществом здесь является то, что последовательность атомов отражает порядок, в котором должны выполняться действия. Предположим, как и раньше, что каждый атом состоит из класса и значения. Тогда атом УМНОЖИТЬ (b, c, r1) принадлежит классу "УМНОЖИТЬ" и имеет значение, состоящее из 3-х указателей на элемент таблицы: для b, c и r1 соответственно. Внутри транслятора атом будет представлен целым числом, обозначающим "УМНОЖИТЬ", и тремя указателями, обозначающими его значение. Выполняя необходимые преобразования, синтаксический блок (СБ) должен учитывать структуру языка так же, как и при переводе с естественных языков учитываются их грамматические особенности. Генератор кода. Этот блок "развертывает" атомы, построенные СБ, в последовательность команд вычислительной машины, которые выполняют соответствующие действия. Точный характер этого развертывания зависит от многих признаков, например: от типа операторов (b, c) - с фиксированной или плавающей точкой; от используемых регистров машины и др. Та часть работы транслятора, которая связана со смысловым значением лексем, называется семантической обработкой. Семантика идентификатора может включать его тип, а в случае, если это массив, то его размерность. Один из видов семантической обработки включает занесение в таблицу имен свойств отдельных идентификаторов по мере их выявления. Другой вид включает действия, зависящие от типа данных. В некоторых трансляторах определенные семантические действия выполняются отдельным семантическим блоком, помещаемым между синтаксическим блоком и генератором кода. Иногда перед генератором кода размещается еще оптимизирующий блок, цель которого - повысить эффективность работы программы, например, вычисления внутри цикла, когда они не меняются. Это необходимо определить и вынести эти вычисления вне цикла. Будет рассмотрено пять видов действия транслятора: - лексическая обработка; - синтаксическая обработка; - семантическая обработка; 36 - оптимизация работы; - генерация кода. 3.1.2 Блоки и проходы транслятора Обсуждая упрощенную модель транслятора, мы не касались того, как управление передается от одного блока к другому. Рассмотрим взаимодействие ЛБ и СБ. Возможно, когда ЛБ выдает лексему, управление передается СБ для обработки этой лексемы. Когда возникает необходимость в следующей лексеме, управление опять передается ЛБ. При другом типе воздействия ЛБ выдает всю цепочку лексем до того, как управление передается СБ. В этом случае говорят, что работа ЛБ образует отдельный проход. В упрощенной модели проходы можно организовать четырьмя различными способами. Если модель организованна как однопроходный транслятор, управление передается из блока в блок всякий раз, когда требуется или когда формируется лексема или атом. Если она организована как трехпроходный транслятор, то ЛБ подготавливает всю последовательность лексем, которая затем используется СБ при порождении всей последовательности атомов, которая, в свою очередь, используется ГК при порождении машинного кода. Возможны два типа двухпроходных организаций работы: - ЛБ и СБ <--> ГК , - ЛБ <--> СБ и ГК. Чем больше блоков, тем больше вариантов многопроходных организаций работы. Разбиение на проходы требует дополнительной памяти, но мотивируется следующими факторами. ЛОГИКА ЯЗЫКА. Иногда сам исходный язык наводит на мысль о том, что транслятор должен иметь не менее двух проходов. Такая потребность возникает, если в какой-то момент времени транслятору требуется информация из еще не рассмотренной части программы, например, описание идентификатора располагается после его применения. ОПТИМИЗАЦИЯ КОДА. Иногда объектный код получается более эффективный, если ГК доступна информация о всей программе, например, согласно некоторым методам оптимизации, нужно знать все те места программы, где используются переменные и где могут изменяться их значения. ЭКОНОМИЯ ПАМЯТИ. Обычно многопроходные трансляторы занимают в памяти меньше места, чем однопроходные, так как код 37 каждого прохода может вновь использовать память, занимаемую кодом предыдущего прохода. Каждый проход транслятора можно организовать в виде одного блока или комбинации нескольких блоков. 3.1.3 Организация рабочей программы Как правило, задача построения транслятора перед разработчиком ставится недостаточно четко. Известно только, что выход должен соответствовать "семантике" языка и удовлетворять требованиям быстродействия и объема памяти. Поэтому на первом этапе определяют: - размещение в памяти массивов и организацию доступа к ним; - порядок организации вызова процедур и обработки рекурсий. Это относится к организации рабочей программы при реализации языка и нами не рассматривается, так как основной задачей является сама проблема перевода. 3.1.4 Математические модели перевода Теория построения трансляторов основана на математической теории перевода и теории синтеза устройств, выполняющих перевод. Трансляторы - это машины (программы, автоматы), выполняющие перевод. Они достаточно просты, что позволяет их изучить теоретически. Эти автоматы являются "строительными кирпичиками" теории в том смысле, что транслятор представляет собой систему взаимосвязанных автоматов. Теория построения трансляторов состоит из двух частей: 1. Математическое описание автоматов, определяющее: - их возможности в качестве транслятора языка; - их синтез по заданному переводу, который они должны осуществить. 2. Применение теории к построению транслятора, включающее: - представление транслятора (или некоторого блока транслятора) в виде системы взаимосвязанных моделей автоматов; - реализация или моделирование автоматов в виде программ для вычислительной машины. 3.2 Конечные распознаватели 3.2.1 Определение конечного распознавателя Теория построения трансляторов базируется на двух взаимосвязанных теориях, рассмотренных ранее: на теории конечных автоматов и на теории формальных языков и грамматик. Мы начнем с использования теории конечных автоматов при построении трансляторов, потому что именно основные идеи теории КА и ее реализации нашли применение при построении лексических блоков. 38 В первую очередь нас будут интересовать КА особого типа, которые характеризуются фиксированным и конечным объемом памяти, которые читают (воспринимают) последовательности входных символов, принадлежащих некоторому конечному множеству, единственным "выходом" которого является указание на то, "допустима" или "недопустима" данная входная цепочка. Допустимой называют "правильно построенную" или "синтаксически правильную" цепочку. Конечный автомат такого типа называется конечным распознавателем (КР). Пример. Рассмотрим контроллер нечетности, который анализирует цепочку двоичных символов и определяет: четное или нечетное число единиц содержит эта цепочка. Следовательно, входной алфавит А = {a1,a2}, где a1 = 0, a2 = 1; число состояний Q ={q1, q2}; q1= 0, q2= 1; где q1- состояние, характеризуемое четным числом единиц; q2 - состояние, характеризуемое нечетным числом единиц. Таблица переходов контроллера будет Допуск q/a а1 а2 q1 q1 q2 0 q2 q2 q1 1 Некоторые состояния автоматически выбираются в качестве допускающих или заключительных. Если автомат, начав работу в начальном состоянии, при прочтении всей цепочки переходит в одно из допускающих состояний, то говорят, что эта входная цепочка допускается автоматом. В противном случае говорят, что автомат отвергает цепочку. Контроллер нечетности имеет одно допускающее состояние - НЕЧЕТ. Суммируя все сказанное, можно дать следующее определение конечного распознавателя. КР задается: - конечным множеством входных символов; - конечным множеством состояний; - функцией переходов f, которая каждой паре, состоящей из входного символа и текущего состояния, приписывает некоторое конечное состояние; - состоянием, выделенным в качестве начального; - подмножеством состояний, выделенных в качестве допускающих или заключительных. В примере "ЧЕТ" - начальное состояние. Автомат применяется к цепочке 1101. Так как последнее состояние "НЕЧЕТ", то цепочка 1101 допускается нашим автоматом. Множество всех цепочек, состоящих из 1 и 0, называется регулярным, если оно распознается КР. Таким образом, таблица переходов 39 задает КР. Символы справа от таблицы определяют допускающее (1) и не допускающее (0) состояния. 3.2.2 Концевые маркеры и выходы из распознавания Конечный распознаватель лежит в основе процессов распознавания цепочек в трансляторе. При этом желательно, чтобы КР играл более активную роль, чтобы он сам узнавал, когда задание выполнено, и в этот момент выходил из процесса распознавания. Пример. Рассмотрим КР, задаваемый таблицей переходов a b 1 1 2 1 2 E 1 0 E E E 0 Этот автомат распознает цепочки, типа abba и т.п., причем, если символ b встречается не парами, то КР переходит в состояние ошибки (Е), т.е. цепочка aba - ошибочная и далее она так и останется ошибочной, какие бы символы не подавались. Однако, как определить, когда цепочка закончилась? Это может сделать вычислительная система по сигналу "КОНЕЦ ФАЙЛА", но можно модифицировать КР, введя концевой маркер специальный символ, который не является символом входного алфавита. a b 1 1 2 да 2 E 1 нет E E E нет Тогда, например, для цепочки abb символ "да" - указание, что работа закончена и цепочка допустима. С введением концевого маркера следует различать алфавит распознающего языка: {a, b} и алфавит обрабатывающего языка: {a, b, }. Автомат при этом становится обрабатывающим автоматом (ОА) (процессором). Перед нами стояла цель "просмотреть" цепочку до конца. На практике многие конечные процессоры (КП), применяемые в трансляторе, выполняют свою работу раньше, чем прочитана вся цепочка, и прекращают работу, не доходя до концевого маркера. Такой вариант реализуется таблицей. a b 1 1 2 да 2 нет 1 нет 40 Как только обнаруживается первая ошибка (например aba), так сразу же символ "нет" вызывает процедуру прекращения работы. В результате состояние Е удалено из автомата, так как отсутствие переходов в него означает, что автомат никогда не достигнет этого состояния, исходя из начального. Раньше это состояние было введено, чтобы обеспечить дочитывание входной цепочки после того как обнаружена ошибка. Теперь в нем нет надобности, так как мы решили, что при обнаружении ошибки автомат должен выходить из процесса распознавания. Мы допускаем теперь, что любой элемент таблицы переходов КР может быть не переходом, а выходом из распознавания. Этот аспект процесса обработки входных цепочек назовем обнаружением (или детекцией). Автомат обнаруживает некоторую ситуацию до того, как прочитана вся цепочка, и прекращает работу. Детекция может встречаться при обработке как допустимых, так и ошибочных цепочек. Если мы хотим, чтобы пустую цепочку обработал один из автоматов, рассмотренных ранее, необходимо к ней добавить справа концевой маркер. Автомат допустит эту цепочку, если элемент таблицы переходов, соответствующий начальному состоянию и концевому маркеру, - "да", и отвергнет ее, если - "нет". Пустая цепочка не изменяет состояние КР. Одно из неудобств пустой цепочки состоит в том, что ее трудно изобразить графически в печатном тексте. Решаем эту проблему, обозначив пустую цепочку через . 3.2.3 Получение минимального автомата Произвольный КР можно превратить в эквивалентный ему минимальный, исключив недостижимые состояния и объединив эквивалентные состояния, аналогично тому, как это делается в общем виде для КА. Дополнительно требуется учитывать наличие допускающих и отвергающих состояний: два эквивалентных состояния должны быть или оба допускающими, или оба - отвергающими. 41 3.2.4 Недетерминированные автоматы Недетерминированный автомат - это формализм, не связанный со стохастичностью (вероятностями), а связанный с определением множеств цепочек. Недетерминированный КР представляет собой обычный КР с той разницей, что значениями функций переходов являются множество состояний, а не отдельные состояния, и вместо одного начального состояния задается множество начальных состояний. Определение недетерминированного КР: - конечное множество входных сигналов, - конечное множество состояний, - функция переходов f каждой паре, состоящей из состояния и входного символа, ставит в соответствие множество состояний, - подмножество состояний, выделяемых в качестве начальных, - подмножество состояний, выделяемых в качестве допускающих. Это определение изменяет таблицу переходов 0 1 A C 0 А B C 1 B C A,C 1 Из таблицы следует, что исходных состояний два - A и B. Если в исходном состоянии поступает символ 0, то автомат может перейти в состояния A или B. Цепочка 11, например, допускаемая, так как наличие другой возможности не влияет на результат проверки. Один из переходов f (C, 0) - пустой, т.е. при этом дальнейшие переходы невозможны. Работу недетерминированого автомата можно интерпретировать двояким образом: 1. Из состояния А при поступлении символа 0 выбираются состояния A или B, и если при всех возможных последующих вариантах автомат переходит в допускающее состояние, то входная цепочка допускаемая, хотя из всех вариантов имеются некоторые недопускаемые. 2. Автомат распадается на два автомата: один в состоянии А, а другой - в В. При продолжении обработки происходит дальнейшее деление каждого автомата в соответствии с возможностями, содержащимися в таблице переходов. Когда вход обработан, цепочка допускается, если один из результирующих автоматов находится в допускающем состоянии. Эти две интерпретации эквивалентны. 42 3.2.5 Эквивалентность недетерминированных и детерминированных КР Для недетерминированного КР существует детерминированный КР, который допускает в точности те же входные цепочки, что и недетерминированный. Пусть Мн - недетерминированный, а Мд - детерминированный автоматы. Процедура нахождения Мд по имеющемуся Мн. 1. Пометить первую строку таблицы переходов Мд множеством начальных состояний Мн. Применить к этому множеству шаг 2. 2. По данному множеству состояний S, помечающему строку таблицы переходов Мд, для которой переходы еще не вычислены, вычислить те состояния Мн, которые могут быть достигнуты из S с помощью каждого входного символа X и поместить множества последующих состояний в соответствующие ячейки таблицы для М. 3. Для каждого нового множества, порожденного переходами на шаге 2, посмотреть имеется ли уже в Мд строка, помеченная этим множеством. Если нет, то создать новую строку и пометить ее этим множеством. 4. Если в таблице Мд есть строка, для которой еще не вычислялись переходы, вернуться назад и применить к этой строке шаг два. Если все переходы вычислены, то перейти к шагу 5. 5. Пометить строку как допускающее состояние Мд тогда и только тогда, когда она содержит допускающее состояние Мн. В противном случае - пометить как отвергающее. 3.2.6 Расширение КР до преобразователя Нами рассмотрено, как КР использовать для определения множества цепочек и как их превращать в простейшие процессоры, которые могут выдавать "да", когда входная цепочка допускается, или "нет", когда цепочка отвергается. На практике конечные процессоры (практические) должны делать нечто большее. Теория автоматов не может дать ответ, как расширять КР до процессоров специального назначения, поскольку требования к ним зависят от конкретных приложений. Этот последний шаг, от распознавателя к практическому процессору, состоит в дополнении переходов короткими процедурами. Чтобы увидеть, как можно расширить КР до практического процессора, построим процессор, распознающий константы языка высокого уровня и превращающий их в последовательности битов, 43 которые являются их внутренним представлением в виде чисел с плавающей точкой. Эта конструкция включается в лексический блок транслятора. На первом шаге вычисления число представляется в виде двух целых чисел, которые называются значащей частью и порядком,например: 12.35Е14 имеет значащую часть 1235 и порядок-12. На процессе преобразования этих чисел в последовательности бит мы не останавливаемся, полагая их известными. Кроме того мы не учитываем (в целях упрощения) наличие знака перед числом и различные процедуры обработки (переполнение и т.п.). Входное множество распознавателя состоит из 4-х символов, а именно А={ЦИФРА . Е ЗНАК }. Предполагаем, что имеется процессор, переводящей символы языка в эти четыре символа, играющие роль лексем. Конкретно имеется в виду следующий перевод символов: 1. Каждая из цифр 0, 1, 2,. ..и 9 переводится в пару, состоящую из символа "цифра" и значения этой цифры. 2. Каждый знак + или - переводится в пару, состоящую из символа "знак" и указания на то, какой это знак. 3. Буква Е переводится в символ Е; точка переводится в точку, причем, обе эти лексемы не имеют значений. Выписав несколько цепочек (38.7Е-3.; .9Е21; 1123456; 73466), мы можем выделить семь состояний. Эти состояния можно описать так: 1) цифра перед возможно присутствующей десятичной точкой; 2) необязательно присутствующая десятичная точка; 3) цифра после десятичной точки; 4) буква Е; 5) знак порядка; 6) цифра порядка; 7) десятичная точка, требующая десятичных цифр. Необходимо различать состояния 2 и 7, что неочевидно. Отличие заключается в том, что после вхождения символа в состояние 2 может следовать не только цифра, но и Е, а также может не следовать ничего (38.Е-21; 38.). После вхождения символа в состояние 7 должна следовать цифра (обязательно), так как цепочки, вроде . Е-21 или (.) - одна точка, не считаются правильно построенными. Затем мы вводим начальное состояние 0 и используем это состояние вместе с описанными состояниями для построения таблицы переходов. Состояния, которые могут завершить цепочку, отмечаются как допускающие состояния. Результат показан в таблице 3.1. 44 Таблица 3.1 Ц Е 1 0 1 1 4 2 3 4 3 3 4 4 6 5 6 6 6 7 3 . 7 2 Таблица 3.2 Ц Е 1 0 1 1 23 23 4 3 5 0 1 1 1 0 0 1 0 4 5 6 7 6 6 6 23 4 . 7 3 0 1 1 23 5 0 0 1 0 Полученный автомат фактически детерминированный, если пробелы в таблице интерпретировать как переходы в состояние ошибки, которое не включено в список состояний. Очевидно, этот автомат не является приведенным, так как состояния 2 и 3 эквивалентны, поэтому следует их объединить в одно состояние, назовем его 23. Результат показан в таблице 3.2. Следующий шаг построения процессора состоит в добавлении к входному множеству концевого маркера и присвоении каждому переходу своего имени (отдельного). Результат показан в таблице 3.3. Таблица 3.3 Ц Е . 3 0 1а 7 1 1в 4а 23с да1 23 23а 4в да2 4 6а 5 5 6в 6 6с да3 В исходной таблице было два перехода в состояние 1, мы назвали их 1а и 1в, чтобы различать в процедурах обработки, описываемых ниже. Допускающим состояниям, в которые КР переходит при чтении концевого маркера, также даны разные имена. Механическая часть построения процедуры завершена, и начинается специальная часть. Переходы автомата интерпретируются как вызовы процедур или частей объектного кода, которые должны выполняться при переходах. Задача разработчика - описать эти процедуры так, чтобы получить желаемый результат, который заключается в данном случае в переходе входной цепочки в пару, состоящую из значащей части и порядка. 45 Чтобы выполнить это преобразование, введем четыре переменные: регистр числа - РЧ, регистр порядка - РП, регистр счетчика - РС и регистр знака - РЗ. Их назначение: - РЧ - для накопления значащей части; - РП - для накопления порядка; - РС - для подсчета разрядов, следующих за десятичной точкой; - РЗ - для запоминания знака порядка. Теперь обеспечим каждый переход процедурой: - 1а : поместить значение цифры в РЧ. Для очередного входного символа сделать переход из состояния 1; - 1в : умножить содержимое РЧ на 10. Прибавить значение цифры и запомнить результат в РЧ. Для очередного входного символа сделать переход из состояния 1; - 23а: увеличить РС на 1. Умножить содержимое РЧ на 10. Прибавить значение цифры и запомнить результат в РЧ. Для очередного входного символа сделать переход из состояния 23; - 23в: инициализировать РС единицей. Поместить значение цифры в РЧ. Для очередного входного символа сделать переход из состояния 23. - 23с: инициализировать РС нулем. Для очередного входного символа сделать переход из состояния 23; - 4а : инициализировать РС нулем. Для очередного входного символа сделать переход из состояния 4; - 4в : для очередного входного символа сделать переход из состояния 4; - 5 : если знак +, заслать +1 в РЗ. Если знак -, заслать -1 в РЗ. Для очередного входного символа сделать переход из состояния 5; - 6а : поместить +1 в РЗ. Поместить значащие цифры в РП. Для очередного входного символа сделать переход из состояния 6; - 6в : поместить значение цифры в РП. Для очередного входного символа сделать переход из состояния 6; - 6с : умножить содержимое РП на 10. Прибавить значение цифры и запомнить результат в РП. Для очередного входного символа сделать переход из состояния 6; - 7 : для очередного входного символа сделать переход из состояния 7; - да1: заслать 0 в РП. Прекратить работу автомата; - да2: заслать в РП значение РС с минусом. Прекратить работу автомата; - да3: если РЗ равен -1, сделать отрицательным РП. Вычесть РС из РП. Прекратить работу автомата. 46 Таблица 3.4 входной символ переход число порядок счетчик знак 4 7 . 2 Е 1 3 1а 1в 23с 23а 4в 5 6в 6с да3 4 47 47 472 472 1 13 -14 0 1 1 1 1 1 1 -1 -1 -1 -1 472 472 Для завершения построения процессора нужно поместить в пустые ячейки таблицы вызовы процедур, обрабатывающих ошибки. Чтобы посмотреть, как работает автомат, и приобрести некоторую уверенность в том, что он действительно работает, построим проверочную программу. В таблице 3.4 приведен пример реализации программы для константы 47.2Е-13. 3.3 Реализация конечных распознавателей 3.3.1 Введение Реализация КР (процессора) в трансляторе связана со следующими проблемами: 1. Как представить входы, состояния и переходы КР, чтобы удовлетворить противоречивым требованиям по быстродействию и затратам памяти? 2. Как решить специфические проблемы, возникающие при трансляции? 3. Как расчленить задачу построения транслятора, чтобы получить КР, допускающий простую реализацию? В языках высокого уровня имеется конечное множество слов, типа IF, GOTO, READ и т.п., которые необходимо распознавать (идентифицировать). Задачу такого характера называют проблемой "идентификации". Для решения такой задачи может потребоваться огромное число состояний. По этой причине нередко рекомендуется строить транслятор так, чтобы проблема идентификации решалась отдельным процессором, специально предназначенным для этого. Однако проблема идентификации переменных принципиально не может быть решена КР обычного типа, так как число переменных 47 практически может быть неограниченно и число состояний КР становится бесконечным. Это затруднение преодолевается с помощью понятия расширяющегося КР. При считывании своей входной цепочки этот автомат отводит для идентификатора необходимое место в памяти и элемент в таблице, как только тот впервые встречается в программе. Если этот идентификатор встречается в программе снова, то, чтобы идентифицировать его, автомат использует технику идентификации слов с помощью КР. Когда появляется новый идентификатор, автомат опять расширяется и т. д. Хотя этот автомат не является, строго говоря, конечным, к нему применимы многие принципы построения и анализа КА. Большинство методов идентификации потенциально полезны при идентификации как фиксированных множеств, для которых память отводится заранее, так и расширяющихся множеств. Поэтому в данном случае необходимо учитывать ещё один параметр - легкость расширения. 3.3.2 Представление входных символов Как правило, входные символы нумеруются десятичными числами, которые и представляют эти символы. Проблема кодировки может возникнуть при чтении символов исходной программы. Обратим внимание на этот вопрос. Если бы множество символов исходной программы непосредственно служило входом для некоторого конечного процессора (КР), содержащего много состояний, то автомат имел бы большую таблицу переходов, так как одни только цифры и буквы английского алфавита составили бы 36 символов, не включая остальных. Удобный способ уменьшения таблицы заключается в обработке исходных символов двумя последовательно соединенными автоматами: первый автомат это транслитератор, единственная задача которого сократить входное множество до приемлемых размеров; второй автомат выполняет остальную часть работы (рис. 3.2). Исходный транслитератор язык входное КА множество Рисунок 3.2 Выход транслитератора называется символьной лексемой. Такая лексема состоит из двух частей: класса и значения. Так, класс символа а - 48 "буква" (1) и ее значение 'a' и т.д. Некоторые лексемы имеют только класс, ввиду своей уникальности ( . , ). Класс лексемы служит входом для второго КА, и ее значение доступно процедурам переходов этого автомата (см., например, обработку констант). Транслитератор - это просто процессор с одним состоянием, хотя вряд ли имеет смысл рассматривать его именно в этом качестве. Он реализуется как процедура, которая находит в таблице перевод для каждого входного символа. символ а b … z 1 2 … 9 0 + * / . , и т.д. перевод класс буква 1 1 … 1 цифра 2 2 … 2 2 операция 3 3 3 3 5 6 значение a b … z 1 2 … 9 0 + * / Множество классов символьных лексем обычно представляется с помощью последовательности натуральных чисел, так как они являются возможными входными символами для второго КА. 3.3.3 Представление состояний Есть два основных способа, с помощью которых программа, моделирующая КА, может запоминать состояние моделируемого автомата. Первый заключается в запоминании номера, соответствующего текущему состоянию в некотором регистре или ячейке памяти машины. Этот способ называется явным, так как состояние явно задается некоторой переменной. Второй основной метод заключается в том, что для каждого состояния имеется отдельная часть программы. Поэтому тот факт, что моделируемый автомат находится в заданном состоянии, "запоминается" тем, что моделирующая программа исполняет часть кода, которая "принадлежит" этому состоянию. 49 Такой метод называется неявным. Явный или неявный метод выбирается, исходя из удобства реализации. Важно отметить, что слово "состояние" не подразумевает никакой особой техники реализации. 3.3.4 Выбор переходов Суть программы, моделирующей КА, состоит в способе выбора переходов, так как для заданного состояния и очередного входного символа программа должна выполнить переход, указанный в таблице переходов. Поэтому информация, содержащаяся в таблице переходов, должна где-то храниться или же ее надо включить в моделирующую программу. Предположим, что состояния запоминаются неявно. В этом случае косвенно известна нужная строка таблицы переходов и задача сводится к нахождению перехода только по входному символу. Эта задача решается для каждого состояния в отдельности. Рассмотрим два метода решения задачи выбора перехода: - метод вектора переходов; - метод списка переходов. МЕТОД ВЕКТОРА ПЕРЕХОДОВ Согласно этому методу, адреса или метки тех процедур переходов, на которые должно передаваться управление, хранятся в виде вектора в последовательных ячейках памяти, по одной ячейке для каждого входного символа. Очередной входной символ служит индексом, по которому выбирается элемент вектора, дающий нужный переход. Для того, чтобы этот метод работал, входное множество должно быть представлено какимнибудь подходящим образом, например, в виде множества последовательных целых чисел. Пример такого вектора приведен в таблице 3.5, где изображен вектор переходов для состояния А конечного распознавателя, представленного в таблице 3.6. Таблица 3.5 адрес А1 адрес ОШ Таблица 3.6 1 A A1 B C адрес ОШ адрес В3 2 3 C2 C3 адрес ОШ 4 B3 A4 5 B5 50 адрес А6 6 A6 B6 адрес ОШ адрес ОШ 7 C1 Достоинство метода в том, что нужную процедуру перехода можно выбрать очень быстро. Затраты памяти - по ячейке на каждый элемент строки. В языках программирования этот метод реализуется переключателем или вычисляемым переходом. МЕТОД СПИСКА ПЕРЕХОДОВ Согласно этому методу входные символы делятся на два класса: каждому символу 1 класса приписывается индивидуальный переход, а символам 2 класса - общий переход (процедура обработки ошибок - ОШ). В таблице 3.7 показан пример списка для состояния А (первая строка табл. 3.6). Таблица 3.7 1 A1 6 A6 4 B3 обработка ОШ 2,3,5,7, Выбор одного или другого метода определяется конкретными условиями, например: для состояния В - предпочтительней первый метод, а для состояния С - второй метод. Если состояние хранится в явном виде, то оба метода применимы и возможны разные варианты. Одна возможность - пользование состоянием как индексом для передачи управления одной из описанных процедур. Другая заключается в том, чтобы хранить таблицу переходов как единый двумерный массив, индексы которого - это состояния и входные сигналы. 3.3.5 Автоматная идентификация слов Начнем с проблемы идентификации слов, когда задано конечное множество слов в некотором алфавите, и нужно установить, с каким элементом множества совпадает некоторое слово. Необходимо построить процессор, который по предъявленному входному слову с концевым маркером устанавливает его идентичность некоторому элементу множества. Автомат, идентифицирующий некоторое множество слов, можно построить путем введения в него состояния для каждой подцепочки, которая может стать префиксом какого-либо слова из этого множества. Множество префиксов включает "пустую цепочку", а также сами заданные слова, так как каждое слово является своим префиксом. Продемонстрируем эту процедуру на примере. Задан входной алфавит {a, d, e, o, r} плюс концевой маркер; множество слов для идентификации {read, do}(табл. 3.8) 51 Таблица 3.8 a d r do re rea rea read d d e o do re read r r нет нет нет да нет нет да Процессор для этого множества изображен в таблице 3.8. Начальное состояние процессора - . Большинство элементов таблицы являются переходами в состояние ошибки (пустые ячейки). Поэтому метод списка переходов здесь предпочтителен. Автомат можно реализовать еще проще, так как здесь нет процедур переходов и нет необходимости снабжать каждый переход меткой процедуры перехода. Вместо этого, используя списки переходов (1-7), свяжем с каждым неошибочным входным символом указатель на список переходов для следующего состояния (табл. 3.9). Таблица 3.9 1 2 3 4 5 6 7 r 2 e 4 o 6 a 5 d 7 do read d 3 ОШ ОШ ОШ ОШ ОШ ОШ ОШ При поступлении первого символа происходит обращение к первому списку (1). Переход состоит в простой замене указателя списка переходов для текущего состояния на полученный по таблице указатель списка переходов для следующего состояния. Если текущий символ - r, то указатель (2) осуществляет обращение ко второму списку; для символа d (3) – к третьему списку; для любого другого символа фиксируется ошибка (ОШ). При поступлении второго символа происходит обработка списка, выбранного на первом шаге, аналогично рассмотренному выше. Данный метод предполагает фиксированный список. Для того, чтобы реализовать расширяющийся список, каждому символу в прежнем списке на самом деле соответствуют два списка. Один - это список переходов, который используется, если символ совпадает с входным символом, а другой - список дополнительных символов, которые надо проверять, если входной символ не совпал с рассматриваемым символом таблицы. 52 Допустим, что множество анализируемых слов расширяется за счет введения еще одного слова readln. Тогда списки переходов можно представить в виде таблицы 3.10. Таблица 3.10 1 2 3 r 2 d l e o n a d do readln read Адрес первого символа задается соответствующим указателем. В этой новой реализации каждому символу соответствует либо указатель на следующую проверку, либо пустой элемент. Этот пустой элемент указывает, что больше нет символов, с которыми нужно производить сравнение, т.е. имеет место ошибка, например, если первый символ d, то сравнивается d с r в первом списке. Так как имеет место несовпадение и символу r соответствует указатель на список 2, процессор сравнивает d с d в списке 2. Символы совпадают и начинается прием очередного символа. Если это, например, ошибочный символ a, то он сравнивается с очередным символом o списка 2, так как имеет место несовпадение и отсутствует указатель (пустой элемент), то следует заключение о том, что нет слова da, и фиксируется обработка "ошибки". Концевому маркеру также может соответствовать указатель списка (например, список 1) или пустой элемент (список 3). Когда обнаруживается совпадение с концевым маркером, то входное слово идентифицируется со словом данного списка. Если мы хотим расширить наш автомат, чтобы он мог дополнительно обрабатывать слово readln, то после символа d первого списка должен допускаться символ l. Для этого в списке 1 против символа устанавливается указатель 3 на дополнительный список 3. Это изменение не повлечет за собой перестройку всей исходной структуры, поскольку дополнительная память добавляется в конце уже используемой области памяти. Автоматная идентификация используется для распознавания служебных слов языков программирования высокого уровня, при этом каждое распознанное слово представляет собой лексему, которой присваивается уникальный класс (число), и который является "внутренним" именем этой лексемы при последующей обработке в синтаксическом блоке. 53 3.3.6 Идентификация слов методом индексов Возможна идентификация слов без использования автомата. Эти методы основаны на хранении всех возможных слов в виде таблицы (массива, списка) и относятся к группе методов табличной идентификации, применяемых для распознавания различного типа имен (идентификаторов). Первый из этих методов основан на использовании индексов при распознавании идентификаторов. Когда на вход поступает какое-либо слово, представляющее собой имя программы (имя переменной и т.п.), выясняется, есть ли это слово в списке. Если данное слово в списке имеется, то оно принимается, если нет - отвергается. Метод основан на том, что по входной цепочке вычисляется индекс, который обеспечивает прямой доступ к таблице (по конкретному слову), например: если имя переменной может быть одной латинской буквой или одной буквой и одной цифрой (а, b1, c7 и т.п.), то индекс любой переменной может быть вычислен по формуле N+26*(d+1), где 26 - число букв в алфавите, d - цифра. Тогда индекс Z будет 26, индекс A0 - 27. Вторая часть формулы прибавляется, когда после буквы стоит цифра (Z9 имеет индекс 286, так как 26+26*(9+1) = 26+260 = 286). Таблица, хранящая индексированные переменные, называется индексной. Но индекс может указывать и на таблицу указателей, содержащую указатели на таблицу имен. В этом случае элемент таблицы имен появляется только в том случае, когда это слово есть в программе. Поэтому таблица указателей имён в начале всегда инициализируется нулями, а затем по мере появления имен ставятся указатели элементов в таблице имен. 3.3.7 Идентификация слов методом линейного списка В этом случае в памяти хранится таблица и входное слово последовательно сравнивается с хранимыми, пока не произойдет совпадение. При совпадении - слово допускается, при несовпадении отвергается. Если слов М, то минимальное число совпадений - 1, максимальное М, а среднее - (М+1)/2. Есть два способа хранения слов в списке: последовательное и связанное. Последовательное хранение предусматривает отведение определенной области памяти, где слова в ячейках располагаются последовательно. Связанное хранение предусматривает наличие указателей на каждое последующее слово. 54 3.3.8 Идентификация слов методом упорядоченного списка Этот метод называется методом логарифмического поиска (бинарного поиска, дихотомии). При поступлении входного слова весь список (таблица) разбивается на две равные части и проверяется, в какую половину должно входить слово. При этом используется алфавитный принцип, например: если слово начинается на "Ш", то оно входит во II половину списка. После этого одна половина отбрасывается, а вторая опять разбивается на две равные части, и процедура повторяется до обнаружения или не обнаружения совпадения слов. Среднее число совпадений в этом случае определяется выражением k = log2M. Недостаток метода - неудобство расширения списка, так как слова должны вставляться в определенные места. Эту процедуру легко применять к связанным спискам, если каждое слово снабдить двумя указателями: один определить на середину списка после данного слова, а другой - на середину списка до данного слова. Такой список размещается в виде дерева (рис. 3.3). array begin ask boolean do else for end goto integer own procedure string until real then value real while Рисунок 3.3. Входное слово for сравнивается c integer, do, end и for. Если добавляется слово, например ask, то оно присоединяется к array. Расширение эффективно до некоторого предела, так как нарушается логарифмическая зависимость поиска и тогда необходимо произвести реструктуризацию списка. 55 3.3.9 Идентификация слов методом расстановки (хеширования) Идеальным методом решения проблемы идентификации больших и расширяющихся множеств слов был бы такой, который позволял бы легко расширять множества, требовал умеренных затрат памяти и гарантировал обнаружение заданного слова (в среднем) при небольшом числе сравнений. Рассмотрим некоторый гибридный метод, обладающий всеми достоинствами. Идентификация осуществляется за два шага: 1.По входному слову вычисляется индекс (как в 3.3.6), однако в данном случае одни и те же слова могут иметь один и тот же индекс. Этот индекс затем используется для нахождения указателя списка в таблице указателей списков. Если он равен 0, т.е. указывает на нулевой список, то входное слово не принадлежит множеству. В противном случае применяется шаг 2. 2.Найденный элемент таблицы указателей списков указывает на некоторый связанный список слов, индекс которых совпадает с вычисленным индексом. Поиск в этом списке продолжается до совпадения или до конца списка. В последнем случае множество не содержит входного слова и это слово можно добавить, просто связав его с последним элементом списка. В литературе этот метод расстановки (хеширования) описывается в терминах "гроздей". Говорят, что каждый индекс указывает на некоторую гроздь, и все слова, имеющие этот индекс, принадлежат одной грозди (рис. 3.4), 0 for begin then until 1 goto 2 integer value real 3 while boolean else own 4 step string 5 do array end Рисунок 3.4 например: индекс слова array получается как остаток от деления суммы чисел, представляющих первые две буквы слова a(1) и r(18) на 7 (простое число) - (1+18)/7, т.е. остаток от деления равен 5. Цель вычисления индекса - уменьшить длину списка, в котором должен производиться поиск. Задача оптимизации состоит в том, как список из М слов распределить на N групп. При увеличении числа групп уменьшается время поиска слова в группе, но увеличивается время вычисления индекса и объем памяти для 56 хранения индексов. При уменьшении числа групп - наоборот. Для каждого конкретного случая требуется оценка. Табличная идентификация применяется для распознавания имен различных объектов языков программирования высокого уровня. При этом каждое распознанное слово представляет собой лексему, которой присваивается общий класс идентификаторов (число) и значение, в качестве которого выступает само уникальное имя. Класс и значение каждой лексемы-идентификатора используются при последующей обработке в синтаксическом блоке. Контрольные вопросы 1. Принцип действия транслятора. 2. Реализация конечных распознавателей. 3. Дать определение автоматной идентификации лексем. 4. Дать определение табличной идентификации лексем. 4 Синтаксический анализ языков программирования 4.1 Введение Теория конечных распознавателей является адекватной теоретической базой для разработки конечных процессоров лексических блоков только для достаточно простых случаев лексического анализа. При распознавании контекстно-свободных языков используются автоматы с магазинной памятью (МП-автоматы). В отличие от КР для МП-распознавателей оказывается сложнее задать процедуры расширения (выход из распознавания, вычисление значений и т.д.). Поэтому теория распознавания КС-языков сама по себе не обеспечивает адекватной теоретической базы для построения трансляторов. Необходимо ее дополнение (модификация), которое позволило бы решать все задачи трансляции. Это дополнение базируется на технике, в которой процесс обработки КС-языка определяется в терминах обработки каждого конкретного правила соответствующей грамматики. Обобщенно эта техника носит название - синтаксически управляемые методы. Эти методы базируются на понятиях "транслирующая грамматика" и "атрибутная грамматика". 57 4.2 Автоматы с магазинной памятью (МП - автоматы) 4.2.1 Определение МП - автомата Материал предыдущего раздела ответил на вопросы осуществления лексического анализа языков программирования высокого уровня и синтеза основных частей лексического блока (транслитератора, КР служебных слов, КР констант, распознавателя идентификаторов). Как было показано, ключевая роль при этом отводится конечным автоматам, используемых для распознавания цепочек. КА имеют небольшой объем памяти, однако возникает много задач, которые не могут быть решены при таком ограничении. Рассмотрим, например, задачу обработки скобок в арифметических выражениях, которые могут иметь неограниченное количество левых скобок, и транслятор должен проверить, имеется ли такое же количество правых скобок. Для этого КА усиливается дополнительной магазинной памятью (МП) или памятью типа стека. Особенность МП состоит в том, что символы можно помещать в МП и удалять по одному, причем удаляемый символ - это всегда тот, который был помещен последним. Иногда этот принцип интерпретируют так: первый пришел - последний ушел (обслужен); последний пришел - первый ушел (LIFO-last in first out). На рисунке 4.1, а) в МП - четыре символа АВАС (причем С поступил последний). Если один символ удалить (это будет С), то останется 3 - АВА (рисунок 4.1, б). На рисунке 4.1, в) удалены все символы и имеется только символ дна МП (или маркер дна). Он никогда не выталкивается из МП . С А В А А В А С г) в) Рисунок 4.1 МП - автомат (МПА) изображается следующим образом (рис. 4.2). МПА может находиться в одном из состояний (блок q), как КА и еще имеет МП, куда может помещать и извлекать информацию. а) б) 58 состояние q 1 0 0 1 1 входная цепочка 1 0 0 ┤ С А магазин В Рисунок 4.2 Как и в КА, обработка входной цепочки осуществляется за ряд мелких шагов. На каждом шаге действия автомата конфигурация его памяти может изменяться за счет перехода в новое состояние, а также вталкивания символа в магазин или выталкивания из него. Однако в отличие от КА, МПА может обрабатывать один входной символ в течение нескольких шагов. На каждом шаге управляющее устройство автомата решает, пора ли закончить обработку текущего символа и получить, если это возможно, новый входной символ или продолжить обработку текущего символа на следующем шаге. Каждый шаг обработки задается правилами, использующими информацию трех видов: 1) состояние автомата; 2) верхний символ магазина; 3) текущий входной символ. Это множество правил называется управляющим воздействием. На рисунке 4.2 состояние автомата - q, верхний символ - С, текущий входной символ - 0. В зависимости от управляющего воздействия автомат или прекращает обработку, или переходит в новое состояние. Переход состоит из трех операций: над магазином, над состоянием и над входом. Операции над магазином: - втолкнуть в магазин определенный магазинный символ; - вытолкнуть символ (верхний); - оставить магазин без изменений. Операции над состояниями: -перейти в новое состояние. Операции над входом: - перейти к следующему входному символу и сделать его текущим; - оставить данный входной символ текущим, т.е. держать его до следующего шага. Если текущим входным символом является концевой маркер, то МПА не должен требовать входного символа и не должен выталкивать символ из магазина, если это маркер дна. 59 Таким образом МПА задается: 1. Конечным множеством входных символов (включая концевой маркер). 2. Конечным множеством магазинных символов (включая маркер дна). 3. Конечным множеством состояний, включая начальное состояние. 4. Управляющим воздействием, которое каждой комбинации входных символов, магазинных символов и состояний автомата ставит в соответствие выход и переход. Переход заключается в выполнении операций над магазином, состоянием и входом. 5. Начальным содержимым магазина. МПА называется МП - распознавателем (МПР), если у него два выхода - допустить и отвергнуть (входную цепочку). Рассмотрим обработку скобок МПР, который при появлении левой скобки вталкивает символ А в магазин, а при появлении правой скобки выталкивает символ А. Зададим МПР, обрабатывающий скобки: 1) входное множество {( ) }; 2) множество магазинных символов {A, }; 3) множество состояний {S}, где S - начальное состояние (одно, следовательно, не меняется), например: следует обработать последовательность скобок ( ( ) ( ) ) . На рисунке 4.3 показаны шаги работы МПР при обработке скобок. а: б: в: г: д: е: ж: з: А АА А АА А допустить (()()) ┤ ()()) ┤ )()) ┤ ()) ┤ )) ┤ ) ┤ ┤ S S S S S S S Рисунок 4.3 Управляющее устройство рассмотренного МПР можно задать следующей управляющей таблицей А ( втолкнуть (А) сдвиг втолкнуть (А) сдвиг ) вытолкнуть сдвиг отвергнуть 60 ┤ отвергнуть допустить 4.2.2 Некоторые обозначения для множеств цепочек Рассмотрим три операции над множествами цепочек: - объединение, - конкатенация, - итерация Клини (или просто итерация). ОБЪЕДИНЕНИЕ. Если Р и Q - множества цепочек, то объединение определяется выражениями PUQ или P+Q (в теории автоматов). Пример. {FOR, IF, THEN}+{DO, IF}={FOR, IF, THEN, DO} . КОНКАТЕНАЦИЯ. Это сцепление двух цепочек, определяемое выражением P*Q. Пример. Задано P ={10}; Q ={1, 00}; W = P * Q = {10} * {1, 00} = {101, 1000} Конкатенация множества R c самим собой, обозначается R*R . ИТЕРАЦИЯ КЛИНИ. Если А - множество символов алфавита, то А* множество всех цепочек, составленных из символов алфавита А, причем А* всегда содержит ε- пустую цепочку. Так {0, 1}* обозначает множество всех цепочек в алфавите {0, 1}. Часто используют вариант итерации А+, называемый позитивной итерацией А+ = АА*, т.е. А+ = А1+А2+А3+..... Применение этих операций к регулярным множествам дает регулярные множества. Регулярное множество распознается КА. Можно дополнить данные операции так, что в результате получим нерегулярные множества из регулярных. Первый способ - в использовании переменных как показателей степеней при обозначении множеств {1n0n|n>0}=10, 111000, 11110000,… {1n0m|n>m} = 1110, 111100, и т.д. Второй способ - это обозначение операции обращения цепочки с помощью верхнего индекса r (abc)r=cba. 4.2.3 Распознавание нерегулярных множеств МПА Рассмотрим задачу распознавания множества {1n0n|n>0}. В качестве первого шага построения МПР опишем словами схему распознавания: начальный отрезок цепочек, состоящий из нулей, вталкивается в магазин. Затем каждый раз, когда встречается 1, один 0 выталкивается из М. Цепочка допускается тогда и только тогда, когда в момент завершения считывания цепочки М пуст. Если после первого вхождения 1 встречается 0, цепочка сразу же отвергается (рис. 4.4). 61 На рисунке 000111. 1: 2: Z 3: ZZ 4: ZZZ 5: ZZ 6: Z 7: 8: допустить 4.4, а/ приведен пример приема "правильной" цепочки S1 S1 S1 S1 S2 S2 S2 0000111┤ 00111┤ 0111┤ 111┤ 11┤ 1┤ ┤ Z ZZ Z отвергнуть 1: 2: 3: 4: 5: а) S1 S1 S1 S2 001011┤ 01011┤ 1011┤ 011┤ б) Рисунок 4.4 При приеме 0 в М вталкивается Z (состояние S1). Когда начинается прием 1, МПР переходит в состояние S2 и Z из М выталкивается. К концу приема М пуст. На рисунке 4.4, б) принимается "неправильная цепочка" 001011. После приема нулей (состояние S1) до позиции 4 МПР переходит в состояние S2 и поэтому прием 0 в состоянии S2 МПР приводит к отвержению цепочки. Определение данного МПР следующее: 1) входное множество {0, 1, ┤} , {0n1n|n>0}; 2) множество магазинных символов {Z, }; 3) множество состояний {S1, S2}, где S1 – начальное; 4) начальное содержимое М - . Управляющие таблицы МПР для двух состояний приведены ниже. 0 Z ┤ 1 S1 S2 втолк.(Z) вытолкн. сдвиг сдвиг S1 втолк.(Z) отвергн. сдвиг 0 1 ┤ S2 вытолк. сдвиг отвергн. отвергн. Z отвергн. отвергн. отвергн. отвергн. состояние 1 допустить состояние 2 4.2.4 Расширение операции под магазином Рассмотренный МПА называется примитивным, так как он включает не более одной операции вталкивания и выталкивания при переходах. Это накладывает ограничение на практическую реализацию. Введем расширенную операцию над магазином, которая называется ЗАМЕНИТЬ. 62 Она заключается в выталкивании одного верхнего символа М и вталкивании нескольких новых символов, например: ЗАМЕНИТЬ (ABC) означает:ВЫТОЛКНУТЬ(верхний символ)→ВТОЛКНУТЬ(А)→ВТОЛКНУТЬ(В)→ВТОЛКНУТЬ(С). Если ЗАМЕНИТЬ (АВС) применяется к МПА с магазином XYZ, то новый магазин будет: XYABC. Применим новую операцию для распознавания множества {0n1n}. Новый МПА использует тот же метод счета, что и предыдущий (вталкивание и выталкивание символа Z). Однако для различения фаз вталкивания и выталкивания используется другая стратегия. Во время фазы вталкивания в верхней ячейке магазина хранится новый магазинный символ Х. Единственное его назначение - напоминать МПА, что он находится в фазе вталкивания (рис. 4.5). Когда впервые встречается 1, Х выталкивается из магазина и МПА начинает сопоставлять символы Z и единицы. 1: X 0000111┤ 6: 11┤ ZZ 2: ZX 00111┤ 7: 1┤ Z ┤ 3: ZZX 0111┤ 8: 4: ZZZX допустить 111┤ 9: 5: ZZZ 111┤ Рисунок 4.5 Операция "заменить" реализуется при вталкивании 0. В этом случае происходит выталкивание Х, вталкивание Z и Х. Появляется новая операция "держать", которая реализуется при переходе, на котором Х выталкивается из М и начинается "фаза выталкивания". Входной символ 1 удерживается, т.е. сдвига на входе не происходит. Сравнивая новый МПА, видим, что он имеет только одно состояние и два вида информации: входные и магазинные символы. С другой стороны операции с магазином усложняются. Если примитивный МПА требовал память для определения состояний, то расширенный МПА этой памяти не имеет, она как бы "перешла в магазин". А если ее оставить, то можно использовать для реализации дополнительных функций. Управляющая таблица данного МПА имеет следующий вид: 0 X Z заменить (ZX) сдвиг отвергн. 1 вытолк. X держать вытолк. Z ┤ отвергн. отвергн. 63 отвергн. сдвиг отвергн. допустить 4.2.5 Перевод с помощью МПА МПА называется транслятором, если при распознавании он порождает выходную цепочку. Для того чтобы МПА это делал, управляющее устройство должно помимо операции над состоянием, входом и магазином производить операции над выходом. Если надо выдать цепочку АВ, то в определении соответствующего МП- перехода мы пишем ВЫДАТЬ (АВ). Рассмотрим, как преобразовать цепочку из 1 и 0 в цепочку вида 1n0m, где n и m число 1 и 0 в исходной цепочке(рис. 4.6). 1: 2: 3: 4: 5: 6: 7: 8: 9: А А АА АА АА А допустить выдать 1 выдать 1 выдать 1 выдать 0 выдать 0 а) 01011┤ 1011┤ 0 011┤ втолкнуть А 11┤ сдвиг 1┤ А ┤ ┤ втолкнуть А сдвиг ┤ Рисунок 4.6 1 выдать 1 сдвиг ┤ выдать 0 вытолкнуть А выдать 1 сдвиг закончить б) Например, необходимо преобразовать цепочку 011011→111100. Один способ такого перевода заключается в том, чтобы выдавать единицы сразу при их появлении на входе, а при появлении нулей помещать их в магазин. Когда встречается концевой маркер, автомат выталкивает из магазина нули и выдает их на выход. На рисунке 4.6 изображены управляющая таблица МПА и этапы обработки. В данном случае магазин служит не для распознавания, а для перевода. Нули вталкивают в магазин символ А только для того, чтобы позже автомат выдавал их на выход в соответствии с количеством символов А в магазине. Рассмотрим еще один пример перевода цепочки w2wr в цепочку 1n0m, где n и m соответствует числу 1 и 0 в цепочке w. 01011211010 → 11100, где w =01011, wr=11010 (обращенная цепочка). Для того чтобы выполнить этот перевод, построим МПА, работающий в двух фазах. Первая из них - фаза вталкивания длится до тех пор, пока на входе не встретится символ 2. Во время этой фазы входные символы 64 сравниваются с магазинными символами, чтобы проверить, совпадают ли они. В результате мы убедимся, что цепочка после символа 2 действительно является обращением цепочки, предшествующей 2. При совпадении символов каждый встреченный нуль выдается на выход. Фазы МПА запоминаются с помощью состояния. Две управляющие таблицы задают МПА, реализующий процесс перевода. 0 1 сост. (Ф1) сост. (Ф1) 0 втолк. 0 втолк. 1 сдвиг выдать 1 сдвиг сост. (Ф1) сост. (Ф1) 1 втолк. 0 втолк. 1 сдвиг выдать 1 сдвиг сост. (Ф1) сост. (Ф1) втолк. 0 втолк. 1 сдвиг выдать 1 сдвиг 2 сост. (Ф2) ┤ 0 1 2 ┤ сост.(Ф2) отверг. 0 вытолк. отверг. отверг. отверг. сдвиг выдать 0) сдвиг сост. (Ф2) сост.(Ф2) отверг. 1 отверг. вытолк. отверг. отверг. сдвиг сдвиг сост. (Ф2) сдвиг отверг. отверг. отверг. допусти отверг. ть Состояние - фаза 1 Состояние - фаза 2 На рисунке 4.7 показана работа МПА, отвергающего цепочку 0012101. Мы говорим, что у этой цепочки нет перевода, даже если МПА выдает что-то на выход до того, как обнаруживает входной символ 1, который не совпадает с соответствующим ему символом цепочки, предшествующей символу 2. 0012101 ┤ 1: Ф1 012101 ┤ 2: 0 Ф1 12101 ┤ 3: 00 Ф1 4: 001 Ф1 2101 ┤ 5: 001 Ф2 101 ┤ 6: 00 Ф2 01 ┤ 7: 0 Ф2 1┤ 8: отвергнуть Рисунок 4.7 Автоматы с магазинной памятью, рассмотренные выше, обладают широкими возможностями по обработке нерегулярных цепочек, к которым относятся и программы языков программирования высокого уровня. Это обусловливает их применение при синтаксическом анализе программ, реализуемом в синтаксическом блоке. 65 4.3 Транслирующие грамматики В качестве иллюстрирующего примера используется задача трансляции арифметических выражений. Общепринятой является инфиксная запись, которая отображается следующей грамматикой: 1. <E> → <E> + <T> | <T>. 2. <T> → <T> * <P> | <P> . 3. <P> → (<E>) | I , где <E> - начальный символ; I - произвольное целое число. На рисунке 4.8 показано дерево вывода для цепочки a + bc. <E> /|\ / | \ <E> + <T> | / \ <T> <T> * <P> | | | <P> <P> | | | | a b c Рисунок 4.8 Однако существует другой способ описания арифметических выражений - постфиксная польская запись, разработанная польским математиком Я. Лукасевичем. В постфиксной записи знак операции следует сразу же за ее операндами. Множество выражений с операциями + и * можно породить <E> <E> <E> а <E> <E> b + * c Рисунок 4.9 при помощи грамматики: <E> → <E> <E> + | <E> <E> * | I . Дерево вывода в этом случае показано на рисунке 4.9. Каждому выражению в инфиксной записи соответствует выражение в постфиксной польской записи, например, постфиксной записью для `a+bc` будет `abc*+`, а для `ab+c` - `ab*c+`. 66 Постфиксная польская запись не содержит скобок, даже когда соответствующие инфиксные выражения заключаются в скобки, например: инфиксное выражение `(a+b)c` записывается как `ab+c*`, а выражение `a+b(c+d)*(e+f)` будет `abcd+*ef+*+`. Транслятор должен обеспечить перевод из одной формы записи в другую. Это осуществляется синтаксическим блоком транслятора. Задача состоит в том, чтобы по входной грамматике (грамматике входного языка) построить грамматику выходного языка (транслирующую грамматику). Для рассматриваемого примера это значит, что по КС-грамматике, описывающей инфиксные арифметические выражения (1), получить КСграмматику, которая переводила бы выражения в инфиксной записи в постфиксную форму. Принципиальным здесь является то, что алгоритм обработки (и соответствующее устройство) превращаются из распознающего в транслирующее (переводящее). Следовательно, если конечный распознаватель, построенный на основе КС-грамматики (1), должен был только распознать выражение `a+bc` и сделать его допустимым, то новая процедура обработки должна "перевести" выражение `a+b*c` в выражение `abc*+`. И самое главное, необходимо построить (создать) эту новую транслирующую грамматику, которая бы обеспечила необходимый перевод. Из рассмотренного примера видно, что при переводе можно выделить цепочки символов на входе (a+bc), построенные на основе входной грамматики и цепочки символов на выходе (abc*+), построенные на основе транслирующей грамматики. Начиная с данного момента, будем отличать символы на выходе (в выходных цепочках) тем, что будем заключать их в фигурные скобки {a}{b}{c}{*}{+}. Иногда их называют символами действия. Оказывается, что входную КС- грамматику (1) легко преобразовать в транслирующую КС-грамматику, если правила дополнить символами действия (выходными символами) в требуемых местах. Эти места определяются последовательностью символов в постфиксных записях. Выражения (2) представляют транслирующую грамматику, соответствующую входной КС-грамматике (1): 67 1.<E> → <E> + <T> {+} 2.<E> → <T> 3.<T> → <T> * <P> {*} (2) 4.<T> → <P> 5.<P> → (<E>) 6.<P> → I {I} В правилах 1 и 3 символы действия {+} и {*} расположены после соответствующих операндов, а символ действия {I} в правиле 6 расположен сразу же за соответствующим входным терминальным символом I. В нашем примере терминальный символ I обозначает любой из символов `a, b, c`. Левый вывод входной цепочки `a+b*c` дает следующий порядок применения правил (2) - 2, 4, 6, 3, 4, 6, 6: < E > => < T > + < T >{+} => < P > + < T >{+} => a{a} + < T >{+}=> a{a} + < T > * < P >{*}{+} => a{a} + < P > * < P >{*}{+} => a{a} + b{b}*< P> {*}{+} => a{a} + b{b} * c{c}{*}{+} . Если в конечном результате вывода выделить только входные символы, то получим распознаваемую входную цепочку `a+b*c`. При выделении символов действия (выходных) получим перевод (трансляцию) входной цепочки в выходную `abc*+`={a}{b}{c}{*}{+}. Приведенные выше рассуждения сформулируем в виде математической модели с помощью следующих операций. Транслирующей грамматикой или грамматикой перевода называется КС-грамматика, множество терминальных символов которой разбито на множество входных символов и множество символов действия. Цепочки языка, определяемого транслирующей грамматикой, называются последовательностями актов. Подразумевается, что каждый символ действия представляет процедуру, которая осуществляет выдачу символа, заключенного в скобки. Иногда такая грамматика называется грамматикой, транслирующей в цепочки или грамматикой цепочечного перевода. 68 4.4 Синтаксически управляемый перевод Математически перевод рассматривается как множество пар, где первый элемент принадлежит множеству объектов, которые надо перевести, а второй элемент - множеству объектов, которые являются результатом перевода. Напомним, что первый элемент - это цепочка входного языка, а второй элемент - последовательность, являющаяся результатом перевода входной цепочки. Когда эти пары получают при помощи некоторой грамматики, перевод называют синтаксически управляемым переводом. Как ранее было определено, последовательность актов транслирующей грамматики включает входные символы и символы действия. Входная последовательность (входная цепочка) может быть получена из последовательности актов путем вычеркивания всех входных символов. Считают, что входная последовательность образует пару с последовательностью действий. Множество всех пар называется переводом, определяемым данной транслирующей грамматикой. В рассмотренном примере инфиксная и постфиксная записи арифметических выражений являются множеством пар перевода. Акцентируем внимание на определении грамматик. Входная грамматика - определяет входной язык (в нашем примере формирует инфиксные выражения). Транслирующая грамматика может быть получена из входной грамматики путем добавления в последнюю символов действия. Аналогично входную грамматику получают из транслирующей методом вычеркивания из последней всех символов действия. Можно определить и выходную грамматику (грамматику действий), если из транслирующей грамматики вычеркнуть все терминальные входные символы. Таким образом, транслирующую грамматику можно рассматривать как переводящую с одного КС-языка (входного) на другой КС-язык (выходной). 4.5 Атрибутные транслирующие грамматики 4.5.1 Синтезируемые атрибуты При переводе цепочек символов не учитывалось, что символ может состоять из двух частей (двух характеристик): класса и значения. Транслирующие грамматики учитывают только класс символов. Расширим транслирующие грамматики, включив в перевод и значение символа. Такая грамматика называется атрибутной. Атрибутная транслирующая грамматика (АТГ) - это транслирующая грамматика, к которой добавляются следующие определения: 69 1.Каждый входной символ, символ действия или нетерминальный символ имеет конечное множество атрибутов, и каждый атрибут имеет (возможно, бесконечное) множество допустимых значений. 2.Все атрибуты нетерминальных символов и символов действия делятся на наследуемые и синтезируемые. 3.Заданы правила вычисления атрибутов. В АТГ грамматике атрибуты записываются в виде индексов ( по одному индексу на каждый атрибут), например: <X>p,q,r → <Y>y,u <Z>v,w , где атрибуты q, r - синтезируемые, p, y, v - наследуемые. Тогда, продукция может быть дополнена правилами: q ← SIN(u+w) , (r, v) ← u , y← р . Первое правило вычисляет синтезируемый атрибут q. Второе правило вычисляет два атрибута, причем один из них синтезируемый - r, а второй - наследуемый - v, при этом оба атрибута вычисляются одинаково . Третье правило определяет наследуемый атрибут y. В атрибутной грамматике значения символов сопоставляются со всеми вершинами дерева вывода как терминальными, так и нетерминальными. Отношения между входными и выходными значениями выражаются по принципу "от правила к правилу". При этом значения находятся в вершинах дерева. Допустим, лексический блок задает входное множество {(, ), +, *, с}, где `с` - лексема константы; ее значением является значение константы, построенное лексическим блоком. Рассмотрим теперь проблему проектирования синтаксического блока, который допускает арифметические выражения, сформированные из символов данного входного множества, и выдает численные значения этих выражений. Соответствие между компонентами входов и выходов, представляющих их классы, можно выразить следующей грамматикой, транслирующей в цепочки: 1. <S> → <E> {ответ}. 2. <E> → <E> + <T>. 3. <E> → <T>. 4. <T> → <T> + <P>. 5. <T> → <P>. 6. <P> → (<E>). 7. <P> → C . 70 Значение выходного символа ОТВЕТ должно быть числом. Требуемое отношение между значениями входных лексем и значением выходной лексемы ОТВЕТ можно выразить словами "значение лексемы ОТВЕТ - это числовое значение входного выражения" (рисунок 4.10). Найдем математический способ выражения рассмотренного отношения. Рассмотрим конкретную цепочку (С3 + С9)*(С2 + С41), где значения входных лексем, выданных ЛБ, указаны индексами. Определим, как получить значения для всех частей дерева. С этой целью сопоставим каждому правилу грамматики (продукции) правило вычисления значения вершины, соответствующей нетерминалу левой части продукции, по данным значениям ее прямых потомков, соответствующих символам правой части продукций. Представим продукции и правила вычислений значений в общем виде: 1. < S > < E >q { ОТВЕТ r} 5. < T >p < P >q r q p q 2. < E >p < E >q + < T >r 6. < P >p ( < E >q ) p q + r p q 3. < E >p < T >q 7. < P >p Cq p q p q 4. < T >p < T >q * < P >r p q*r Трактуются правила следующим образом. Для продукции 4 значение <T> в левой части равно значению <T> в правой части, умноженному на значение <P> в правой части и т.д., наконец, для (1) правило вычисления значения будет: значение символа ОТВЕТ равно значению <E>. Правила вычисления отражают "значение" или "смысл" продукций. Однако технику сопоставления правил продукциям можно использовать для определения значений, которые не обязательно отражают явное значение входной цепочки, например, с помощью правил для продукций можно с каждой вершиной дерева связать элемент некоторой таблицы. Продукции вместе с правилами вычисления значений являются атрибутными правилами и образуют атрибутную грамматику. В примере значение атрибута каждого нетерминала определяется символами, рассмотренными в дереве вывода под этим нетерминалом. 71 <S> < E >516 { ОТВЕТ 516 } < T >516 < T >12 * < P >12 < P >43 ( < E >43 ) ( < E >12 ) < E >2 + < T >41 < E >3 + < T >9 < T >2 < T >3 < P >9 < P >2 < P >3 C9 C2 < P >41 C41 C3 Рисунок 4.10 Такое "восходящее" вычисление выражается в том, что правила вычисления атрибутов нетерминалов, ассоциированные с продукциями, указывают, как вычислить атрибуты левой части продукции по данным атрибутам символов правой части (рисунок 4.10). Атрибуты, значения которых получаются таким восходящим способом, т.е. снизу вверх, называются "синтезированными" атрибутами. Замечание по поводу обозначений. Атрибут символа действий пишется вне скобок {X}q, но в нашем случае символ действия образован заключением в скобки выходного символа. В этом случае атрибут может быть внутри скобок {ОТВЕТ 516}, т.е. он должен быть выдан как часть заключенного в скобки выходного символа. 72 4.5.2 Наследуемые атрибуты Если синтезируемые атрибуты вычисляются по правилу: -атрибуты левых частей правил определяются атрибутами правых частей; то наследуемые атрибуты вычисляются по правилу: -атрибуты нетерминала правой части правила определяются символами правой части и атрибутами нетерминала левой части этого правила. Нисходящий характер вычисления значений атрибутов отражается в том, что каждое правило вычисления атрибутов нетерминалов, сопоставленное с продукциями, указывает, как вычислять атрибуты нетерминала, входящего в правую часть продукции. Атрибуты, значения которых задаются таким нисходящим способом, называют наследуемыми атрибутами. Определим, как можно получить атрибутную информацию, чтобы она распространялась вниз по дереву вывода. АТГ используется для определения атрибутных деревьев вывода, а затем - атрибутных последовательностей актов и атрибутных переводов. Деревья определяются следующими процедурами построения: 1.По соответствующей неатрибутной грамматике построить дерево вывода последовательности актов, состоящей из входных символов и символов действия без атрибутов. 2.Присвоить значения атрибутам входных символов, входящим в дерево вывода. 3.Присвоить начальные значения наследуемым атрибутам исходного символа дерева вывода. 4.Вычислить значения атрибутов символов, входящих в дерево вывода, повторяя следующие действия до тех пор, пока это возможно: -найти атрибут, которого еще нет в дереве, но аргументы правила его вычисления уже имеются, -вычислить значение этого атрибута и добавить его к дереву. 5.Если выполнение п.4 приведет к тому, что значения всех атрибутов всех символов дерева окажутся вычисленными, то будем называть полученное дерево завершенным. В противном случае незавершенным. Если АТГ всегда приводит к построению завершенного дерева, то АТГ называется вполне определенной (корректной). Именно такие АТГ используются в трансляторах. Если дана АТГ и дерево вывода, то последовательность атрибутных символов действия и атрибутных входных символов, полученная по этому дереву вывода, называется атрибутной последовательностью актов. 73 Атрибутная последовательность действий данной атрибутной последовательности актов называется переводом атрибутной входной цепочки. Множество пар, состоящих из атрибутной входной цепочки и атрибутной последовательности действий, которые получаются по данной АТГ, называется атрибутным переводом, определяемым этой грамматикой. Если АТГ соответствует однозначная входная грамматика, то для каждой атрибутной входной цепочки существует не более одного дерева вывода и не более одного атрибутного перевода. Во многих применениях символы действия записываются заключением в фигурные скобки входных символов. Это означает, что каждый символ действия представляет процедуру, которая выдает символ, содержащийся внутри скобок, используя при этом атрибуты символа действия в качестве атрибутов этого выходного символа. В этом случае называют грамматику атрибутной грамматикой цепочечного перевода, или атрибутной грамматикой, транслирующей в цепочки, т.е. в данном случае мы имеем перевод входного атрибутного языка в атрибутный выходной язык. Атрибутные символы действия тогда записываются в виде {Xp,q }, в отличие от общего вида {X}p,q. 4.5.3 Перевод арифметических выражений Построим атрибутную транслирующую грамматику, которая описывает обработку арифметических выражений синтаксическим блоком (СБ). Ранее было отмечено, что СБ формирует из лексем атомы, например, если необходимо обработать выражение (a+b)*(a+c), то в СБ оно уже представляется в виде: (I1+I2)*(I1+I4), где I - класс идентификатора, определяемый в ЛБ, а Ii - лексема идентификаторов a, b, c (i - указатели на элементы ТИ). Выходом СБ будет 3 атома: СЛОЖ(1, 2, 3); СЛОЖ(1, 4, 5) ; УМНОЖ(3, 5, 6), где 3, 5 и 6 указатели на элементы ТИ, являющиеся промежуточными результатами. Указатели могут указывать на одну ТИ, но возможен вариант и различных ТИ, где первая - формируется в ЛБ для идентифицируемых операндов, а вторая - в СБ для промежуточных результатов. Свойства атомов СБ: 1. Каждой бинарной операции в входной строке соответствует атом. 2. Атомы в цепочке расположены в том же порядке, что и операции, которые должны быть выполнены во время работы программы. 3. Каждый атом имеет три указателя на элементы таблицы: 74 -левый операнд; -правый операнд; -результат операции. Атомы играют роль инструкций генератору кода относительно того, какие операции должны выполняться при работе программы. Введем символы действия {СЛОЖ.} и {УМНОЖ.}, соответствующие определенным атомам. Расположим их так в продукциях, чтобы атомы создавались сразу же, как только обработаны оба операнда операции, т.е. в крайнее правое положение. В результате получим следующую грамматику: < E > → < E > + < T > {СЛОЖ.} <E>→<T> < T > → < T > * < P > {УМНОЖ.} <T>→<P> < P > → (< E >) <P>→I Для нашего примера эта грамматика обеспечит перевод: (a + b {СЛОЖ.} ) * (a + c {СЛОЖ.} ) {УМНОЖ.} и выходная последовательность будет СЛОЖИТЬ СЛОЖИТЬ УМНОЖИТЬ, указывая порядок выдачи атомов. Зададим теперь атрибуты и правила их вычисления, чтобы эта грамматика стала атрибутной грамматикой цепочечного перевода, позволяющей вычислить для выходных символов нужные значения. Основная идея состоит в том, что каждый нетерминал снабжается единственным синтезируемым атрибутом, являющимся указателем на ТИ. Входная лексема I имеет единственный атрибут, являющийся указателем на элемент таблицы, задаваемым ЛБ. Каждый выходной символ {СЛОЖ.} или {УМНОЖ.} имеет три атрибута (наследуемых). Дерево вывода показано на рисунке 4.11 Транслирующая грамматика будет следующей: < E >к к - синтезируемый ; < T >к к - синтезируемый ; < P >к к - синтезируемый ; { СЛОЖ.y.z.p} y,z,p - наследуемые ; { УМНОЖ.y,z,p} y,z,p – наследуемые; < E > - начальный символ . 1.< E >k → < E >q + < T >r { СЛОЖ.y.z.p } (x,p) ← НОВТ, y ← q, z ← r. 2.< E >k → < T >p. x←p 3.< T >k → < T >q * < P >r { УМНОЖ.y.z.p } (x,p) ← НОВТ, y ← q, z ← r 75 4.< T >k → < P >p . x←p 5.< P >k → ( < E >p ). x←p 6.< P >k → Ip. x←p Выражение (x, p) ← НОВТ в правиле (1) означает, что значения Р и Х должны вычисляться с помощью вызова системной процедуры НОВТ, выдающей указатель на некоторый неиспользованный элемент таблицы. < E >6 <T>6 < T >3 <P>5 * < P >3 ( < E >1 ( < E >1 < T >1 < E >3 + ) ) + < T >4 < T >1 < P >4 < P >1 I4 < T>2 1 < P >2 > 3 < P >1 I1 < E >5 I1 I2 {СЛОЖ.1,2,3} {УМНОЖ.3,5,6} Рисунок 4.11 Этот указатель используется как третий атрибут выходного символа СЛОЖ. и как атрибут левого вхождения < E >. Отметим, что во всех случаях используются однозначные транслирующие грамматики, дающие однозначный перевод. Контрольные вопросы 1. Дать определение автомата с магазинной памятью. 2. Распознавание нерегулярных множеств МП-автоматами. 3. Дать определение транслирующих грамматик. 4. Синтезируемые атрибуты. 5. Наследуемые атрибуты. 6. Дать определение атрибутных транслирующих грамматик. 76 5 Нисходящие методы обработки языков 5.1 Принципы нисходящей обработки КС - грамматики и транслирующие грамматики до настоящего времени рассматривались как способ задания языков и переводов. Однако это не главное их назначение. При проектировании трансляторов основное назначение грамматик состоит в синтаксическом анализе отдельных правил в дереве вывода. Таким образом, грамматика является математической моделью синтаксического анализатора транслятора, реализация которого осуществляется на базе МП - автоматов. Методы анализа можно разделить на две категории: нисходящие и восходящие. Во - первых, правила вывода распознаются "сверху вниз", в то время как во-вторых нижние правила распознаются раньше, чем расположенные выше. Нисходящий разбор с помощью МП-автомата называют детерминированным нисходящим анализом в отличие от методов, основанных на нисходящем анализе с возвратом (недетерминированном). Допустим, задана грамматика: 1.< S > → d < S > < A > 2.< S > → b < A > c 3.< A > → d < A > 4.< A > → c Магазинными символами МПА являются нетерминалы, некоторые терминалы и маркер дна. В данном случае это будет множество {< S >, < A >, c, }. На каждом шаге процесса обработки магазин представляет некоторое утверждение о цепочке входных символов, оставшихся необработанными. Это утверждение о том, что вся входная цепочка допустима тогда и только тогда, когда цепочка оставшихся символов (включая текущий входной символ, если он не является концевым маркером) выводима из цепочки символов, находящихся в магазине, например: если в процессе обработки входной цепочки в магазине оказалось <S > < A > c < A > , то данный магазин (М) представляет утверждение, что вся входная цепочка допустима тогда и только тогда, когда цепочка входных символов (ЦВС), которую предстоит еще обработать, состоит из цепочки (Ц), порождаемой нетерминалом (НТ) < S >, за которой следует Ц, порождаемая < A >, потом терминал (Т) "с", потом еще цепочка, порождаемая < A >, и наконец - концевой маркер (КМ). Покажем, как МПА использует М при распознавании Ц -dbccdc, для чего поместим в М < S > , что рассматривается как инициализация МПА (рис. 5.1.). 77 1: < S > 2: < A > < S > 3: < A > c < A > 4: < A > c 5: < A > 6: < A > 7: 8: Допустить dbccdc ┤ bccdc ┤ ccdc ┤ cdc ┤ dc ┤ c┤ ┤ Рисунок 5.1 Из начальной конфигурации автомат должен сделать переход на основании того факта, что верхним символом М является <S>, а текущийвходной символ "d". Верхний символ М указывает на то, что оставшаяся ЦВС (если она допустима) должна начинаться с Ц, порождаемой <S>, а текущий входной символ (ВС) указывает на то, что эта Ц начинается с "d". Мы заключаем, что Ц, порождаемая начальным НТ <S>, должна начинаться с d. Для вывода Ц из <S> к нему можно применить два правила (1 и 2). Однако Ц, начинающуюся с "d", нельзя получить, применив сразу правило 2, так как правая часть правила 2 начинается с "b", а это значит, что все выводимые из правой части правила 2 (П2) Ц должны начинаться с "b". Отсюда неизбежное заключение, что требуемая Ц получается из <S>, если к нему применить правило 1, так как правая часть этого правила равна d<S><A> и, следовательно, порождает все Ц, начинающиеся с "d". Для того, чтобы зарегистрировать этот факт, МПА переходит к следующему ВС и заменяет верхний символ М <S> на символы <S> <A>, причем <S> помещается выше <A>, так как в правиле 1 он располагается перед <A>. Делая такой переход, МПА утверждает, что ЦС, следующая за "d", состоит из Ц, порождаемой <S>, за которой следует Ц, порождаемая <А>. Это отражено строкой 2 (рис. 5.1). Текущим символом становится "b", и требуемая Ц получается из <S>, если к нему применить правило 2, правая часть которого будет b<A>c. Следовательно, очередной шаг МПА будет состоять в том, что он перейдет к анализу очередного текущего символа, а верхний символ М <S> будет заменен символами <A>c (строка 3 рис. 5.1). Текущим символом становится "c" и требуемая цепочка получается из <A>, если к нему применить правило 4, так как правая часть этого правила содержит "с", т.е, порождает цепочки, начинающиеся с "с". Так как правая часть правила 4 не содержит больше ничего, кроме "с", то верхний символ М <A> выталкивается и на его место не записывается никакой символ (строка 4 рис. 5.1). 78 Текущим входным символом становится символ "с". Верхний магазинный символ "с" указывает на то, что Ц необработанных входных символов должна начинаться с "с". Автомат отмечает этот факт, выталкивая верхний символ М - "с" и сдвигаясь к следующему ВС(строка 5 рис. 5.1.). Теперь наверху М символ < A > и текущий ВС - 'd'. Единственным правилом, которое можно применить, чтобы начать вывод из < A > ЦС, начинающихся с 'd', является правило 3. МПА сдвигается на выходе и заменяет верхний символ М < A > на символ < A > (строка 6 рис. 5.1.). Текущим символом становится символ "с", и из < A > можно вывести Ц, начинающуюся с "с", если применить правило 4. МПА делает очередной переход, выталкивая < A > и сдвигаясь по входу (строка 7). При этом оказывается, что цепочка закончилась (на входе - концевой маркер) и верхний символ М - маркер дна, что указывает на окончание обработки Ц и на то, что она допущена. Порядок работы МПА совпадает с левым выводом дерева вывода, который не приводим. МПА как бы "обходит" нетерминалы дерева вывода, даже если само дерево не изображается, причем этот "обход" производится сверху вниз. Отсюда - название "нисходящий анализ". Если же относительно двух НТ нельзя сказать, который из них ниже, то в этом случае первым обрабатывается более левый символ. Если М содержит описание необработанной части входной цепочки (когда она допустима), то содержимое М называют "предсказанием" необработанной части ВЦ. Разбор такого типа называют "предсказывающим разбором". В процессе разбора верхний символ М называют "целью" и работу МПА представляют как поиск подцепочки ВС, порождаемой предсказываемой целью. Ниже представлена управляющая таблица МПА, осуществляющего обработку цепочек, порождаемых грамматикой (рис. 5.1). d b c ┤ заменить (<A><S>) заменить (с<A>) <S> сдвиг сдвиг отвергнуть отвергнуть <A> заменить (<A>) сдвиг отвергнуть вытолкнуть сдвиг отвергнуть c отвергнуть отвергнуть вытолкнуть сдвиг отвергнуть отвергнуть отвергнуть отвергнуть отвергнуть 79 Верхние символы М изображаются слева, входные символы справа, начальное содержимое магазина: V < S >. Решения "отвергнуть" соответствуют ситуациям, когда состояния МПА (верхний символ М) и входной символ Ц не совместимы, например: если верхний символ М < S > и входной символ - "с", то МПА отвергает анализируемую цепочку, так как состояние < S > не порождает цепочек, начинающихся с "с" (в грамматике для < S > нет ни одного правила, правая часть которого начиналась бы с символа "с"). 5.2 S-грамматики Рассмотрим построение нисходящих распознавателей по заданным грамматикам на базе МПА, так как они отличаются наибольшей простотой, быстродействием и не требуют больших затрат памяти. Кроме того они легко расширяются для использования синтаксически управляемых переводов (трансляций). К сожалению, не все КС-грамматики пригодны для нисходящего анализа МП-автоматом, так как для многих грамматик множество всех допустимых продолжений обработанной части ВЦ не всегда можно представить единственной цепочкой терминальных и нетерминальных символов. Рассмотрим такие классы грамматик, для которых нисходящие МП распознаватели можно построить. К ним относится класс S-грамматик. КС - грамматика называется S - грамматикой ( а также раздельной или простой) тогда и только тогда, когда выполняются условия: -правая часть каждого правила начинается терминалом; -если два правила имеют совпадающие левые части, то правые части этих правил должны начинаться различными терминальными символами. Для заданной S-грамматики МП-распознаватель с одним состоянием задается следующим образом: 1.Множество входных символов - это множество терминальных символов, расширенное концевым маркером. 2.Множество магазинных символов состоит из маркера дна, нетерминальных символов грамматики и терминалов, входящих в правые части правил, за исключением тех, что занимают крайнюю левую позицию. 3.Начальное состояние М-маркер дна и начальный нетерминал. 4.Управление работой МПА описывается управляющей таблицей, строки которой помечены магазинными символами, столбцы - входными символами. 5.Каждому правилу грамматики соответствует элемент таблицы. Если правило имеет вид < A > → bα, где b - терминал, α - цепочки из Т и НТ, то этому правилу соответствует элемент в строке < A > и столбце "b": ЗАМЕНИТЬ (αr), СДВИГ, где αr - цепочка α, записанная в обратном 80 порядке. Если правило имеет вид < A > → b, то вместо ЗАМЕНИТЬ используется операция ВЫТОЛКНУТЬ. 6.Если магазинным (верхним) символом является терминал "b", то элементом таблицы в строке "b" и столбце "b" будет ВЫТОЛКНУТЬ, СДВИГ. 7.Элементом таблицы, который находится в строке маркера дна и в столбце - концевой маркер (-|), является ДОПУСТИТЬ. 8.Элементы таблицы, не описанные ни в одном из пунктов 5, 6, 7 заполняются операцией ОТВЕРГНУТЬ. Управляющая таблица может быть представлена в упрощенной форме d b c ┤ <S> #1 #2 ОТВ ОТВ <A> #3 ОТВ #4 ОТВ с ОТВ ОТВ ВЫТОЛКН. СДВИГ ОТВ ОТВ ОТВ ОТВ ДОП где # 1, 2, 3, 4 - правила в соответствии с которыми производится ЗА МЕНА и СДВИГ. Возможна оптимизация таблицы, например: правило 3, в соответствии с которым верхний символ М - < A > заменяется на < A >, можно модифицировать и записать так: СДВИГ. В этом случае символ < A > просто сохраняется в М для следующего шага. 5.3 Нисходящая обработка для транслирующих грамматик Рассмотрим транслирующие грамматики, входными грамматиками которых являются S-грамматиками. Покажем, что цепочечные переводы для таких грамматик можно выполнить МП-автоматами, расширенными до трансляторов. Метод преобразования МП-распознавателя в транслятор заключается в следующем. Каждый переход исходного МПА изменяется так, чтобы в нем учитывалось соответствующее правило транслирующей грамматики (ТГ). Рассмотрим пример. Предположим, что в некоторой ТГ имеется правило: <A> → {v}a{w}<B>{x}c{y}<D>{z}, соответствующее следующему правилу входной грамматики <A> → a<B>c<D>. МП-распознаватель использует данное правило при переходе, когда верхним символом М является < A >, а текущим ВС - "а". Правило перехода определяется так: ЗАМЕНИТЬ (<D> c <B>), СДВИГ. 81 Наша цель - изменить этот переход так, чтобы в соответствующие моменты времени происходила выдача символов v, w, x, y, ,z. При этом изменении используется две разные стратегии, одна - для выходных символов v, w, другая - для выходных символов x, y, z. Выдачу символов v , w зададим, добавив выходную операцию ВЫДАТЬ (v w) в определение данного перехода. Этот переход подходящий момент времени для выдачи данных символов, так как символы действия {v} и {w} примыкают к терминалу "а" в правой части правила. Выдачу символов x, y, z нельзя осуществить как часть данного перехода, поскольку ТГ указывает, что входные символы v , w отделены в выходной последовательности переводом цепочки, выводимой из нетерминала <B>. Для того, чтобы обеспечить выдачу этих символов в надлежащее время, операция ЗАМЕНИТЬ модифицируется так, что она помещает в М добавочные символы вместе с символами < D >, c , < B >, которые нужны для распознавания (рис. 5.4). <B> <A> {x} c …….. {y} <D> {z} Рисунок 5.4 Новые магазинные символы - это символы действия. Когда наверху М оказывается символ действия, автомат поступает следующим образом: выдает соответствующий выходной символ, удаляет символ действия из М и удерживает входной сигнал (сдвига нет). Переход измененного таким образом МПА можно записать так: ВЫДАТЬ (v w), ЗАМЕНИТЬ ({z}<D>{y}c{x}<B>), СДВИГ. 5.4 q - грамматики Рассмотрим следующую грамматику: 1.< S > → a < A > < S > 2.< S > → b 3.< A > → c < A > < S > 4.< A > → ε Эта грамматика не является S-грамматикой, так как правая часть правила 4 не начинается с терминального символа, несмотря на это, МПА может использовать эту грамматику для управления магазином, который содержит утверждение о форме необработанной части ВЦ. 82 Покажем, как МПА, реализующий приведенную грамматику, обрабатывает цепочку a a c b b. Из начального состояния < S > при поступлении символа "а" МПА применяет к < S > правило 1. Верхним символом М становится < A >, а очередным текущим символом опять символ "а". Для < A > среди правил нет правила, начинающегося с символа "а", но можно предположить, что < A > порождает пустую цепочку, а после "пустой цепочки" окажется цепочка, начинающаяся с "а". Поэтому МПА к < A > применяет правило 4 при этом выталкивает верхний символ < A > и сохраняет текущий символ "а" на входе. Верхним символом М становится символ < S >, к которому МПА применяет опять правило 1, правая часть которого начинается символом "а". Ситуация повторяется при том отличии, что текущим символом становится символ "с". Теперь к верхнему символу М < A > можно применить правило 3, правая часть которого начинается с символа "с". Правило 4 в данном случае применять нельзя, так как за символом < A > в М всегда следует символ <S>, который не может порождать цепочки, начинающиеся с "с". Мы акцентировали внимание только на отличиях. В остальном МПА q -грамматик работает так же, как и МПА s - грамматик. Управляющая таблица, реализующая МПА для рассмотренной q – грамматики, приведена ниже. Обратим внимание, что когда входным символом является ┤, а верхним символом М - < A >, то применяется правило 4, хотя можно применить операцию ОТВЕРГНУТЬ, так как в любом случае на следующем шаге ВЦ будет отвергнута. Это обусловлено тем, что правило 4 вытолкнет символ <A>, верхним символом М станет < S >, а текущим останется ┤. Из таблицы следует, что ВЦ отвергается. a b c ┤ <S> 1 1 ОТВ ОТВ 1 ЗАМЕН.(<S><A>), СДВИГ <A> 1 ОТВ 1 ОТВ 2 ВЫТОЛКН., СДВИГ ОТВ 1 1 ОТВ 3 ЗАМЕН. (<S><A>), СДВИГ 4 ВЫТОЛКН., ДЕРЖАТЬ Для того, чтобы упростить дальнейшие рассуждения, введем понятие множества терминалов, "следующих за" данным нетерминалом. Для данной КС - грамматики мы определим СЛЕД (<X>) как множество терминальных символов, которые могут следовать непосредственно за < X > в какой-либо промежуточной цепочке. Это понятие называется множеством следующих за < Х > терминалов. Из примера следует, что за < A > могут следовать только "а" и "b". Это можно выразить так СЛЕД (< A >)={a, b}. Следовательно, если текущий входной символ "с" или ┤, то применение правила 4 не обеспечит получение допустимой цепочки. 83 Если текущий входной символ "b", а верхний символ М - < A >, то возможно применения или правила типа < A > → bε, или правила < A > → ε, если "b" принадлежит множеству терминалов, следующих за < A > (за подцепочкой, выводимой из < A >). Для того, чтобы рассматривать обе эти ситуации одновременно, введем понятие множества выбора для данного правила: - если правило грамматики имеет вид < A > → bε, то определим ВЫБОР (< A > → bε) = { b }; - если правило имеет вид < A > → ε, то определим ВЫБОР (<A> → ε ) = CЛЕД (< A >); - если р - номер правила < A > → α, то будем писать ВЫБОР (р) и называть это множеством выбора правила "р". Для рассматриваемой грамматики можно записать: ВЫБОР (1) = { a } ВЫБОР (2) = { b } ВЫБОР (3) = { c } ВЫБОР (4) = СЛЕД (< A >) = { a, b } На основании изложенного определим q - грамматику: 1.Правая часть каждого правила либо начинается терминалом, либо содержит ε. 2.Множества выбора правил с одной и той же левой частью не пересекаются. Второе условие исключает конфликтные ситуации при построении МПА. Наш пример удовлетворяет обоим условиям, так как правило имеет надлежащий вид и кроме того справедливы равенства: ВЫБОР (1) ∩ ВЫБОР (2) = { a } ∩ { b } = { }, ВЫБОР (3) ∩ ВЫБОР (4) = { c } ∩ { a, b } = { }. 5.5 LL(1) - грамматики Обобщим понятие множества выбора таким образом, чтобы его можно было применять к КС-грамматикам произвольного вида. Предположим, что КС - грамматика имеет правило <S>→<A>b<B>. Правая часть правила не начинается с терминального символа, но анализ всех других правил показывает, что из < S > могут выводиться цепочки, начинающиеся терминалами "а", "b", "c", "e". Мы можем заключить, что если верхним магазинным символом МПА является < S >, а текущим входным символом - один из перечисленных (a, b, c, e), то указанное правило может быть применено к < S >. Переход будет таким: ЗАМЕНИТЬ (< B > b < A >), ДЕРЖАТЬ. При этом верхний символ М - < S > заменяется на < A > b < B > и текущий входной символ остается прежним. 84 Введем понятие ПЕРВЫЕ(α) символы как множество терминальных символов, которые стоят в начале цепочек, выводимых из α . Рассмотрим КС-грамматику и определение множества ПЕРВ для ее правил. 1.< S > → < A > b < B > ПЕРВ (< A > b < B >) = {a, b, c, e} 2.< S > → d ПЕРВ (d) = {d} 3.< A > → < C > < A > b ПЕРВ (< C > < A > b) = {a, e} 4.< A > → < B > ПЕРВ (< B >) = {c} 5.< B > → c < S > d ПЕРВ (c < S > d) = {c} 6.< B > → ε ПЕРВ (ε) = { } 7.< C > → a ПЕРВ (a) = {a} 8.< C > → ed ПЕРВ (ed) = {e} Вычисление множеств ПЕРВ для правил 2, 5, 7 и 8 тривиально, поскольку их правые части начинаются с терминалов и множества содержат по одному элементу - первому терминальному символу правила. Множества ПЕРВ для правила 6 - пустое, так как из ε нельзя вывести никакой цепочки, кроме самой ε (пустой цепочки, не содержащей ни одного символа). Для правила 3 ПЕРВ (< C > < A > b) состоит из терминалов, начинающих правила для нетерминала < C >, а именно из "а" (правило 7) и "e" (правило 8). Для правила 4 множество ПЕРВ (< B >) состоит только из одного символа "с" (по правилу 5), так как применив правило 6, получим ε. Для правила 1 ПЕРВ (< A > b < B >) = {a, b, c, e}, так как < A > порождает цепочки, начинающиеся с < C > и < B > (правила 3, 4), которые, в свою очередь начинаются с терминалов "с", "а" и "е" и поэтому они включаются в ПЕРВ(< A > b < B >). Цепочкой, порождаемой < A > может быть и ε, так как < A > → < B > (правило 4) и < B > ε (правило 6). Поэтому терминал "b", следующий за < A > в правиле 1, также может стать первым символом в порождаемой цепочке, и он включается в множество ПЕРВ. Осталось рассмотреть применение ε - правил, которое реализуется для правила 6 и может быть реализовано для правила 4. В рассматриваемом примере ε - правило 6 должно применяться к < B >, когда текущий символ принадлежит множеству СЛЕД(< B >) = {b, d, ┤}, что следует из выводов: 1.< S > ┤ → < A > b < B > ┤ → < B > b < B > ┤ 2.<S> ┤ → <A>b<B> ┤ → <A>bc<S>d ┤ → <A>bc<A>b<B>d Первый вывод показывает, что за < B > следует "b" и ┤, а второй что за < B > следует "d". 85 Как ε – правило, так и правило 4 должно применяться в тех случаях, когда верхний символ М - < A >, а текущий входной символ принадлежит множеству СЛЕД (< A >) = { b }. Для упрощения дальнейших рассуждений о правых частях правил, приводящих к ε, введем понятие аннулирующих цепочек и правил. Цепочка α, состоящая из символов грамматики, называется аннулирующей тогда и только тогда, когда α → ε. Правило грамматики называется аннулирующим только тогда, когда его правая часть является аннулирующей. В нашем примере к нему относятся правила 4 и 6. С учетом изложенного сделаем обобщение. Для данного правила < A > → α, где α - цепочка, состоящая из терминалов и нетерминалов, определим ВЫБОР (< A > → α) = ПЕРВ ( α ), где α неаннулирующая, и ВЫБОР (< A > → α) = ПЕРВ ( α) U СЛЕД ( α), если α - аннулирующая. Отметим, что в дальнейшем в целях упрощения мы будем писать ВЫБОР(р), где р - номер правила, вместо ВЫБОР (< A > → α). В рассматриваемом примере множества выбора двух аннулирующих правил вычисляются так: ВЫБОР(4) = ПЕРВ(<B>) U СЛЕД(<A>) = {c}U{b} = {c, b}; ВЫБОР(6) = ПЕРВ(ε) U СЛЕД(<B>) = { } U {b, d, ┤} = {b, d, ┤}. Для других правил множества выбора совпадают с множествами ПЕРВ. После того, как вычислены множества выбора для всех правил, можно задать управляющую таблицу МПА. Применим изложенную методику для синтеза МП-распознавателя арифметических выражений, описываемых следующей грамматикой: 1.< E > → < T > <E-список . 2.<E-список> → + < T > <E-список>. 3.<E-список> → ε . 4.< T > → < P > <T-список>. 5.<T-список> → * < P > <T-список>. 6.<T-список> → ε. 7.< P > → (< E >). 8.< P > → I. Эта грамматика является LL(1)-грамматикой со следующими множествами выбора: ВЫБОР(1) = ПЕРВ(< T > <E-список>) = {I, (} ВЫБОР(2) = ПЕРВ(+< T > <E-список>) = {+} ВЫБОР(3) = СЛЕД(<E-список>) = {), ┤} ВЫБОР(4) = ПЕРВ(< P > <T-список>) = {I, (} ВЫБОР(5) = ПЕРВ(* < P > <T-список>) = {*} 86 ВЫБОР(6) = СЛЕД(<T-список>) = {+, ), ┤} ВЫБОР(7) = ПЕРВ((< E >)) = {(} ВЫБОР(8) = ПЕРВ(I) = {I} Ниже приведена управляющая таблица, реализующая процессор обработки арифметических выражений. Процедуры обработки, содержащиеся в ячейках, имеют вид: 1 ЗАМЕНИТЬ (<E-список> < T >), ДЕРЖАТЬ; 2 ЗАМЕНИТЬ (<E-список> < T >), СДВИГ; 3 ВЫТОЛКНУТЬ, ДЕРЖАТЬ; 4 ЗАМЕНИТЬ (<T-список> < P >), ДЕРЖАТЬ; 5 ЗАМЕНИТЬ (<T-список> < P >), СДВИГ; 6 ВЫТОЛКНУТЬ, ДЕРЖАТЬ; 7 ЗАМЕНИТЬ ( ) < E > ), СДВИГ; 8 ВЫТОЛКНУТЬ. СДВИГ. <E> <T> <P> < E-спис> < T-спис> ) I + * ( ) --| 1 ОТВ ОТВ 1 ОТВ ОТВ 4 ОТВ ОТВ 4 ОТВ ОТВ 8 ОТВ ОТВ 7 ОТВ ОТВ ОТВ 2 ОТВ ОТВ 3 3 ОТВ 6 5 ОТВ 6 ОТВ ОТВ ОТВ ОТВ 6 ВЫТОЛ. СДВ ОТВ ОТВ ОТВ ОТВ ОТВ ОТВ ДОП Изложенные принципы построения МП-распознавателей на основе LL(1)-грамматик могут быть расширены для построения МПтрансляторов введением в грамматику символов действия и организации в таблице управления процедур ВЫДАТЬ - для выдачи выходных цепочек, определяемые символами действия. Относительно этого расширения LL(1)-грамматики не отличаются от q-грамматик. Название LL(1)-грамматика объясняется тем, что МПА начинает просматривать входную цепочку слева (Left) и обнаруживает появление правила по самому левому (Leftmost) одному (1) символу - отсюда LL(1). Имеются LL(k)-грамматики, но они применяются редко, и поэтому нами не рассматриваются. 87 5.6 Нахождение множеств выбора Рассмотрим алгоритм, который проверяет, является ли данная грамматика LL(1)-грамматикой, и определяет множества выбора, обеспечивающие построение нисходящего МП-распознавателя. Полагаем, что все лишние нетерминалы в исходной грамматике предварительно удалены. Алгоритм содержит следующие шаги: 1. Нахождение аннулирущих нетерминалов и правил Данная процедура включает в себя: а) вычеркивание всех правил, правые части которых содержат хотя бы один терминальный символ ( см. таблицу); Исходная грамматика 1.<A>→a<B> 2.<B>→<D><C> 3.<B>→b 4.<C>→c<D> 5.<D>→a 6.<D>→ε Преобразование 2.<B>→<D><C> 6.<D>→ε Результат 6.<D>→ε б) в оставшейся части определение непродуктивных (мертвых) нетерминалов (подчеркнутые); в) удаление правил, содержащих мертвые нетерминалы. Оставшиеся нетерминалы и правила будут аннулирущими. 2. Построение отношений НАЧИНАЕТСЯ - ПРЯМО - С (НПС) Эти отношения определяются на множестве символов грамматики и означают: если применить к нетерминалу <A> точно одно правило и возможно, заменив в нем некоторые аннулирующие нетерминалы на ε, то можно получить цепочку, начинающуюся, например, с <B>. В этом случае пишут <A>НПС<B>. Тогда множество отношений НПС можно представить таблично. Запишем <A> НПС а, применив правило 1 и т.д. 3. Вычисление отношений НАЧИНАЕТСЯ - С (НС) В общем случае матрица отношения НС определяется рефлексивнотранзитивным замыканием матрицы НПС. Пишут <A>НС<B> тогда и только тогда, когда существует цепочка, начинающаяся с <B>, которую можно вывести из <A>. Признак рефлексивности определяется тем, что любой символ всегда начинается с самого себя (звездочки на главной диагонали). Транзитивные отношения характеризуются всеми возможными подстановками при осуществлении вывода. 88 НПС A B C D a b c A B C D a b c 1 1 1 1 1 1 A B C D * * 1 1 * * НС ПП a b c 1 * 1 * 1 1 * * * A B C D a b c 1 1 1 Для рассмотренного примера и нетерминала <B> получим: <В> НС <B>; <В> НС <C> (после применения правила 6); <В> НС <D> (непосредственно); <B> НС a (после подстановки правила 5 в правило 2); <B> НС b (непосредственно из 3); <B> НС с (после подстановки правил 6 и 4 в правило 2). Полученный результат удобно представить в виде таблицы НС (матрицы). Формальный алгоритм транзитивного замыкания включает в себя следующие шаги: 1) для каждого единичного элемента aij исходной матрицы в строку i дописать единичные элементы из строки j; 2) п.1 повторять сверху вниз и слева направо для всех элементов матрицы. В качестве примера приведем работу алгоритма для единичного элемента aBC. Для этого элемента необходимо в строку В дописать единичные элементы из строки С (один элемент - звездочка ). 4. Вычисление множества ПЕРВ для каждого нетерминала Множество ПЕРВ(<A>) - множество всех терминалов, с которых могут начинаться цепочки, выводные из <A>. Для нашего примера ПЕРВ(<A>) = {a}, ПЕРВ(<В>) = {b}, ПЕРВ(<С>) = {с}, ПЕРВ(<D>) = {a} . 5. Вычисление множества ПЕРВ для каждого правила В нашем случае ПЕРВ (1) ={a}, ПЕРВ (2) = ПЕРВ (<D>) U ПЕРВ (<C>) = {a}+{c} = {a,c}, ПЕРВ (3) ={b}, ПЕРВ (4) ={c}, ПЕРВ (5) ={a}, ПЕРВ (6) ={ }. 6. Построение отношений ПРЯМО - ПЕРЕД (ПП) Эти отношения определяются следующим образом: мы пишем А ПП В тогда и только тогда, когда существуют правила вида: <D> → <A> <B> , <D> → <A> β <B> , где <А>, <В> - НТ символы; β - аннулирующая цепочка. Для всех правил и всех символов может быть составлена таблица ПП, отражающая это состояние. Для нашего примера: < D > ПП < C >; a ПП <B>; c ПП <D>. 7. Вычисление отношений ПРЯМО - НА - КОНЦЕ (ПНК) 89 Отношение определяется следующим образом: если <А> и <В> символы грамматики, то <А> ПНК <В> тогда и только тогда, когда есть правила вида <B> →α <A>, <B> →α <A> β, где β - аннулирующая цепочка, α - произвольная цепочка. Это отношение является инверсией отношения НАЧИНАЕТСЯ ПРЯМО - С (НПС), если правую часть правила рассматривать "справа налево". ПНК A B C D a b c A B 1 C 1 D 1 a 1 b 1 c 1 НК A * 1 * * * * * B C D a b c * 1 * * 1 * НК*ПП A B C D a b c * 1 * * 1 * * * * * 1 * * Для нашего примера: <B> ПНК < A >; < C > ПНК < B >; <D> ПНК < C >; b ПНК < B>; c ПНК < C> (после применения правила 6 к <D> правила 4). 8. Вычисление отношений НА - КОНЦЕ (НК) Мы пишем <А> НК <В> тогда и только тогда, когда из <В> можно вывести цепочку, заканчивающуюся символом <А>. В нашем примере: <B> НК <А> (непосредственно из правила 1); <С> НК <В> (непосредственно из правила 2); <C> НК <A> (после применения правила 2 к <B> правила 1) и т.д. В общем случае матрица отношения НК определяется рефлексивнотранзитивным замыканием матрицы ПНК. 9. Вычисление отношений ПЕРЕД ( П ) Мы пишем <А> П <В> тогда и только тогда, когда из начального символа можно вывести цепочку, в которой за вхождением <А> сразу же следует вхождение <В>. П П+ Управляющая таблица A B C D a b c A B C D a b c * * * * * * * * * * A B C D a b c ┤ 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 A B C D Можно записать без вывода, что произведением отношений НК, ПП и НС: 90 a #1 #2 ОТВ #5 ОТВ b ОТВ #3 ОТВ ОТВ ОТВ отношение c ОТВ #2 #4 #6 ОТВ П ┤ ОТВ ОТВ ОТВ #6 ДОП является П = НК * ПП * НС . В таблице НК*ПП приведен промежуточный результат произведения НК*ПП, а в таблице П- окончательный результат. В нашем примере: < D > П < C > (непосредственно из правила 2); < D > П с (после применения правила 4 к <C> правила 2) и т.д. Формальный алгоритм произведения матриц отношений включает в себя следующие шаги: 1)для каждого единичного элемента aij матрицы - множимого в строку i результирующей матрицы дописать единичные элементы из строки j матрицы - множителя; 2)п.1 повторять сверху вниз и слева направо для всех элементов матрицы - множимого. 10. Расширение отношений ПЕРЕД, включив концевой маркер Это отношение соответствует тем символам, которые находятся НА КОНЦЕ начального нетерминала для всех выводимых из него цепочек. В нашем случае: содержимое первого столбца матрицы НК (все символы - перед ┤). 11. Вычисление множеств СЛЕД для каждого аннулирующего нетерминала Это легко выполнить, если определена матрица отношений ПЕРЕД. Тогда эти множества будут содержать все терминальные символы, для которых аннулирующие нетерминалы расположены ПЕРЕД. В нашем случае: СЛЕД (< D >) = { c, ┤ } . 12. Вычисление множества ВЫБОР Эти множества можно получить из уже вычисленных значений множеств ПЕРВ и СЛЕД. ВЫБОР (1) = ПЕРВ (1) = {a}. ВЫБОР (2) = ПЕРВ (2) = {a, c}. ВЫБОР (3) = ПЕРВ (3) = {b}. ВЫБОР (4) = ПЕРВ (4) = {c}. ВЫБОР (5) = ПЕРВ (5) = {a}. ВЫБОР (6) = СЛЕД (< D >) = {c, ┤}. Множества выбора правил, имеющих в левой части один и тот же нетерминал, отсутствуют, следовательно, данная грамматика является LL(1)-грамматикой. Такой же вывод можно было сделать, если бы правила с одинаковыми левыми частями присутствовали, но в этом случае необходимо, чтобы множества выбора для этих правил не пересекались. На базе множеств ВЫБОР правил строится управляющая таблица МПА. 91 5.7 L - атрибутные грамматики 5.7.1 Определение L-атрибутной грамматики Грамматики данного типа характеризуются следующими свойствами вычисления атрибутов: 1. Наследуемые атрибуты символа правой части продукции определяются правилом, аргументы которого могут быть наследуемыми атрибутами левой части данной продукции или любыми атрибутами символов правой части данной продукции, расположенными левее рассматриваемого символа. 2. Синтезируемые атрибуты левой части продукции определяются правилом, аргументы которого могут быть или наследуемыми атрибутами левой части, или любыми атрибутами символов правой части данной продукции. 3. Синтезируемый атрибут символа действия определяется правилом, аргументами которого могут быть наследуемые атрибуты данного символа действия. Приведенные свойства позволяют реализовать расширенный атрибутный автомат с магазинной памятью, который выполняет перевод, задаваемый L-атрибутной грамматикой , имеющей LL(1)-входную грамматику. Приведем в качестве примера L-атрибутную грамматику для арифметических выражений: 1.< E >s → < T >s1 <E-список>n,s2. ss2 ns1 2.<E-список>n,s → + < T >s1 {СЛОЖ}n1,n2,n3 <E-список>n4,s2. ss2 n1n; n2s1; (n3,n4)НОВТ; 3.<E-список>n,s → ε . sn 4.< T >s → < F >s1 <T-список>n,s2. ss2 ns1 5.<T-список>n,s → * < F >s1 {УМНОЖ}n1,n2,n3 <T-список>n4,s2. ss2 n1n; n2s1; (n3,n4)НОВТ; 6.<T-список>n,s → ε. sn 7.< F >s → (< E >s1). ss1 8.< F >s → Is1 . ss1 5.7.2 Атрибутный МП-автомат Управляющая таблица автомата строится так же, как и в п.5.5 и имеет следующий вид: 92 <E> <T> <F> < E-спис> < T-спис> ) {СЛОЖ} {УМН} I + * ( ) ┤ 1 ОТВ ОТВ 1 ОТВ ОТВ 4 ОТВ ОТВ 4 ОТВ ОТВ 8 ОТВ ОТВ 7 ОТВ ОТВ ОТВ 2 ОТВ ОТВ 3 3 6 5 ОТВ 6 6 ВЫТОЛ. ОТВ ОТВ ОТВ ОТВ СДВ ОТВ ВЫДАТЬ(СЛОЖ1,2,4),ВЫТОЛКНУТЬ, ДЕРЖАТЬ ВЫДАТЬ(УМНОЖ4,3,5),ВЫТОЛКНУТЬ, ДЕРЖАТЬ ОТВ ОТВ ОТВ ОТВ ОТВ ОТВ ДОП Отличие состоит в том, что в качестве магазинных символов добавились символы действия: {СЛОЖ} , {УМН} . При этом в магазине символы действия представляются в виде записей, содержащих +1 полей, где - число атрибутов данного символа действия. Кроме того процедуры обработки, содержащиеся в ячейках, расширились за счет операций по вычислению атрибутов: 1 ЗАМЕНИТЬ (<E-список> < T >), ДЕРЖАТЬ. -связать указатель вводимого НТ <T> с первым полем вводимого НТ <E-список>; -связать второе поле вводимого НТ <E-список> с первым полем НТ <Tсписок>, если он находится в магазине, (перенести указатель заменяемого НТ в поле вводимого НТ); 2 ЗАМЕНИТЬ (<E-список> {СЛОЖ} < T >), СДВИГ, - записать значение наследуемого атрибута заменяемого НТ <Eсписок> в первое поле символа действия {СЛОЖ}; - связать указатель вводимого НТ < T > со вторым полем символа действия {СЛОЖ}; - перенести указатель заменяемого НТ в поле вводимого НТ (второе поле <E-список>); - записать НОВТ в третье поле {СЛОЖ} и в первое поле вводимого НТ <E-список>; 3 ВЫТОЛКНУТЬ, ДЕРЖАТЬ, - записать значение атрибута выталкиваемого символа по указателю. 4 ЗАМЕНИТЬ (<T-список> < F >), ДЕРЖАТЬ; - связать указатель НТ < F > с первым полем вводимого НТ <Tсписок>; 93 - связать указатель второго поля вводимого НТ <T-список> с первым полем НТ <Е-список> (перенести указатель заменяемого НТ в поле вводимого НТ); 5 ЗАМЕНИТЬ (<T-список> {УМНОЖ} < F >), СДВИГ, - записать значение наследуемого атрибута заменяемого НТ <Tсписок> в первое поле символа действия {УМНОЖ}; - связать указатель вводимого НТ < F > со вторым полем {УМНОЖ}; - перенести указатель заменяемого НТ в поле вводимого НТ (<Tсписок>) ; 6 ВЫТОЛКНУТЬ, ДЕРЖАТЬ, - записать значение атрибута выталкиваемого НТ по указателю ; 7 ЗАМЕНИТЬ ( ) < E > ), СДВИГ , - связать указатель НТ < Е > с первым полем НТ <T-список> (перенести указатель заменяемого НТ < F > в поле вводимого НТ < Е >); 8 ВЫТОЛКНУТЬ. СДВИГ, - записать значение атрибута вводимого терминала I по указателю. Рассмотрим работу автомата на примере синтаксического анализа и перевода арифметического выражения (I1 + I2)*I3 . На рисунке 5.5 показаны состояния магазина (1-4), отображающие этапы обработки выражения и вычисления атрибутов. Состояние 1 характеризуется тем, что в магазине находится начальный символ грамматики < E > (маркер дна не показан). Указатель пока не привязан ни к какому полю, так как в магазине отсутствуют требуемые символы. В соответствии с правилом (ss2) первой продукции этот указатель должен быть связан со вторым полем НТ <Eсписок>. На входе автомата- левая скобка [ ( ]. Из управляющей таблицы (УТ) следует, что применяется операция #1, включающая: 1 ЗАМЕНИТЬ (<E-список> < T >), ДЕРЖАТЬ. - перенести указатель выводимого НТ < E > во второе поле вводимого НТ <E-список> и связать поле НТ <T> с первым полем вводимого НТ <E-список>. Автомат переходит в состояние 2. Необходимость организации связей при помощи указателей обусловлена тем, что синтаксический анализ осуществляется сверху - вниз (нисходящий метод), а вычисление синтезируемых атрибутов выполняется снизу - вверх. 94 3) #7 2) 1) #1 #4 <T> < E-сп > <F> < T-сп > < E-сп > 4) I1 #1 <E> ) < T-сп > < E-сп > <E> Рисунок 5.5 В состоянии 2 показано, что организована связь при помощи указателя и верхним символом магазина стал НТ <T>. Если на входе "держится" левая скобка, то из УТ следует, что применяется операция #4, включающая: 4 ЗАМЕНИТЬ (<T-список> < F >), ДЕРЖАТЬ -связать указатель НТ < F > с первым полем вводимого НТ <Tсписок>, -связать указатель второго поля вводимого НТ <T-список> с первым полем НТ <Е-список> (перенести указатель заменяемого НТ в поле вводимого НТ). Автомат переходит в состояние 3. В состоянии 3 верхним символом магазина становится НТ < F > и, так как на входе находится левая скобка, то УТ применит операцию #7, включающую: 7 ЗАМЕНИТЬ ( ) < E > ), СДВИГ , -связать указатель НТ < Е > с первым полем НТ <T-список> (перенести указатель заменяемого НТ < F > в поле вводимого НТ < Е >). Новое состояние показано на рисунке 5.5, 4). Характерной особенность этого состояния является то, что произошел СДВИГ и на входе автомата появился идентификатор I1. Верхний символ магазина - < Е >. Реакцией УТ будет операция #1, включающая ранее рассмотренные этапы, которые переведут автомат в состояние 5 (рис. 5.6, 5)) В этом состоянии верхний символ магазина - НТ <T> и на входе идентификатор I1, что позволит УТ сформировать операцию #4, рассмотренную ранее. В результате автомат перейдет в состояние 6. В новом состоянии наличие верхнего символа < F > и идентификатора I1 на входе вызовет (УТ) операцию #8, включающую: 95 8 ВЫТОЛКНУТЬ, СДВИГ,записать значение атрибута вводимого терминала I1 по указателю. Первый раз происходит вычисление синтезируемого атрибута и его запись в первое поле НТ (<T-список>) (рисунок 5.6, 7)). Так как верхним символом стал НТ (<T-список>) и на входе произошел СДВИГ и появился символ + , то реакцией автомата будет опереция #6, включающая: 6 ВЫТОЛКНУТЬ, ДЕРЖАТЬ, - записать значение атрибута выталкиваемого НТ по указателю . Происходит вычисление и запись наследуемого атрибута в первое поле НТ <Е-список> (правило ns1 первой продукции). Для входного символа + и верхнего НТ <Е-список> (рисунок 5.6, 8)). автомат формирует операцию #2, включающую: 2 ЗАМЕНИТЬ (<E-список> {СЛОЖ} < T >), СДВИГ, - записать значение наследуемого атрибута заменяемого НТ <Eсписок> в первое поле символа действия {СЛОЖ}, - связать указатель вводимого НТ < T > со вторым полем символа действия {СЛОЖ}, - перенести указатель заменяемого НТ в поле вводимого НТ (второе поле <E-список>), - записать НОВТ в третье поле {СЛОЖ} и в первое поле вводимого НТ 96 6. I1 #8 5. I1 #4 <F> < T-сп > 7. + #6 < T-сп > 1 < E-сп > <T> < E-сп > < E-сп > ) < T-сп > ) < T-сп > ) < T-сп > < E-сп > < E-сп > < E-сп > 8. + #2 < E-сп > 1 ) < T-сп > < E-сп > Рисунок 5.6 10. I2 #8 9. I2 #4 <F> < T-сп > 11. ) #6 < T-сп > 2 {СЛОЖ} 1 <T> {СЛОЖ} 1 {СЛОЖ} 1 4 < E-сп > 4 ) < T-сп > 4 < E-сп > 4 ) < T-сп > 4 < E-сп > 4 ) < T-сп > 12. {СЛОЖ} ) 1 BB 2 4 < E-сп > 4 ) < T-сп > < E-сп > < E-сп > < E-сп > < E-сп > Рисунок 5.7 97 <E-список> (рис. 5.7, 9)). Операция НОВТ представляет собой обращение к таблице идентификаторов для резервирования области памяти (указатель 4), используемой в последующем для записи промежуточного результата СЛОЖЕНИЯ. Состояния 9-11 (рис. 5.7) не отличаются от ранее рассмотренных случаев и поэтому не будут рассматриваться подробно. Отметим только, что при этом обрабатывается второй идентификатор I2 и вычисляются наследуемые атрибуты (2) символов <Т-список>, {СЛОЖ}(рис. 5.7, 11),12)). В состоянии 12 на входе действует правая скобка, а верхний символ магазина {СЛОЖ}. Из ТУ следует, что необходимо: ВЫДАТЬ(СЛОЖ1,2,4),ВЫТОЛКНУТЬ, ДЕРЖАТЬ. Новое состояние показано на рисунке 5.8, 13). Оно характеризуется тем, что вход не изменился, а верхним символом стал НТ < E-список >. Это вызовет операцию #3, включающую: 3 ВЫТОЛКНУТЬ, ДЕРЖАТЬ, - записать значение атрибута выталкиваемого символа по указателю. При этом значение наследуемого атрибута (4) записывается в первое поле НТ < Т-список >. В состоянии 14 происходит выталкивание правой скобки из магазина и СДВИГ, в результате чего на вход поступает символ * , а 13. ) #3 < E-сп > 4 ) < T-сп > < E-сп > 16 I3 #8 14 ) ) < T-сп > В,С 4 15 * #5 < E-сп > < T-сп > 4 < E-сп > <F> {УМН} 4 5 < T-сп > 5 < E-сп > Рисунок 5.8 верхним символом становится НТ < Т-список >. Такое состояние вызывает операцию #5, включающую: - записать значение наследуемого атрибута заменяемого НТ <Tсписок> в первое поле символа действия {УМНОЖ}, 98 - связать указатель вводимого НТ < F > со вторым полем 17. В,В ┤ {УМН} 4 3 5 < T-сп > 5 < E-сп > 18 #6 ┤ < T-сп > 5 < E-сп > 19 #3 ┤ < E-сп > 5 20 ┤ 5 Рисунок 5.9 {УМНОЖ}, - перенести указатель заменяемого НТ в поле вводимого НТ <Tсписок> . При этом вычисляются наследуемые атрибуты символов <Tсписок>, {УМНОЖ} (рис. 5.8, 16)). На рисунке 5.9 показаны заключительные этапы обработки арифметического выражения. На входе автомата находится концевой маркер (┤), характеризующий конец выражения. Поэтому в состоянии 17 происходит выталкивание и выполнение символа действия {УМНОЖ}, а в состояниях 18 и 19 выполнение операций #6 и #3, описанных ранее. При этом заканчивается вычисление и запись наследуемых атрибутов в поля символов <Т-список> и <Е-список>. Контрольные вопросы 1. Принципы нисходящей обработки. 2. Дать определение s - грамматики. 3. Нисходящая обработка для транслирующих грамматик. 4. Дать определение q - грамматики. 5. Дать определение LL(1) - грамматики. 6. Нахождение множеств ВЫБОРА. 7. Дать определение L- атрибутной грамматики. 99 6 Схемы программ 6.1 Стандартные схемы программ (ССП) 6.1.1 Формы определения ССП Схемы программ (СП)- это математические модели программ, описывающие строение программ, где индивидуальные конкретные операции и функции заменены абстрактными функциональными и предикатными символами. Рассмотрим стандартные схемы программ (ССП). Они характеризуются базисом и структурой схемы. Базис класса фиксирует символы, из которых строятся схемы (переменные, функциональные и др.), задает вид выражений и операторов схем. Полный базис В класса стандартных схем состоит из 4-х непересекающихся, счетных множеств символов и множества операторов - слов, построенных из этих символов. Множества символов: Х = {х1, х2...хn; у, у1, у2...; z, z1, z2...} - множество символов, называемых переменными; F = {f(0), f(1), f(2)...; g(0), g(1), g(2)...; h(0), h(1), h(2)...} - множество функциональных символов; верхний символ задает местность символа; нульместные символы называют константами и обозначают начальными буквами латинского алфавита a, b, c...; Р.= {р(0), р(1), р(2)...; q(0), q(1), q(2)...; } - множество предикатных символов; р(0), q(0) - логические константы; {старт, стоп, ...,:= и т. д.} - множество специальных символов. Термами (функциональными выражениями) называются слова, построенные из переменных, функциональных и специальных символов по следующим правилам: одно-символьные слова, состоящие из переменных или констант являются термами; слово t вида f(n)(t1, t2...tn), где t1, t2...tn - термы, является термом; только те слова, о которых говорится в п.п. 1,2, являются термами. Примеры термов: х, f(0), а, f(1)(х), g(2)(x, h(3)(y, a)). Тестами (логическими выражениями) называются логические константы и слова вида р(n)(t1, t2...tn). Примеры: p(0), p(0)(х), g(3)(x, y, z), p(2) (f(2(x, y)). Допускается в функциональных и логических выражениях опускать индексы местности, если это не приводит к двусмысленности или противоречию. 100 Множество операторов включает пять типов: начальный оператор - слово вида старт(х1, х2...хк), где х1, х2...хк переменные, называемые результатом этого оператора; заключительный оператор - слово вида стоп (t1, t2...tn), где t1, t2...tn - термы; вхождения переменных в термы t называются аргументами этого оператора; оператор присваивания - слово вида х:=t, где х - переменная, а t терм; вхождения переменных в термы называются аргументами этого оператора; условный оператор (тест) - логическое выражение; вхождения переменных в логическое выражение называются аргументами этого оператора; оператор петли - одно-символьное слово петля. Среди операторов присваивания выделим случаи: когда t - переменная, то оператор называется пересылкой (х:=у); когда t -константа, то оператор называется засылкой (х:=а). Подклассы используют ограниченные базисы. Так, например, подкласс У1 имеет базис: х1, х2, а, f(1), p(1), старт, стоп, (,),:=, , и множество операторов старт(х1, х2); х1:=f(x1), x2=f(x2), x1:=а, х2:=а, р(х1), р(х2), стоп(х1,х2), т. е. схемы из этого подкласса используют две переменные, константу а, один одноместный функциональный символ, один предикатный символ и операторы указанного вида. Графовая форма стандартной схемы Представим стандартную схему программ (ССП) как размеченный граф, вершинам которого приписаны операторы из некоторого базиса В. ССП в базисе В называется конечный (размеченный ориентированный) граф без свободных дуг и с вершинами следующих пяти видов: 1. Начальная вершина (ровно одна) ; она помечена начальным оператором. Из нее выходит ровно одна дуга. Нет дуг, ведущих к начальной вершине. 2. Заключительная вершина (может быть несколько). Помечена заключительным оператором. Из нее не выходит ни одной дуги. 3. Вершина-преобразователь. Помечена оператором присваивания. Из нее выходит ровно одна дуга. 4. Вершина-распознаватель. Помечена условным оператором (называемым условием данной вершины). Из нее выходит ровно две дуги, помеченные 1 (левая) и 0 (правая). 101 5. Вершина - петля. Помечена оператором петли. Из нее не выходит ни одной дуги. Конечное множество переменных схемы S составляют ее память ХS. Из определения следует, что один и тот же оператор может помечать несколько вершин схемы. Вершины именуются (метки вершины) целым неотрицательным числом (0, 1, 2...). Начальная вершина всегда помечается меткой 0. Схема S называется правильной, если на каждой дуге l заданы все переменные . В дальнейшем будем полагать, что все ССП правильные. Пример правильной ССП в графовой форме приведен на рисунке 6.1, а) (S,1). Вершины изображены прямоугольниками, а вершина распознаватель - овалом. Операторы записаны внутри вершины. Линейная форма СПП Для использования линейной формы СПП множество специальных символов расширим дополнительными символами:: , на, если, то, иначе . СПП в линейной форме представляет собой последовательность инструкций, которая строится следующим образом: - если выходная дуга начальной вершины с оператором старт (х1,...хn) ведет к вершине с меткой l, то начальной вершине соответствует инструкция: 0: старт (х1,...хn) на l; - если вершина схемы S с меткой l - преобразователь с оператором присваивания х:=t, выходная дуга которого ведет к вершине с меткой l1, то этому преобразователю соответствует инструкция: l: x: =t на l1; - если вершина с меткой l - заключительная вершина с оператором стоп (t1,...tm), то ей соответствует инструкция l: стоп (t1,...tm); - если вершина с меткой l - распознаватель с условием р(t1,...tk), причем 1-дуга ведет к вершине с меткой l1, а 0-дуга - к вершине с меткой l0, то этому распознавателю соответствует инструкция l: если р(t1,...tk) то l1 иначе l0; - если вершина с меткой l - петля, то ей соответствует инструкция l: петля; Обычно используется сокращенная запись. Полная и сокращенная линейные формы ССП (рис. 6.1,а)) приведены ниже 0: старт (х) на 1, старт (х), 102 старт(x) 1 старт(x) y:= a 1 y:=1 P(x) 2 x =0 2 0 3 y:=g(x,y) стоп (y) 5 стоп(y) 5 4 1 y:=ε 2 x=ε 0 1 1 старт(x) 3 стоп(y) y:=xy 5 4 x:=x-1 y:=CONSCAR(x,y ) 3 x:= h(x) 4 а) б) x:=CDR(x) в) Рисунок 6.1 1: у: = а на 2; 2: если р(х) то 5 иначе 3; 3: у: = g (x,y) на 4; 4: х: = h (x) на 2; 5: стоп (у). у: = а; 2: если р(х) то 5 иначе 3; 3: у: = g (x,y); х: = h (x) на 2; 5: стоп (у). 6.1.2 Интерпретация ССП ССП не является алгоритмом, поэтому позволяет исследовать структурные свойства программ, но не семантику вычислений. При построении семантической теории схем программ вводится понятие интерпретация ССП. Определим это понятие. Пусть в некотором базисе В определен класс ССП. Интерпретацией базиса В, в области интерпретации D, называется функция I, которая сопоставляет: - каждую переменную х из базиса В с некоторым элементом d=I(x) из области интерпретации D; - каждую константу а из базиса В с некоторым элементом d=I(а) из области интерпретации D; - каждый функциональный символ f(n)с определенной функцией F(n)=I(f(n)); 103 - каждую логическую константу р(0) с одним символом множества { 0,1 }; - каждый предикатный символ р(n) с определенным предикатом P(n)=I(p(n)). Пара (S,I) называется интерпретированной стандартной схемой (ИСС), или стандартной программой (СП). Определим понятие выполнения программы. Состоянием памяти программы (S,I) называют функцию W: XS D, которая каждой переменной X из памяти схемы S сопоставляет элемент W(x) из области интерпретации D. Значение терма t при интерпретации I и состоянии памяти W (обозначим tI(W)) определяется следующим образом: 1) если t = х, то tI(W) = W(x); 2) если t = 0, то tI(W) = I(0); 3) если t = f(n)(t1, t2... tn), то tI(W) = I(f(n))(t1I(W), t2I(W),... tnI(W)). Аналогично определяется значение теста при интерпретации I и состоянии памяти W (обозначим I(W)): если = р(n)(t1, t2... tn), то I(W) = I(p(n))(t1I(W), t2I(W),... tnI(W)). Конфигурацией программы называют пару U = (l,W), где l - метка вершины схемы S, а W - состояние ее памяти. Выполнение программы описывается конечной или бесконечной последовательностью конфигураций, которую называют протоколом выполнения программы (ПВП). Протокол (U0, U1,...Ui, Ui+1,...) выполнения программы (S,I) определяем следующим образом (ниже ki означает метку вершины, а Wi состояние памяти в i- конфигурации протокола, Ui = (ki,Wi)): U0 = (0 , W0); пусть Ui = (Ki, Wi) - i- конфигурация ПВП, а О - оператор схемы S в вершине с меткой ki. Если О - заключительный оператор СТОП(t1, t2... tn), то Ui - последняя конфигурация, так что протокол конечен. В этом случае считают, что программа (S,I) останавливается, а последовательность значений t1I(W), t2I(W),... tnI(W) объявляют результатом val (S,I) выполнения программы (S,I). В противном случае, т. е. когда О - не заключительный оператор, в протоколе имеется следующая, (i+1)-я конфигурация Ui+1 = (Ki+1, Wi+1), причем: а) если О - начальный оператор, а выходящая из него дуга ведет к вершине с меткой l, то Ki+1 = l и Wi+ 1= Wi; б) если О - оператор присваивания х:=t, а выходящая из него дуга ведет к вершине с меткой l, то Ki+1 = l, Wi+1 = Wi, Wi+1(х) = t1(Wi); в) если О - условный оператор и I(Wi) = , где 0,1, а выходящая из него дуга ведет к вершине с меткой l, то Ki+1 = l и Wi+1 = Wi; 104 г) если О - оператор петли, то Ki+1 = l и Wi+1 = Wi, так что протокол бесконечен. Рассмотрим две интерпретации СПП S1 (рисунок 6.1, а/). Интерпретация (S1, I1) задана следующим образом: − область интерпретации D1 - множество целых неотрицательных чисел; − I1(x) = 4; I1(y) = 0; I1(a) = 1; − I1(g) = G, где G - функция умножения чисел, т. е. G(d1,d2) = d1*d2; − I1(h) = H, где H - функция вычитания единицы, т. е. H(d) = d-1; − I1(p) = P1, где P1 - предикат "равно 0", т.е. P1(d) = 1, если d = 0. Программа (S1, I1) вычисляет 4! (рисунок 6.1,б/). Интерпретация (S1, I2) задана следующим образом: − область интерпретации D2 = V*, где V = a,b,c, V* - множество всех возможных слов в алфавите V. − I2(x) = abc; − I2(y) = , где - пустое слово; − I2(a) = ; − I2(g) = CONSTAR, где CONSTAR - функция, приписывающая первую букву первого слова к началу второго слова, т. е. CONSTAR(d,) = d; − I2(h) = CDR, где CDR - функция, стирающая первую букву; CDR(d) = ; − I2(p) = P2, где P2 - предикат "равное ", т.е. P2() = 1, если = . Программа (S1, I2) преобразует слово abc в слово cba (рисунок 6.1,в)). ПВП (S1, I1) конечен, результат - 24 (табл. 6.1). Таблица 6.1 Конфигураци U0 U1 U2 U3 U4 U5 U6 U7 U8 U9 U10 U11 U12 я Метка 0 1 2 3 4 2 3 4 2 3 4 2 3 Значени Х 4 4 4 4 3 3 3 2 2 2 1 1 1 я У 0 1 1 4 4 4 12 12 12 24 24 24 24 ПВП (S1, I2) конечен, результат - cba (табл. 6.2). Таблица 6.2 Конфигураци U0 U1 U2 U3 U4 U5 U6 U7 U8 U9 U10 U11 U12 я Метка 0 1 2 3 4 2 3 4 2 3 4 2 5 Значени X abc abc abc abc bc bc bc c c c я У a a a ba ba ba cba cba cba cba Контрольные вопросы 1. Чем определяется ССП? 105 2. Что представляют собой терм и тест ССП?. 3. Какими признаками характеризуется интерпретация ССП? 4. Пояснить сущность графовой и линейной форм представления ССП. 6.1.3 Свойства и виды стандартных схем программ Тотальность и пустота ССП S в базисе В тотальна, если для любой интерпретации I базиса В программа (S,I) останавливается. ССП S пуста, если для любой интерпретации I базиса В программа (S,I) зацикливается. Функциональная эквивалентность Стандартные схемы S1 и S2 в базисе В функционально эквивалентны (S1 S2), если либо обе зацикливаются, либо обе останавливаются с одинаковым результатом, т. е. val (S1,I) val (S2,I). Примеры тотальных, пустых и эквивалентных схем приведены на рисунке 6.2. Цепочкой стандартной схемы (ЦСС) называют: - конечный путь по вершинам схемы, ведущий от начальной вершины к заключительной; - бесконечный путь по вершинам, начинающийся начальной вершиной схемы. В случае, когда вершина-распознаватель(v), то дополнительно указывается верхний индекс (1 или 0), определяющий 1-дугу или 0-дугу, исходящую из вершины. Примеры цепочек схемы S1 (рис. 6.1,а)): (0, 1, 21, 3, 40, 7); (0, 1, 20, 5, 61, 7) и т. д. Цепочкой операторов (ЦО) называется последовательность операторов, метящих вершины некоторой цепочки схемы. Например: (старт (х), у:=f(x),р1(y), x:=f(y), р0(y), стоп(у)) и т. д. Предикатные символы ЦО обозначаются так же, как вершиныраспознаватели в ЦСС. 106 Пусть S - ССП в базисе В; I - некоторая интерпретация базиса В; (0, 1, к2, к3...) - последовательность меток инструкций S, выписанных в том порядке, в котором эти метки входят в конфигурации протокола старт(x) старт(x) 1 y:=ƒ(x) 1 y:=ƒ(x) 2 1 x:=ƒ(y) 3 1 0 0 1 1 0 4 P(y) 1 0 6 x:=ƒ(x) стоп(y) 0 3 y:=ƒ(y) P(x) 5 P(x) 6 P(y) 1 стоп(y) 8 5 x:=ƒ(x) P(y) 4 2 P(y) 8 0 стоп(y) 7 x:=ƒ(x) 7 a) б) старт(x) старт(x) 2 1 y:=ƒ(x) P(y) 1 2 P(y) 0 y:=ƒ(y) 3 стоп(y) 1 5 1 x:=ƒ(x) 1 y:=ƒ(x) 0 3 y:=ƒ(y) 4 P(y) 1 P(x) 1 0 7 x:=ƒ(x) 8 стоп(y) P(y) 4 6 x:=ƒ(x) 5 1 P(x) 0 10 стоп(y) 0 0 P(x) 7 0 9 1 8 x:=ƒ(x) г) в) Рисунок 6.2 107 x:=ƒ(x) выполнения программы (S,I). Ясно, что эта последовательность - ЦСС S. Считают, что интерпретация I подтверждает (порождает) эту цепочку. ЦСС в базисе В называют допустимой, если она подтверждается хотя бы одной интерпретацией этого базиса. Не всякая ЦСС является допустимой. В схеме S1 (рис. 6.1,а)) цепочки (0, 1, 20, 5, 61, 7), (0, 1, 21, 3, 40, 7) и все другие конечные цепочки не подтверждаются ни одной интерпретацией. Свойство допустимости цепочек играет чрезвычайно важную роль в анализе ССП. В частности оно определяет те случаи, когда стандартная схема свободна. Свободные стандартные схемы ССП свободна, если все ее цепочки допустимы. Допустимая цепочка операторов - это цепочка операторов, соответствующая допустимой цепочке схемы. В тотальной схеме все допустимые цепочки (и допустимые цепочки операторов) конечны. В пустой схеме - бесконечны. Рассмотренные свойства распространяются на все другие классы стандартных схем и образуют опорные пункты схематологии программирования. 6.1.4 Свободные интерпретации (СИ) Множество всех интерпретаций очень велико, и поэтому вводится класс свободных интерпретаций (СИ), который образует ядро класса всех интерпретаций в том смысле, что справедливость высказываний о семантических свойствах ССП достаточно продемонстрировать для программ, получаемых только с помощью СИ. Все СИ базиса В имеют одну и ту же область интерпретации, которая совпадает со множеством Т все термов базиса В. Все СИ одинаково интерпретируют переменные и функциональные символы, а именно: а) для любой переменной х из базиса В и для любой СИ Ih этого базиса Ih(x) = x; б) для любой константы а из базиса В Ih(a) = a; в) для любого функционального символа f(n) из базиса В, где n1, Ih(f(n)) = F(n): TnT, где F(n) - словарная функция такая, что F(n)(1, 2, ...n) = f(n) (1, 2, ...n), т. е. функция F(n) по термам 1, 2, ...n из Т строит новый терм, используя функциональный смысл символа f(n). Если говорить об интерпретации предикатных символов, то она полностью свободна, т.е. разные СИ различаются лишь интерпретаций предикатных символов. 108 Таким образом, после введения СИ термы используются в двух разных качествах: как функциональные выражения в схемах и как значения переменных и выражений. В дальнейшем термы-значения будем заключать в апострофы, например: значение свободно интерпретированного терма 3 = f(x,h(y)), где 1 = `f(x,a)` - терм-значение переменной х, а 2 = `g(y)` - терм-значение переменной у, равно термузначению `f(f(x,a), h(g(y)))`. Пример. Пусть Ih -СИ базиса, в котором определена схема S1 (рис. 6.1, а/ ), и в этой интерпретации предикат Р=I(р) задан так: 1, если число функциональных символов в больше двух; P()= 0, в противном случае. Тогда ПВП (S1,Ih) можно представить таблицей 6.3. Обратим внимание на следующую особенность термов. Если из терма удалить все скобки и запятые, то получим слово (назовем его бесконечным термом), по которому можно однозначно восстановить первоначальный вид терма (при условии, что отмечена или известна местность функциональных символов). Таблица 6.3 КонфиМетка Значение гурация Х У U0 0 `x` `y` U1 1 `x` `a` U2 2 `x` `a` U3 3 `x` `g(x,a)` U4 4 `h(x)` `g(x,a)` U5 2 `h(x)` `g(x,a)` U6 3 `h(x)` `g(h(x),g(x,a))` U7 4 `h(h(x))` `g(h(x),g(x,a))` U8 2 `h(h(x))` `g(h(x),g(x,a))` U9 3 `h(h(x))` `g(h(h(x)),g(h(x),g(x,a)))` U10 4 `h(h(h(x)))` `g(h(h(x)),g(h(x),g(x,a)))` U11 2 `h(h(h(x)))` `g(h(h(x)),g(h(x),g(x,a)))` U12 5 `h(h(h(x)))` `g(h(h(x)),g(h(x),g(x,a)))` Например, терм терму ghxgxy. g(2)(h(1)(x), g(2)(x,y)) соответствует бесконечному 109 Правила восстановления терма по бесскобочной записи аналогичны правилам восстановления арифметических по их прямой польской записи. Бесскобочная запись термов короче и она будет широко использоваться в дальнейшем наряду с обычной записью. 6.1.5 Согласованные свободные интерпретации (ССИ) Полагают, что интерпретация I и СИ Ih согласованы (того же базиса В), если для любого логического выражения справедливо Ih()=I(). Пусть, например, =`g(h(h(x)),g(h(x),g(x,a)))`, а интерпретация определяет: I(x)=3; I(a)=1; I(g)- функция умножения; I(h)- функция вычитания 1; I(p)- имеет такой же вид, как I1(p) (рис. 6.1, б)). Тогда получим: I()=((3-1)-1)*((3-1)*(3*1))=6. В этом случае Ih примера из п. 6.1.4 согласована с только что рассмотренной интерпретацией I, а так же с I2 (рис. 6.1, в)), но не согласована с I1 (рис. 6.1, б)). Доказывается, что для каждой интерпретации I базиса В существует согласованная с ней СИ этого базиса. Справедливо и обратное утверждение. Так, например, выше было сказано, что Ih рассмотренного примера не согласована с I1. Чтобы сделать Ih согласованной, необходимо условие Р() видоизменить: 1, если число функциональных символов в больше трех; P()= 0, в противном случае. Можно поступить и по-другому. Оставить Ih, без изменения и согласовать с ней I1. Для этого необходимо будет заменить I1(x) = 4, на I1(x) = 3. Введем важное вспомогательное понятие подстановки термов, используемое в дальнейшем. Приведем без доказательства, следующие из вышерассмотренного , важные утверждения. Если интерпретация I и свободная интерпретация Ih согласованы, то программы (S, I) и (S, Ih) либо зацикливаются, либо останавливаются и I(val(S,Ih))= val(S,I), т.е. каждой конкретной программе можно поставить во взаимнооднозначное соответствие свободно-интепретированную (стандартную) согласованную программу. Если интерпретация и свободная интерпретация согласованы, они порождают одну и ту же цепочку операторов схемы. Стандартные схемы S1 и S2 в базисе В функционально эквивалентны только тогда, когда они функционально эквивалентны на множестве всех 110 свободных интерпретаций базиса В, т.е. когда для любой свободной интерпретации Ih программы (S1,Ih) и (S2,Ih) либо обе зацикливаются, либо обе останавливаются и val(S1,Ih) = val (S2,I). Стандартная схема S в базисе В пуста (тотальна, свободна) только тогда, когда она пуста (тотальна, свободна) на множестве всех свободных интерпретаций этого базиса, т.е. если для любой свободной интерпретации Ih программа (S,Ih) зацикливается (останавливается), когда каждая цепочка схемы подтверждается хотя бы одной свободной интерпретацией. 6.1.6 Логико-термальная эквивалентность Отношение эквивалентности Е, заданное на парах стандартных схем, называют корректным, если для любой пары схем S1 и S2 из S1Е S2 следует, что S1 S2, т.е. S1и S2 функционально эквивалентны. Поиск разрешимых корректных отношений эквивалентности представляет значительный интерес с точки зрения практической оптимизации преобразования программ, поскольку в общем виде функциональная эквивалентность стандартных схем алгоритмически неразрешима. Идея построения таких (корректных и разрешимых) отношений связанна с введением понятия истории цепочки схем. В истории с той или иной степенью детальности фиксируются промежуточные результаты выполнения операторов рассматриваемой цепочки. Эквивалентными объявляются схемы, у которых совпадают множества историй всех конечных цепочек. Одним из таких отношений эквивалентности является логикотермальная эквивалентность (ЛТЭ), основанная на понятии логикотермальной истории (ЛТИ). Детерминантом (обозначение: det(S))стандартной схемы S называют множество ЛТИ всех ее цепочек, завершающихся заключительным оператором. Схемы S1 и S2 называют ЛТЭ (лт - эквивалентными) S1ЛТS2, если их детерминанты совпадают. Приведем без доказательства следующее утверждение : Логико-терминальная эквивалентность корректна, т.е. из ЛТЭ следует функциональная эквивалентность S1ЛТS2 S1 S2. Обратное утверждение не верно. Лт–эквивалентность допускает меньше сохраняющих ее преобразований, чем функциональная эквивалентность. Контрольные вопросы 1. Что характеризует свойство тотальности ССП? 2. Что характеризует свойство пустоты ССП? 111 3. Что характеризует свойство функциональной эквивалентности ССП? 4. Сущность свободных интерпретаций (СИ) базиса ССП. 5. Сущность согласованных свободных интерпретаций (ССИ) базиса ССП. 6.1.7 Моделирование стандартных схем программ Одноленточные автоматы Ленточные КА обнаруживают ряд полезных качеств, используемых в теории схем программ для установления разрешимости свойств ССП. Одноленточный конечный автомат (ОКА) над алфавитом А задается набором S = { A, Q, R, q0, #, I } и правилом функционирования, общим для всех таких автоматов, где A - алфавит; Q - конечное множество (непустое) состояний (QnA=); R - множество выделенных заключительных состояний (RQ); q0 - выделенное начальное состояние; I - программа автомата; # - «пустой» символ. Программа автомата I представляет собой множество команд вида qаq', в котором q,q'Q, aA и для любой пары (q, a) существует единственная команда, начинающаяся этими символами. Программа I содержит команды: q0aq1; q0bq3; q1aq1; q1bq2; q2aq3; q2bq2; q3aq3; q3bq3. Неформально ОКА можно представить как абстрактную машину, похожую на машину Тьюринга, но имеющую следующие особенности: 1) выделены заключительные состояния; 2) машина считывает символы с ленты, ничего не записывая на нее; 3) на каждом шаге головка автомата, считав символ с ленты и перейдя согласно программе в новое состояние, обязательно передвигается вправо на одну клетку; 4) автомат останавливается только в том случае, когда головка достигнет конца слова, т.е. символа . Предположим, что автомат допускает слово в алфавите А, если, а b b q0 q3 a a а q 1 b Рисунок 6.3 112 q 2 b начав работать с лентой, содержащей это слово, он останавливается в заключительном состоянии. Автомат S задает характеристическую функцию множества MS допускаемых им слов в алгоритме S, т.е. он распознает, принадлежит ли заданное слово множеству MS. Видно, что ОКА эквивалентен рассмотренным раннее распознающим КА. Наглядным способом задания ОКА служат графы (так же, как в КА). Пример ОКА S = ({a, b}, {q0, q1, q2, q3}, {q2}, q0, , I), допускающего слова {an bm | n = 1,2, ...; m = 1,2, ...}, задается графом (рис. 6.3). Для ОКА доказано: 1. Проблема пустоты ОКА разрешима. Автомат пуст, если MS=. Доказательство основано на проверке допустимости конечного множества всех слов, длина которых не превышает числа состояний ОКА - n. Если ни одно слово из этого множества не допускается, то ОКА «пуст». 2. Предположение о том, что минимальная длина допускаемого слова больше n отвергается на том основании, что оно может быть сведено к слову меньшей длины, путем выбрасывания участков между двумя повторяющимися в пути узлами. 3. Проблема эквивалентности ОКА разрешима. Автоматы S1 и S2 эквивалентны, если MS MS . Доказательство основано на использовании отношения эквивалентности двух состояний q и q': если состояния q и q' эквивалентны, то для всех aA состояния (q, a) и '(q', a) также эквивалентны. При этом в формируемые пары не должны входить одновременно заключительное и незаключительное состояния. Многоленточные автоматы Многоленточный (детерминированный, односторонний) конечный автомат (МКА) задается так же, как ОКА. Отличие состоит в том, что множество состояний Q разбивается на n2 подмножеств (непересекающихся) Q1, .... Qn. «Физическая» интерпретация n ленточного автомата отличается тем, что он имеет n лент и n головок, по головке на ленту. Если автомат находится в состоянии qQi, то i-я головка читает i-ю ленту так же, как это делает ОКА. При переходе МКА в состояние q'Qj (ij) i-я головка останавливается, а j-я начинает читать 1 q10 1 0 0 1 q22 q21 0 1 q23 1 0 Рисунок 6.4 113 2 свою ленту. МКА останавливается, когда головка на одной из лент прочитает символ . Рассмотрим функционирование МКА с n = 2 (рис. 6.4) на примере сравнения пар слов в алфавите {1, 0} и допуске только совпадающих слов. Здесь Q = Q1UQ2, где Q1 ={q01}; Q2 ={q12, q22,q32}; R ={ q01}; начальное состояние - q01 . МКА обрабатывает наборы слов (U1, U2), где слово U1 записано на первой ленте, а U2 - на второй. Допустимое множество наборов MS -это все возможные пары одинаковых слов, т.е. наборы, где U1 = U2. Например: набор может быть (1101, 1101) и т.п. В том случае, когда слова совпадают, МКА остановится в заключительном состоянии q01 (именно в этом состоянии поступит символ ) и набор будет допущен. Если слова не совпадут хотя бы в одном символе, МКА перейдет в состояние q32, из которого не выйдет до появления символа , который и зафиксирует отсутствие совпадения слов, т.е. не допустит искаженный набор. Доказывается разрешимость проблемы эквивалентности двухленточных автоматов. Двухголовочные автоматы Двухголовочный конечный автомат (ДКА) задается набором S = (A, Q, R, q0, , I) и имеет одну ленту и две головки, которые могут независимо перемещаться вдоль ленты в одном направлении. Множество состояний Q= Q1UQ2 разбито на два непересекающихся множества. В состояниях Q1 - активна первая головка, а в состояниях Q2 - вторая головка. Двухголовочный автомат можно рассматривать как такой двухленточный автомат, который работает с идентичными словами на 1 0 q10 0 q22 1 q11 q23 0 1 обеих лентах. Однако двухленточного. 1 q24 0 0 1 Рисунок 6.5 свойства этого 114 автомата отличаются от Так, проблема пустоты разрешима для МКА и неразрешима для ДКА. Проблема эквивалентности также неразрешима для ДКА, хотя для двухленточных - она разрешима. Приведем пример ДКА, который проверяет равенство двух последовательно записанных слов алфавита A ={1, 0}: S = (AU, Q, R, q01, , I), где Q = Q1UQ2; Q1 = {q01,q11}; Q2 ={q22, q32, q42}; R ={q11}. В алфавит введен дополнительный символ, разделяющий два слова, например 110110. На рисунке 6.5 приведен граф, определяющий ДКА . В исходном состоянии первая головка считывает первое слово. При считывании символа ДКА переходит в состояние q11 и первая головка считывает первый символ второго слова (1). При этом ДКА переходит в состояние q32 и вторая головка считывает первый символ первого слова (1), так как вторая головка не передвигалась по ленте. В результате ДКА переходит в состояние q11 и считывается второй символ второго слова (1). ДКА вновь переходит в состояние q32 и считывает второй символ первого слова (1). Происходит переход в q11 и считывается третий символ второго слова (0). При этом ДКА переходит в q22 и вторая головка считывает третий символ (0) первого слова. ДКА переходит в q11 и останавливается, так как. очередным следует символ . Поскольку остановка произошла в заключительном (q11) состоянии, то слова допускаются. При несовпадении хотя бы одной пары сравниваемых символов ДКА переходит в состояние q42 и из этого состояния не может возвратиться до конца сравниваемых слов. В результате сравниваемые слова не будут допущены. Двоичный двухголовочный автомат Проблемы пустоты, эквивалентности, свободы и тотальности ССП неразрешимы. Доказательство этого осуществляется путем сведения ССП к двухголовочным автоматам (ДКА), для которых такой вывод установлен. Для моделирования ДКА стандартной схемой, последний приводится к классу двоичных двухголовочных конечных автоматов (ДДКА), работающих с символами над алфавитом {0, 1}. Пусть ДКА S над алфавитом A={a1, a2, ...an} имеет множество состояний QS = {q1i,q2i,…,qki}, где верхний индекс (i = 1,2) определяет номер активной головки. Произведем перекодировку символов исходного алфавита по правилу: − код () = 0; − код (ai) = 11....10 ; − код (ai) = код () код (ai). 115 Так как символ кодируется нулем, то любому непустому слову на ленте автомата соответствует двоичное слово, оканчивающееся двумя нулями. Автомат останавливается, прочитав два нуля подряд (или 0, означающий пустое слово). Автомат S преобразуется в двоичный Sb так, как показано на графах рисунка 6.6. Каждый фрагмент S (рис. 6.6, а)) заменяется фрагментом (рис. 6.6, б)) для Sb. После замены добавляются фрагменты (рис. 6.6 в), г)). Множество состояний автомата Sb включает: а) все старые состояния S (QS); б) для каждого старого состояния добавляется n новых состояний (S 1, ... . q. i qj a1 ... a2 qj1 ... qj2 1 an qjn i j Sa , Sr Sj1i 1 б a Sj2 1 от Sjn ... i 1 qj2 1 r2 1 в) qj1 Sjni 1 i . r1 1 ... Sr ..r . 1 qjn 1 . .. ... от не Sa заключительных старых Рисунок 6.6 состояний г) от заключительных старых состояний ... Sn), n - число символов алфавита; в) два новых состояния r11 и r21. Заключительные состояния остаются теми же. Вершины Sa (останов допускающий) и Sr (останов отвергающий) носят на графе вспомогательный характер. На рисунке 6.7 приведены графы ДКА S (рис. 6.7, а)) и ДДКА Sb (рис. 6.7, б)), допускающие только те слова в алфавите A={a, b, c}, в которых символом «a» заканчивается каждое слово. Заключительное 116 состояние R={q01}. ДДКА имеет алфавит A={10 - символ «a», 110 - «b», 1110 - «c»}. По заданной ДДКА возможно построить ССП и наоборот, что позволяет решить задачу разрешимости (не разрешимости) свойств ССП, так как эта задача для ДДКА решена. Контрольные вопросы 1 Дать определение одноленточного конечного автомата (ОКА). 2 Дать определение многоленточного конечного автомата (МКА). 3 Дать определение двухголовочного конечного автомата (ДКА). 4 Дать определение двоичного двухголовочного конечного автомата (ДДКА). 5 Пояснить порядок моделирования ДКА стандартной схемой. 6.2 Рекурсивные схемы 6.2.1 Определение рекурсивной схемы Рекурсивная схема (РС) так же, как СПП определяется в некотором базисе. Полный базис РС, как и базис ССП, включает четыре счетных множества символов: переменные, функциональные символы, предикатные символы, специальные символы. Множества переменных и предикатных символов ничем не отличаются от ССП. Множество специальных символов - другое, а именно: {если, то, иначе, (, ), ,}. Отличие множества функциональных символов состоит в том, что оно разбито на два непересекающиеся подмножества: множество базовых функциональных символов и множество определяемых функциональных символов (обозначаются для отличия прописными буквами, например, F(1),G(2), и т.д.). 117 a q01 Sa a b q01 1 c S01 1 1 q12 S021 b с 1 a 1 r1 1 S031 1 r2 1 q12 1 S112 1 Sr S12 2 1 1 Рисунок 6.7 S13 2 б В базисе РС нет множества операторов, вместо него – множество логических выражений и множество термов. Простые термы определяются так же, как термы-выражения в СПП. Среди простых термов выделим базовые термы, которые не содержат определяемых функциональных символов, а также вызовытермы вида F(n)(τ1,τ2,…τn), где τ1,τ2,…τn -простые термы, F(n)-определяемый функциональный символ. Логическое выражение - слово вида p(n)(τ1,τ2,…τn), где p(n) предикатный символ, а τ1,τ2,…τn - простые термы. Терм - это простой терм, или условный терм, т.е. слово вида если π то τ1 иначе τ2 , где π - логическое выражение, τ1,τ2 - простые термы, называемые левой и соответственно правой альтернативой. Примеры термов: − f(x, g(x, y)); h(h(a)) - базовые термы; − f(F(x), g(x, F(y))); H(H(a)) - простые термы; − F(x); H(H(a)) - вызовы; − если p(x, y) то h(h(a)) иначе F(x) - условный терм. 118 Используется бесскобочная форма представления: если pxy то hha иначе Fx - условный терм. Расширим в базисе В множество специальных символов символом "=". Рекурсивным уравнением, или определением функции F назовем слово вида F(n)(x1,x2,…xn) = τ (x1,x2,…xn), где τ(x1,x2,…xn) - терм, содержащий переменные, называемые формальными параметрами (ФРП) функции F. Рекурсивной схемой называется пара (τ, М), где τ - терм, называемый главным термом схемы (или ее входом), а М - такое множество рекурсивных уравнений, что все определяемые функциональные символы в левых частях уравнений различны и всякий определяемый символ , встречающийся в правой части некоторого уравнения или в главном терме схемы, входит в левую часть некоторого уравнения. Другими словами, в РС имеется определение всякой вызываемой в ней функции, причем ровно одно. Примеры РС: RS1: F(x); F(x) = если p(x) то a иначе g(x, F(h(x))). RS2: A(b, c); A(x, y) = если p(x) то f(x) иначе B(x, y); B(x, y) = если p(y) то A(g(x), a) иначе C(x, y); C(x, y) = A(g(x), A(x, g(y))). RS3: F(x); F(x) = если p(x) то x иначе f(F(g(x)), F(h(x))). Пара (RS, I), где RS - PC в базисе В, а I - интерпретация этого базиса, называется рекурсивной программой. При этом заметим, что определяемые функциональные символы не интерпретируются. Протоколы выполнения программы (ПВП) (RS1, I1) и (RS1, I2), где I1 и I2 - интерпретации из 6.1 (рис. 6.1, б), в)), выглядят следующим образом: № п/п Значение терма для (RS1, I1) Значение терма для (RS1, I2) 1 2 3 4 5 6 F(4) 4*F(3) 4*(3*F(2)) 4*(3*(2*F(1))) 4*(3*(2*(1*F(0)))) 4*(3*(2*(1*1)))=24 F(a,b,c) CONSCAR(abc, F(b,c)) CONSCAR(abc, CONSCAR(bc, F(c))) CONSCAR(abc, CONSCAR(bc, CONSCAR(c, F(ε)))) CONSCAR(abc, CONSCAR(bc, CONSCAR(c, ε)))=abc 6.2.2 Трансляция схем программ Доказано: - класс ССП транслируем в класс РС; - класс РС не транслируем в класс ССП. Рассмотренные примеры подтверждают первое утверждение для одинаковых интерпретаций I базисов. В этом случае РС RS1 эквивалентна 119 ССП S1. При разных интерпретациях ССП и РС результаты будут различаться и следовательно программы (RS1, I1) и (S1, I2) будут различны. Второе утверждение подкрепляется РС RS3 . Причина не транслируемости этой схемы обусловлена тем, что при варьировании интерпретаций возникает необходимость запомнить сколь угодно большое число промежуточных значений, в то время как память любой стандартной схемы ограничена. Существуют некоторые классы РС, транслируемые в ССП. К ним относится класс линейных унарных РС, имеющих базис с единственной переменной x и одноместными функциональными и предикатными символами, например: линейная унарная РС RS4 F(x); F(x)=если p(x) то x иначе f(x, F(g(x))) транслируема в ССП. 6.2.3 Схемы с процедурами Схема с процедурами (СПР) строится в объединенном базисе классов стандартных и рекурсивных схем. Она состоит из двух частей главной схемы и множества схем процедур. Главная схема - это стандартная схема, в которой имеются операторы присваивания специального вида x: = F(n)(y1,y2,…yn), называемые операторами вызова процедур. Схема процедуры состоит из заголовка и тела процедуры, разделенных символом равенства. Заголовок имеет тот же вид, что и левая часть рекурсивных уравнений. Тело процедуры - это стандартная схема того же вида, что и главная схема. Заключительный оператор тела процедуры всегда одноместен (стоп(х)). Ниже приведен пример схемы с процедурами. Главная схема (старт(x), 1: z:=x, 2: u:=a, 3: x:=F(x, z, u), 4: u:=b, 5: z:=F(z, x, u) 6: стоп(z)) Множество схем процедур F(y, v, w)=старт, 1: если p(y) то 2 иначе 4, 2: y:=h(y), 3: v:=G(v, w) то 5 иначе 6, 4: если q(w) то 2 иначе 4, Доказано, что класс РС транслируем в класс СПР и наоборот. Контрольные вопросы 1 Дать определение рекурсивной схемы. 2 Что представляет собой простой терм РС? 3 Что представляет собой условный терм РС? 4 Чем отличается определяемый функциональный символ от просто функционального символа в РС? 120 5 Что представляет собой базовый терм РС? 6 В каких случаях возможна трансляция для РС? 6.3 Обогащенные и структурированные схемы 6.3.1 Классы обогащенных схем Выделяют следующие классы обогащенных схем: - класс счетчиковых схем; - класс магазинных схем; - класс схем с массивами. Класс счетчиковых схем образован добавлением в базис ССП счетного множества счетчиков с их интерпретированными операторами. Счетчик - интерпретированная переменная, у которой областью значений является множество целых неотрицательных чисел. Интерпретированные операторы имеют следующий вид: c := c + 1 - оператор прибавления единицы; c := c - 1 - оператор вычитания единицы; c = 0 - условный оператор проверки равенства счетчика нулю. К интерпретированным операторам может быть добавлен оператор пересылки значения счетчика с2 := с1 , который может быть получен при помощи упомянутых стандартных операторов. Класс магазинных схем образован добавлением в базис ССП счетного множества магазинов с их интерпретированными операторами. 121 Магазин - неинтерпретированная переменная сложной структуры. В процессе выполнения интерпретированной схемы состояние магазина – старт(x) старт(x) z:=x w:=x v:=x p(x) p(x) 0 1 p( w) x:=g(x) 1 c1 = 0 y:=v w:=g(w) z:=w 0 1 стоп(x) 1 стоп(x) c2:=c1 1 p(z) 0 y:=g(y) z:=g(z) a) б) старт(x) p(x) p(x) 1 M= 0 M:=x x:=g(x) 1 y:=M x:=f(y,x) c=0 1 стоп(x) 1 стоп(x) в) г) Рисунок 6.8 122 c2 = 0 0 y:=g(y) c2:=c2-1 старт(x) 0 x:=g(z) c1:=c1+1 y:=z c1:=c1-1 0 x:=f(y,x) 1 x:=f(y,x) 0 0 0 А[c]:=x c:=c+1 x:=g(x) c:=c-1 y:=А[c] x:=f(y,x) это конечный набор элементов (d1,d2,…dn) из области интерпретации, где dn - верхушка магазина. Интерпретированные операторы имеют следующий вид: М := x - запись в магазин; х := М - выборка из магазина; М = - условный оператор проверки пустоты магазина, где х - обычная переменная. Класс схем с массивами - это расширение класса счетчиковых схем за счет добавления счетного множества массивов и операторов над ними. Массив - неинтерпретированная переменная сложной структуры. При выполнении интерпретированной схемы состояние массива - бесконечная последовательность (d1,d2,…di,…) элементов из области интерпретациию. Интерпретированные операторы имеют следующий вид: А[c] := x - запись в магазин; х := А[c] - выборка из магазина, где А - массив, [c] - целое число, равное текущему значению счетчика с . На рисунке 6.8 приведены четыре схемы: стандартная (а), счетчиковая (б), магазинная (в) и схема с массивами (г). Все они эквивалентны друг другу и рекурсивной схеме: F(x), F(x)=если p(x) то x иначе f(x, F(g(x))). 6.3.2 Трансляция обогащенных схем Диаграмма на рисунке 6.9 дает полную информацию о сравнительной Y(M) Y(A) Y(R) Y(c) Y(P) Y Рисунок 6.9 мощности классов схем, где стрелки указывают на возможность трансляции одного класса схем в другой и классы имеют следующие обозначения: Y - стандартные схемы; Y(М) - магазинные схемы; 123 Y(R) - рекурсивные схемы; Y(А) - схемы с массивами; Y(с) - счетчиковые схемы; Y(P) - схемы с процедурами. Диаграмма показывает, что классы Y(М) и Y(А) являются универсальными в том смысле, что схемы всех других классов транслируемы в них. В то же время, в класс Y не транслируются схемы ни одного другого класса. Следует отметить, что класс Y(с) достигает полной мощности при количестве счетчиков не менее 2, т.е. класс Y(с) с одним счетчиком равномощен классу Y. 6.3.3 Структурированные схемы Структурированные схемы Y(S) предусматривают отказ от неупорядоченного использования переходов на метки, приводящему к усложнению структуры программ и затрудняющему ее понимание и декомпозицию. Класс Y(S) определяется в том же (полном) базисе В, который был введен для ССП Y. Различие между Y и Y(S) проявляется на уровне структур схем. Вместо специальных символов Y вводятся специальные символы: если, то, иначе, пока, цикл, конец. Вместо инструкций с метками вводятся три типа схемных операторов в базисе В: простой оператор, условный оператор и оператор цикла. Простой оператор - это начальный (заключительный) оператор и оператор присваивания. Условный оператор - это оператор вида если то 1 иначе 0 конец, где - логическое выражение, 1 ,0 - последовательности (может бать, пустые) схемных операторов, среди которых нет ни начального, ни заключительного. Операторы цикла имеют вид пока цикл конец или до цикл конец, где , имеют тот же смысл, что и выше. Ниже приведен пример эквивалентных схем Y и Y(S). Стандартная схема программ Структурированная схема программ Y Y(S) старт(х), старт(х), 1: y := f(x), y := f(x), 2: если p(y) то 7 иначе 3, если p(y) то иначе 3: y := f(y), y := f(y), 4: если p(y) то 5 иначе 7, если p(x) то 5: если p(x) то 6 иначе 7, пока p(x) 6: x := h(x) на 5, цикл x := h(x) конец 7: стоп(х, y). иначе конец конец, стоп(х, y). 124 Доказано, что класс Y мощнее класса Y(S), т.е. схемы Y(S) транслируемы в Y, но не наоборот. Усилить класс Y(S) можно за счет усложнения логических выражений в условных операторах и операторах цикла Y(SL) , введением символов логических операций NOT, OR, AND и атомарных формул, которыми являются логические выражения в старом смысле, например not p(x) and q(y,x). В этом случае схемы класса Y транслируются в схемы класса Y(SL). Контрольные вопросы 1. Дать определение счетчиковой схемы. 2. Дать определение магазинной схемы. 3. Что представляет собой схема с массивами? 4. Дать определение структурированной схемы. 5. Что представляет собой структурированная схема. 6. В каких случаях возможна трансляция схем? 125 Заключение Изучение дисциплины “ Теория автоматов и формальных языков ” позволит студентам направлений 230100.62 – Информатика и вычислительная техника, 231000.62 – Программная инженерия более глубоко изучить принципы построения вычислительных систем и использовать эти знания при проектировании экономических информационных систем различного назначения. Знание автоматных моделей, а именно теории конечных автоматов будет полезно при решении задач информационной безопасности в экономических информационных системах, а знание теории формальных грамматик необходимо при разработке интеллектуальных информационных систем. В учебном пособии рассмотрены основные теоретические и прикладные вопросы, теория языков программирования и методов трансляции, включающие теорию конечных автоматов, теорию формальных языков и грамматик, теорию построения трансляторов языков программирования высокого уровня и теорию схем программ. 126 Библиографический список 1. Кузнецов О.П., Адельсон-Вельский Г.М. Дискретная математика для инженера. - 2 изд. - М.: Энергоиздат, 1988. - 480 с. 2. Шоломов Л.А. Основы теории дискретных логических и вычислительных устройств. - М: Наука, 1989. - 400 с. 3. Льюис Ф., Розенкранц Д., Стирнз Р. Теоретические основы проектирования компиляторов. - М.: Мир, 1979. - 564 с. 4. Гавриков М.М. Теоретические основы разработки и реализации языков программирования: учеб. пособие/ М.М. Гавриков, А.Н. Иванченко, Д.В. Гринченков; под ред. А.Н. Иванченко. – М. КНОРУС, 2010. - 184 с. 5. Теория языков программирования и методы трансляции: метод. указания к курсовому проекту для студентов специальности 220400/ Кубан. гос. технол. ун-т., Сост. В.И. Ключко, А.В. Власенко; Краснодар, 2004. - 24 с. 6. Власенко А.В., Ключко В.И. Теория языков программирования и методы трансляции. Учебное пособие для студентов специальности 220400/ Кубан. гос. технол. ун-т., Краснодар, 2004. -120 с. 7. Ахо А., Лам М., Сети Р., Ульман Дж. Компиляторы: принципы, технологии, инструменты: пер. с англ., 2-е изд. М.: Вильямс, 2008. 8. Власенко А.В., Ключко В.И. Теория проектирования трансляторов. Учебное пособие для студентов специальности 351400/ Кубан. гос. технол. ун-т., Краснодар, 2004. - 98 с. 127 Приложение А (обязательное) Синтез конечного автомата 1. Запуск Visual Studio.Net. Открывается главное окно и в меню Файл выбрать - Создать/Новый проект. Открывается окно создания нового проекта (рисунок А.1), где среди типов проектов выбрать – Visual C# -Windows и в шаблонах Visual Studio выбрать Приложение Windovs Forms. Рисунок А.1 – Окно создания нового проекта Далее ввести имя проекта КА, определить его расположение D:\TIS\Проекты_КА2 и нажать кнопку OK. 128 2. Конструирование формы Открывается форма (рисунке А. 2), где поместить необходимые элементы: три метки Label Рисунок А.2 – Окно Visual Studio с открытым проектом для надписей, три текстовых поля TextBox для ввода и вывода необходимых значений, три кнопки Button для создания КА, его работы и завершения работы программы и таблица DataGridView. Для чего выполнить следующее: a/ “перетащить” мышкой с панели элементов Toolbox текстовое поле TextBox1 и метку Label1 для ввода значения (a) входных символов конечного автомата КА и надписи Aлфавит А; b/ аналогичную операцию проделать для текстового поля TextBox2 и метки Label2 для вывода выходных символов (v) КА и надписи Aлфавит V; c/ текстовое поле TextBox3 и метку Label3 поместить для задания числа состояний КА и надписи Множество Q; 129 d/ “перетащить” мышкой с панели элементов кнопку Button1 и в открытом окне Свойства (Properties) в свойстве Text ввести надпись Синтез для создания КА; e/ аналогичную операцию проделать для кнопку Button2 и в открытом окне Свойства (Properties) в свойстве Text ввести надпись Пуск для начала работы КА; f/ аналогичную операцию проделать для кнопку Button3 и в открытом окне Свойства (Properties) в свойстве Text ввести надпись Стоп для завершения работы КА ; g / “перетащить” мышкой с панели элементов таблицу DataGridView и в открытом окне Свойства (Properties) в свойстве Columns сформировать 4 столбца и ввести их обозначения q_a1, v_a1 и q_a2, v_a2. 3. Создание обработчиков событий Двойное нажатие кнопки Синтез формирует первый обработчик событий private void button1_Click(object sender, EventArgs e) { } где между фигурными скобками будет создана программа, синтезирующая КА, а двойное нажатие кнопки Пуск формирует второй обработчик событий private void button2_Click(object sender, EventArgs e) { } где между фигурными скобками будет создана программа, определяющая работу КА, а двойное нажатие кнопки Стоп формирует третий обработчик событий private void button3_Click(object sender, EventArgs e) { } где между фигурными скобками ввести команду Close() для завершения работы программы. 130 4. Разработка программы a) Синтез КА Программа создается в первом обработчике событий кнопки Синтез: private void button1_Click(object sender, EventArgs e) { dGV.Rows.Clear(); //обновление таблицы int q = int.Parse(textBox3.Text); //инициализация KA.Q = 0; //инициализация Random x = new Random(); //задание случайной переменной х for (int i = 0; i < q; i++) //задание цикла { dGV.Rows.Add(x.Next(q), x.Next(2), x.Next(q), x.Next(2)); dGV.Rows[i].HeaderCell.Value = "q" + i.ToString(); } } На рисунке А.3 показано задание 4-х состояний КА Q={q0, q1, q2, q3}, вводимых в заголовки каждого ряда КА. В первом ряду КА, обозначенном - q0, первая ячейка x.Next(q)=0, что соответствует состоянию q0, вторая ячейка x.Next(2)=1, что соответствует выходному сигналу v=1, третья ячейка x.Next(q)=3, что соответствует состоянию q3, а четвертая ячейка x.Next(2)=1, что соответствует выходному сигналу v=1, при этом x.Next(2) означает, что случайный выбор осуществляется с равной вероятностью из 2-х значений – 0 и 1, а x.Next(q) означает, что случайный выбор осуществляется с равной вероятностью из 4-х состояний – q0, q1, q2, q3. Напоминаем, что начальное состояние КА – q0 (KA.Q = 0). Рисунок А.3 – Синтезированный КА б) Работа КА Программа создается во втором обработчике событий кнопки Пуск: 131 private void button2_Click(object sender, EventArgs e) { int a = int.Parse(textBox1.Text);//инициализация if (a == 0) { KA.V = (int)dGV[1, KA.Q].Value; KA.Q = (int)dGV[0, KA.Q].Value; } else { KA.V = (int)dGV[3, KA.Q].Value; KA.Q = (int)dGV[2, KA.Q].Value; } textBox2.Text = KA.V.ToString(); textBox3.Text = KA.Q.ToString(); } На рисунке А.4 показано, что при подаче на вход а=1, КА из состояния q0 переходит в q3, Рисунок А.4 – Работа КА а на выходе формируется выходной сигнал v=1. в) Завершение работы КА Нажатие кнопки Стоп включает третий обработчик событий private void button3_Click(object sender, EventArgs e) { Close(); 132 } где выполняется команда Close(), что прекращает работу программы. 5. Работа программы a) запустить программу с отладкой, нажатием кнопки F5; b) ввести значение числа состояний КА в текстовое поле TextBox3; c) нажать кнопку Синтез, что создает КА; d) ввод входных сигналов а и нажатие кнопки Пуск обеспечивает работу КА; e) нажать кнопку Стоп для прекращения работы КА. 133 Приложение Б (обязательное) Программа работы КА using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Ka1 { public class KA { public static int Q { get; set; } public static int V { get; set; } } } using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; namespace Ka1 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { dGV.Rows.Clear(); int q = int.Parse(textBox3.Text); KA.Q = 0; Random x = new Random(); 134 for (int i = 0; i < q; i++) { dGV.Rows.Add(x.Next(q), x.Next(2), x.Next(q), x.Next(2)); dGV.Rows[i].HeaderCell.Value = "q" + i.ToString(); } } private void button2_Click(object sender, EventArgs e) { int a = int.Parse(textBox1.Text); if (a == 0) { KA.V = (int)dGV[1, KA.Q].Value; KA.Q = (int)dGV[0, KA.Q].Value; } else { KA.V = (int)dGV[3, KA.Q].Value; KA.Q = (int)dGV[2, KA.Q].Value; } textBox2.Text = KA.V.ToString(); textBox3.Text = KA.Q.ToString(); } private void button3_Click(object sender, EventArgs e) { Close(); } } } 135 Приложение В (обязательное) Автоматные отображения using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Ka1 { public class KA { public static int A { get; set; } public static int Q { get; set; } public static int V { get; set; } public static Random X = new Random(); } } using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; namespace Ka1 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { dGV.Rows.Clear(); 136 KA.A = int.Parse(textBox1.Text); int q = int.Parse(textBox3.Text); KA.Q = 0; Random x = new Random(); for (int i = 0; i < q; i++) { dGV.Rows.Add(KA.X.Next(q), KA.X.Next(2), KA.X.Next(q), KA.X.Next(2)); dGV.Rows[i].HeaderCell.Value = "q" + i.ToString(); } } private void button2_Click(object sender, EventArgs e) { textBox1.Text = String.Empty; textBox2.Text = String.Empty; textBox3.Text = String.Empty; for (int i = 1; i <= KA.A; i++) { int b = KA.X.Next(2); if (b == 0) { KA.V = (int)dGV[1, KA.Q].Value; KA.Q = (int)dGV[0, KA.Q].Value; } else { KA.V = (int)dGV[3, KA.Q].Value; KA.Q = (int)dGV[2, KA.Q].Value; } textBox1.Text = textBox1.Text + b.ToString(); textBox2.Text = textBox2.Text + KA.V.ToString(); textBox3.Text = textBox3.Text + KA.Q.ToString(); } } private void button3_Click(object sender, EventArgs e) { Close(); } } } 137 Рисунок В.1 138 Приложение Г (обязательное) Транзитивное замыкание using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; namespace Am1 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { int v = int.Parse(textBox1.Text); int[,] y = new int[9,9]; Random x = new Random(); dGV1.Rows.Clear(); dGV1.Rows.Add(v-1); for (int i = 0; i < v; i++) { for (int j = 0; j < v; j++) { y[i,j] = x.Next(2); dGV1.Rows[i].Cells[j].Value = y[i,j]; } } for (int j = 0; j < v; j++) { for (int i = 0; i < v; i++) { if (y[i, j] == 1) 139 for (int l = 0; l < v; l++) { if (y[j, l] == 1 & y[i, l]==0) y[i, l] = 2; dGV1.Rows[i].Cells[l].Value = y[i, l]; if (y[i, l] == 2) dGV1.Rows[i].Cells[l].Style.ForeColor = Color.Red; } } } } private void button2_Click(object sender, EventArgs e) { Close(); } } } Рисунок Г.1 140 Приложение Д (обязательное) Произведение матриц отношений using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Am1 { class Rel1 { public static int[,] Y1 = new int[9, 9]; public static int[,] Y2 = new int[9, 9]; public static int V; public static Random X = new Random(); } } using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; namespace Am1 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } public void button1_Click(object sender, EventArgs e) { Rel1.V = int.Parse(textBox1.Text); 141 dGV1.Rows.Clear(); dGV1.Rows.Add(Rel1.V - 1); dGV2.Rows.Clear(); dGV2.Rows.Add(Rel1.V - 1); dGV3.Rows.Clear(); dGV3.Rows.Add(Rel1.V - 1); for (int i = 0; i < Rel1.V; i++) { for (int j = 0; j < Rel1.V; j++) { Rel1.Y1[i, j] = Rel1.X.Next(2); dGV1.Rows[i].Cells[j].Value = Rel1.Y1[i, j]; Rel1.Y2[i, j] = Rel1.X.Next(2); dGV2.Rows[i].Cells[j].Value = Rel1.Y2[i, j]; dGV3.Rows[i].Cells[j].Value = 0; } } } private void button2_Click(object sender, EventArgs e) { Close(); } public void button3_Click(object sender, EventArgs e) { for (int j = 0; j < Rel1.V; j++) { for (int i = 0; i < Rel1.V; i++) { if (Rel1.Y1[i, j] == 1) for (int l = 0; l < Rel1.V; l++) { if (Rel1.Y2[j, l] == 1) { Rel1.Y2[i, l] = 1; dGV3.Rows[i].Cells[l].Value = Rel1.Y2[i, l]; dGV3.Rows[i].Cells[l].Style.ForeColor = Color.Red; } } } } 142 } } } Рисунок Д.1 143 Приложение Е (обязательное) Синтаксический анализ using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace R1 { public class Ren { public static string A; public static string E = "LT"; public static string L = "LT"; public static string T = "CF"; public static string C = "CF"; public static string F = ")E"; public static string X = "K"; public static int I; public static int D; public static Stack<object> S = new Stack<object>(); public static Stack<object> S1 = new Stack<object>(); } } using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; namespace R1 { public partial class Form1 : Form { 144 public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { Ren.S.Push(Ren.X); textBox4.Text = textBox4.Text + Ren.S.Peek().ToString(); dGV1.Rows.Add(Ren.X); Ren.S.Push("E"); textBox4.Text = textBox4.Text + Ren.S.Peek().ToString(); dGV1.Rows.Add("E"); char[] c = Ren.E.ToCharArray(); textBox1.Text = Ren.S.Count().ToString(); } private void button2_Click(object sender, EventArgs e) { Close(); } private void button3_Click(object sender, EventArgs e) { Ren.A = textBox2.Text; label1.Text = Ren.A.Length.ToString(); char[] c3 = Ren.A.ToCharArray(); textBox3.Text =c3[Ren.I].ToString(); switch (c3[Ren.I]) { case '(': { if (Ren.S.Peek().ToString() == "E") { Ren.S.Pop(); char[] c1 = Ren.E.ToCharArray(); foreach (char ch in c1) { Ren.S.Push(ch); } textBox1.Text = Ren.S.Count().ToString(); Ren.D = 0; } else 145 if (Ren.S.Peek().ToString() == "T") { Ren.S.Pop(); char[] c1 = Ren.T.ToCharArray(); foreach (char ch in c1) { Ren.S.Push(ch); } textBox1.Text = Ren.S.Count().ToString(); Ren.D = 0; } else if (Ren.S.Peek().ToString() == "F") { Ren.S.Pop(); char[] c1 = Ren.F.ToCharArray(); foreach (char ch in c1) { Ren.S.Push(ch); } textBox1.Text = Ren.S.Count().ToString(); Ren.D = 1; } else { textBox2.Text = "ОШИБКА"; return; } } break; case 'i': { if (Ren.S.Peek().ToString() == "E") { Ren.S.Pop(); char[] c1 = Ren.E.ToCharArray(); foreach (char ch in c1) { Ren.S.Push(ch); } textBox1.Text = Ren.S.Count().ToString(); Ren.D = 0; } else if (Ren.S.Peek().ToString() == "T") 146 { Ren.S.Pop(); char[] c1 = Ren.T.ToCharArray(); foreach (char ch in c1) { Ren.S.Push(ch); } textBox1.Text = Ren.S.Count().ToString(); Ren.D = 0; } else if (Ren.S.Peek().ToString() == "F") { Ren.S.Pop(); textBox1.Text = Ren.S.Count().ToString(); Ren.D = 1; } else { textBox2.Text = "ОШИБКА"; return; } } break; case '+': { if (Ren.S.Peek().ToString() == "C") { Ren.S.Pop(); textBox1.Text = Ren.S.Count().ToString(); Ren.D = 0; } else if (Ren.S.Peek().ToString() == "L") { Ren.S.Pop(); char[] c1 = Ren.L.ToCharArray(); foreach (char ch in c1) { Ren.S.Push(ch); } textBox1.Text = Ren.S.Count().ToString(); Ren.D = 1; } else { textBox2.Text = "ОШИБКА"; return; } } 147 break; case '*': { if (Ren.S.Peek().ToString() == "C") { { Ren.S.Pop(); char[] c1 = Ren.C.ToCharArray(); foreach (char ch in c1) { Ren.S.Push(ch); } textBox1.Text = Ren.S.Count().ToString(); Ren.D = 1; } } else { textBox2.Text = "ОШИБКА"; return; } } break; case ')': { if (Ren.S.Peek().ToString() == "C") { Ren.S.Pop(); textBox1.Text = Ren.S.Count().ToString(); Ren.D = 1; } if (Ren.S.Peek().ToString() == "L") { Ren.S.Pop(); textBox1.Text = Ren.S.Count().ToString(); Ren.D = 1; } if (Ren.S.Peek().ToString() == ")") { Ren.S.Pop(); textBox1.Text = Ren.S.Count().ToString(); Ren.D = 1; } else { textBox2.Text = "ОШИБКА"; return; } } break; case ';': { if (Ren.S.Peek().ToString() == "C") 148 { Ren.S.Pop();textBox1.Text = Ren.S.Count().ToString(); Ren.D = 1; } if (Ren.S.Peek().ToString() == "L") { Ren.S.Pop(); textBox1.Text = Ren.S.Count().ToString(); Ren.D = 1; } if (Ren.S.Peek().ToString() == "K") { textBox1.Text = Ren.S.Count().ToString(); textBox2.Text = "Правильно"; } else { textBox2.Text = "ОШИБКА"; return; } } break; } { textBox4.Text = ""; //перезагрузка стека с целью dGV1.Rows.Clear(); //отображения его состояния int k = Ren.S.Count(); for (int i = 1; i <= k; i++) { Ren.S1.Push(Ren.S.Peek()); textBox4.Text = textBox4.Text + Ren.S.Peek().ToString(); dGV1.Rows.Add(Ren.S.Peek()); Ren.S.Pop(); } for (int i = 1; i <= k; i++) { Ren.S.Push(Ren.S1.Peek()); dGV1.Rows[i - 1].HeaderCell.Value = (k - i).ToString(); Ren.S1.Pop(); } } Ren.I = Ren.I + Ren.D; } 149 } } Рисунок Е.1 150 Ключко Владимир Игнатьевич Власенко Александра Владимировна Кушнир Надежда Владимировна ТЕОРИЯ АВТОМАТОВ И ФОРМАЛЬНЫХ ЯЗЫКОВ Учебное пособие Технический редактор Н.А. Колычева Компьютерная вёрстка Н.В. Кушнир _______________________________________________________ Подписано в печать Формат 60х84/16 Офсетная бумага Офсетная печать Печ. Л 9,75 Изд. № 151 Усл. печ. л. 9,1 Тираж 75 экз. Уч.-изд. л. 6,9 Заказ № Цена руб. _________________________________________________________ Кубанский государственный технологический университет 350072, г. Краснодар, ул. Московская, 2, кор. А Типография ФГБОУ ВПО «КубГТУ»: 350058, г. Краснодар, ул. Старокубанская, 18/4 151