- •2.2. Управляющие последовательности
- •12.3.3. Отсутствие элементов конструкций.
- •2.4. Терминальные символы
- •12.4.1. Символ из множества.
- •12.4.2. Символ, не принадлежащий множеству
- •12.4.3. Строка символов из множества.
- •12.4.4. Строка из одинаковых символов.
- •12.4.5. Операция конкатенации строк
- •2.5. Круглые скобки
- •2.6. Семантика языка
- •Int I, number;
- •Int number_string - номер последней обрабатываемой строки,
- •3. Пример программы разбора
- •14.2. Преобразование грамматики
- •14.3. Примеры преобразования грамматик на метаязыке
3. Пример программы разбора
Применение метаязыка и генератора языковых процессоров проиллюст-
рируем на примере разработки программы, моделирующей работу калькуля-
тора с памятью. Эта программа представляет собой простой языковый про-
цессор, осуществляющий синтаксический анализ и расчет обычных арифме-
тических выражений.
Программу будем описывать на метаязыке по группам продукций с по-
яснением каждой группы.
<FULL_EXPR> /* начальный символ грамматики*/
::= *
#{ i = 1;}#
<EXPR>
#{ MEMORY = Expression[i];
cout << Expression[i];
}#
||= <FULL_EXPR> ','
#{i = 1;}#
<EXPR>
#{ MEMORY = Expression[i];
cout << Expression[i];
}#
Исходное предложение представляет собой выражения, перечисленные
через запятую. Для промежуточных вычислений используется массив Exp-
ression с индексом i. Начальное значение индекса - 1. Для ввода на-
чального значения в самом начале вычислений нам ничто не мешает ввести
в грамматику пустой символ *. Результат вычислений в конечном итоге
находится в первом элементе массива Expression[1]. По окончании вычис-
ления одного выражения результат заносится в память MEMORY.
<EXPR> /* Расчет выражения */
- 26 -
::= <SU>
#{ Expression[i] = Summand[i];}#
||= <EXPR> ( '+' <SU>
#{ Expression[i] += Summand[i];}#
| '-' <SU>
#{ Expression[i] -= Summand[i];}#
)
Выражение можно представить в виде одного слагаемого или набора
слагаемых, соединенных знаком плюс или минус. По окончании вычислений
выражения результат находится в элементе стека Expression[i], а ре-
зультат вычислений при разборе слагаемого в Summand[i].
<SU> /* Расчет значения слагаемого */
::= <MU>
#{ Summand[i] = Multiplier;}#
||= <SU> ( '*' <MU>
#{ Summand[i] *= Multiplier;}#
| '/' <MU>
#{ Summand[i] /= Multiplier;}#
)
Как видно из грамматики, слагаемое представляется одним множите-
лем или несколькими, соединенными знаками умножения и деления. Резуль-
тат вычисления слагаемого, как было отмечено и раньше, заносится в
Summand[i]. Значение множителя находится в переменной Multiplier.
.
- 27 -
<MU> /* Разбор и вычисление множителя */
::= <DEC>
#{ Multiplier = Decimal; }#
||= 'M'
#{ Multiplier = MEMORY;}#
||= '('
#{ i++;}#
<EXPR>
')'
#{ Multiplier = Expression[i];
i--;
}#
В качестве множителя может выступать десятичное число, значение
предыдущего выражения, находящееся в памяти MEMORY, и выражение в
скобках. Результатом разбора множителя является значение, записываемое
в переменную Multiplier.
Десятичное число вычисляется без учета десятичной точки при раз-
боре группы продукций под именем DEC и записывается в переменную Deci-
mal. В данном фрагменте число приводится в соответствие с наличием
дробной части.
При вычислении выражения в скобках увеличивается индекс массива
после появления открывающей скобки, поскольку начинается вычисление
нового выражения (которое в скобках), но в дальнейшем может понадо-
биться и результат предыдущих вычислений. Значение выражения в скобках
по окончании его расчета является множителем и поэтому заносится в пе-
ременную Multiplier. Далее уменьшается индекс массива и открывается
доступ к вычисленному ранее текущему значению выражения.
<DEC> /*программа разбора десятичного числа*/
::= {09}
#{ strcpy(str,token);}#
- 28 -
( .{09}
#{ strcat(str,token);}#
| *
)
#{ Decimal = atof(str);}#
Описания переменных помещаются в отдельном модуле. Приведенная
программа с помощью генератора языковых процессоров переводится на
язык СИ и сохраняется в виде отдельного модуля. Сгенерированные модули
и модуль описаний обрабатываются транслятором. На этапе компоновки
подключается стандартный объектный модуль и на выходе получается гото-
вая программа расчета выражений. Вся последовательность действий по
генерации производится автоматически до получения загрузочного модуля
транслятора. Генератор запрашивает лишь имя начального символа грамма-
тики и имя модуля описаний переменных. Примером выражения может слу-
жить следующая строка
3.23*5.62*(23.5+56.59-156.456)-1568.2+2.2458*(26.54+0.0015)+
12.5*0.25-1
.
- 29 -
4. ОДНОЗНАЧНОСТЬ ГРАММАТИКИ
14.1. Вывод исходного предложения
Определенная метаязыком грамматика рекурсивным образом определяет
порождаемый язык, который содержит множество выводимых из начального
нетерминального символа строк. Определим формально выводимую строку.
Строка <x><w><y> называется выводимой строкой грамматики , если
<v> ::= <w> ( или <v> ||= <W>) продукция описания на метаязыке и
<x><v><y> строка терминальных и нетерминальных символов длиной > 1,
принадлежащая языку, порождаемому грамматикой. Выводимая строка полу-
чается путем замены строки <v> на строку <w> в строке <x><v><y>. Такое
преобразование строки записывают в виде
<x><v><y> => <x><w><y>
Отношение =*> задается как <x1> =*> <xN>, если существует после-
довательность <x1>,<x2>,..., <xN>, такая что <x1> => <x2> => <x3>
=>...=> <xN>. В этом случае говорят, что строка <xN> выводится из
строки <x1> за один и более шагов.
Если строка <x> является выводимой из начального символа строкой
(<S> => <x>), то ее называют сентенциальной формой. Не содержащая не-
терминальных символов сентенциальная форма называется предложением
(или сентенцией). Множество всех предложений грамматики называют язы-
ком, порождаемым этой грамматикой.
Рассмотрим примеры предложений нескольких языков. Для приведенной
выше грамматики (раздел 1.), с начальным символом <целое> в язык вклю-
чены следующие предложения, представляющие собой десятичные числа.
2 вывод <целое> => <цифра> => 2
23 вывод <целое> => <целое><цифра> ( правило
<целое> ||= <целое><цифра>)
=> <цифра><цифра> (правило <целое> ::= <цифра>)
- 30 -
=> 2<цифра> (правило <цифра> ||= 2 )
=> 23 (правило <цифра> ||= 3 )
Если грамматику задать в виде
<целое> ::= string{09}
то строки - целые числа выводятся за один шаг.
3454 вывод <целое> => 3454
С помощью метаязыка мы определили язык. Однако нам необходимо от-
ветить на вопрос принадлежит исходное предложение этому языку или нет.
Используемый генератором метод разбора будет правильно работать, если
для каждого исходного предложения существует только один вывод. Не на-
рушая общности упорядочим процесс вывода предложения.
При разборе будем использовать только левосторонний вывод. Это
такой вывод, когда на каждом шаге вывода заменяется самый левый из не-
терминальных символов. Причем варианты для замены будем выбирать в по-
рядке их описания на метаязыке слева-направо сверху-вниз. Придержива-
ясь указанного порядка выполняется первая подходящая замена левого не-
терминального символа, которая ведет к выводу данного исходного пред-
ложения.