
- •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. Примеры преобразования грамматик на метаязыке
14.2. Преобразование грамматики
Для обеспечения однозначного вывода необходимо для каждого нетер-
минального символа преобразовать все его продукции таким образом, что-
бы первые символы каждой альтернативы были уникальными. В ходе преоб-
разования допускается вместо нетерминальных символов подставлять их
представления, выносить общую часть за круглые скобки или вводить но-
вые нетерминальные символы. Рассмотрим грамматику одного из приведен-
ных выше примеров.
<E> ::= <E> + <T>
||= <E> - <T>
||= <T>
- 31 -
<T> ::= string{09}
Первые две альтернативы для нетерминала <E> начинаются с одного и
того же нетерминального символа <E>. Поэтому при выводе непонятно, ка-
кую из альтернатив необходимо выбрать, если руководствоваться только
начальными символами. То есть при выводе предложения 45 - 56 по первым
цифрам 45 невозможно определить какую альтернативу выбрать. Поэтому
грамматику нужно преобразовать. Введем новый нетерминал <D> для обоз-
начения различающихся частей первых двух альтернатив.
<D> ::= + <T>
||= - <T>
Тогда правила для нетерминала <E> примут следующий вид
<E> ::= <E><D>
||= <T>
Отметим, что используемый метод разбора допускает прямую левую
рекурсию. При разборе эта эти два правила автоматически будут рассмат-
риваться как <E> ::= <T> <* <D> *>_. Поэтому их дальнейшее преобразо-
вание можно опустить.
Вывод строки 44 - 56 будет следующим
<E> => <T><*<D>*>_ => string{09}<*<D>*>_ => 45<*<D>*>_
=> 45<D><*<D>*>_ => 45-<T><*<D>*>_ => 45-string{09}<*<D>*>
=> 45-56<*<D>*>_ => 45-56
Вместо введения нового нетерминала можно использовать круглые
скобки.
<E> ::= <E> ( + <T> | - <T>)
||= <T>
что эквивалентно <E> ::= <T> <* (+<T> | -<T>) *>_.
Вывод той же строки будет в этом случае таким
<E> => <T><*(+<T>|-<T>)*>_ => string{09}<*(+<T>|-<T>)*>_
=> 45<*(+<T>|-<T>)*>_ => 45(+<T>|-<T>)<*(+<T>|-<T>)*>_
=> 45-<T><*(+<T>|-<T>)*>_ => 45-string{09}<*(+<T>|-<T>)*>_
- 32 -
=> 45-56<*(+<T>|-<T>)*>_ => 45-56
Круглые скобки при выводе заменяются на одну из альтернатив, за-
писанных в них. В заключении данного параграфа отметим, что правила
начинающиеся с одинакового символа левой рекурсии, не обязательно
группировать.
<E> ::= <E> + <T>
||= <E> - <T>
||= <T>
автоматически заменятся на
<E> ::= <T> <* (+<T> | -<T>) *>_
Однако, если совпадающие начальные символы не образуют прямую левую
рекурсию, необходимо выполнять описанные выше преобразования.
Левый рекурсивный цикл необходимо всегда сводить к прямой левой
рекурсии. Например, если
<E> ::= <B>x
<B> ::= <E>y
то необходимо преобразовать в
<E> ::= <E>yx
14.3. Примеры преобразования грамматик на метаязыке
Пример 4.3.1. Рассмотрим грамматику
<A> ::= a<A>y
||= a'b'<A>x
Вынесем за скобки терминальный символ a. Получим
<A> ::= a (<A>y | 'b'<A>x)
Две альтернативы в скобка начинаются с различных терминальных
символов a и b.
Пример 4.3.2. Рассмотрим грамматику
<A> ::= a<A>y
- 33 -
||= a'b'<A>x
||= a
Поступаем аналогично предыдущему примеру.
<A> ::= a (<A>y | 'b'<A>x | *)
Напомним, что * обозначает пустой символ (отсутствие символа).
Пример 4.3.3. Рассмотрим грамматику
<A> ::= a<A>y
||= a'b'z<A>x
||= <A>b<A>y
||= a
Все альтернативы могут начинаются с символа a. Подставим в третью
альтернативу вместо <A> четвертую продукцию и введем её в качестве до-
полнительной альтернативы на место третьей. Подставляем именно четвер-
тую продукцию, поскольку за символом <A> в третьей продукции следует
b. Такая подстановка выявляет неоднозначность между второй и третьей
исходной продукциями (они начинаются с символов 'a''b').
<A> ::= a<A>y // 1 - альтернатива
||= a'b'z<A>x // 2
||= 'a'b<A>y // 3
||= <A>b<A>y // 4
||= a // 5
Данной подстановкой мы не изменили язык. Третья альтернатива дуб-
лируется в четвертой. Однако при выводе из четвертой альтернативы ни-
когда не будет получена третья, поскольку выбор альтернатив произво-
дится сверху вниз. Далее выполним уже известное преобразование.
<A> ::= a (<A>y | 'b'z<A>x | b<A>y | * ) | <A>b<A>y
В скобках получили альтернативы, начинающиеся с одного и того же
символа. Продолжим преобразования и получим.
<A> ::= a (<A>y | 'b'(z<A>x | <A>y) | * ) | <A>b<A>y
Необходимо строго соблюдать, чтобы четвертая альтернатива была
- 34 -
после третьей. Окончательно получим грамматику.
Рассмотрим еще один вариант преобразования данной грамматики.
<A> ::= a<A>y
||= a'b'z<A>x
||= <A>b<A>y
||= a
Леворекурсивный цикл автоматически приводится к виду
<A> ::= (a<A>y | a'b'z<A>x | а) <* b<A>y *>_
или
<A> ::= a(<A>y | 'b'z<A>x | *) <* b<A>y *>_
Видно, что при конкатенации пустого символа * и со строкой за
круглыми скобками, можно получить строку, начинающуюся с символа b. В
скобках у нас уже есть альтернатива начинающаяся также с символа b.
Поэтому выполним следующие преобразования.
<A> ::= a(<A>y | 'b'z<A>x | * | b<A>y) <* b<A>y *>_
<A> ::= a(<A>y | b (z<A>x | <A>y) | *) <* b<A>y *>_
Обратное преобразование к левой рекурсии.
<A> ::= a(<A>y | b (z<A>x | <A>y) | *)
||= <A>b<A>y
<A> ::= a(<A>y | b (z<A>x | <A>y) | * ) | <A>b<A>y
Пример 4.3.4. Рассмотрим грамматику
<A> ::= x<A>y
||= <P>
||= <Q>
||= x'x'<Z>
<P> ::= x<P>m
||= p
<Q> ::= x<Q>n
||= q
Стандартное преобразование приводит к тому, что в скобках альтер-
- 35 -
нативы начинаются снова с символов <P> и <Q> и так до бесконечности.
<A> ::= x<A>y --¬
||= x<P>m ¦
||= x<Q>n ¦ => <A> ::= x(A>y | <P>m | <Q>n | 'x'<Z>)
||= x'x'<Z> ¦ ||= p
||= p ¦ ||= q
||= q ---
В этом случае к цели приводит следующий путь.
<A> ::= x<A>y --¬ <A> ::= x'x'^_<A>'y'^_y
||= <P> ¦ => ||= x^_<P>m^_
||= <Q> ¦ ||= x^_<Q>n^_
||= x'x'<Z> --- ||= x'x'<Z>
<A> ::= x'x'^_<A>'y'^_y
||= x'x'^_<P>m^_m
||= x'x'^_<Q>n^_n
||= x'x'<Z>
||= <P>
||= <Q>
<A> ::= x('x'^_<A>'y'^_y | 'x'^_<P>m^_m | 'x'^_<Q>n^_n | 'x'<Z>)
<A> ::= x('x'^_<A>'y'^_y|'x'^_<P>m^_m|'x'^_<Q>n^_n|'x'^1<Z>*^1)
<A> ::= x('x'^_(<A>'y'^_y | <P>m^_m | <Q>n^_n | <Z>*^1))
Поскольку x^ может представлять и x^1 в частном случае, её можно
вынести за скобки. Заменим в первой альтернативе <A>, поскольку она
содержит одинаковые символы с <P> и <Q>.
<A> ::= x('x'^_('x'^_(<P>m^_'y'^_y |<Q>n^_'y'^_y |<A>'y'^_y |
<P>m^_m^_m | <Q>n^_n^_n) | <Z>*^1))
Хотя в скобках снова остались <Q> и <P> они уже не могут начаться
с символа x, поскольку все символы x вынесены за скобку.
.
- 36 -
<A> ::= x( 'x'^_( 'x'^_( <P>m^_('y'^_y |m^_m) |
<Q>n^_('y'^_y |n^_n) |
<A>'y'^_y
) | <Z>*^1
)
)
Окончательно получим
<A> ::= x( 'x'^_( ( <P>m^_('y'^_y |m^_m) |
<Q>n^_('y'^_y |n^_n) |
<A>'y'^_y
) | <Z>*^1
)
)
||= <P>
||= <Q>
Необходимо заметить, что при использование степеней нужно ис-
пользовать только вынос за скобки. Вводить новые нетерминалы запрещено
( это ограничение не метаязыка, а реализации )
Перечислим правила, которые использовались при преобразовании
грамматик.
- Подстановка вместо нетерминала его продукций.
- Вынос общей части продукций за круглые скобки.
- Добавление новой продукции, которая может быть порождена одной
из исходных продукций, для выявления продукций с одинаковыми начальны-
ми частями. Новая продукция помещается перед порождающей её продукцией.
- Замена группы продукций новым нетерминальным символом.
.
- 37 -
ПРИЛОЖЕНИЕ 1. Синтаксис метаязыка, записанный на метаязыке
Все тексты на метаязыке могут содержать любое число пробельных
символов, к которым относится и комментарий. К этой группе символов
относятся, собственно символы пробелов, символы вертикальной и гори-
зонтальной табуляции, символы новой строки (перевода строки и возврата
каретки) и комментарии.
<пробельные символы> ::= string{\11, /* HT */
\12, /* LF */
\13, /* VT */
\15, /* CR */
\40 /* whitespace */
}
||= <комментарий>
Комментарии используются для пояснения текста программы. Су-
ществует два способа задания комментариев:
<комментарий> ::= '/*' <текст комментария>_ '*/'
||= '//' <строчный комментарий>_
<текст комментария> ::= !string{'/*','*/'}
<строчный комментарий> ::= !string{\12,\15}
Дальнейшее описание строится в предположении, что исходный текст не
имеет пробельных символов. Все пробельные символы пропускаются в ис-
ходном тексте и используются в виде разделителей неявно. Явно в грам-
матике метаязыка они не представлены.
/*------------------------------------------------------------------*/
/* начальный символ грамматики */
- 38 -
<метаязык> ::= <нетерминал> <* <правило> *>
/*------------------------------------------------------------------*/
<нетерминал> ::= '<' <нетерминальный_символ> '>'
/*------------------------------------------------------------------*/
<правило > ::= <нетерминал>_
'::=' <альтернатива><*<следующая_альтернатива>*>_
/*------------------------------------------------------------------*/
<нетерминальный_символ>
::= string {az,AZ,ая,АЯ,'_',09}
/*------------------------------------------------------------------*/
<следующая_альтернатива>
::= '||=' <альтернатива>
||= '|' <альтернатива>
/*------------------------------------------------------------------*/
<альтернатива>
::= <сентенция> <альтернатива_5>_
||= <альтернатива_5>
||= '*'
<альтернатива_5>
::= '(' <альтернатива> <* <следующая_альтернатива> *>_ ')'
<семантика>_ <сентенция>_
/*------------------------------------------------------------------*/
- 39 -
<сентенция>
::= <сентенция> (<обозначение_сентенции> |
<терминал>) <семантика>_ '+'_
||= <обозначение_сентенции> <семантика>_ '+'_
||= <терминал> <семантика>_ '+'_
/*------------------------------------------------------------------*/
<семантика> ::= '#{' < текст_на_языке_СИ > '}#'
< текст_на_языке_СИ >
::= !string{'}#'}
/*------------------------------------------------------------------*/
<обозначение_сентенции>
::= ('<*' <сентенция> '*>' string{09}_ |
'<' <нетерминальный_символ> '>' <степень>_
) '_'_
/*------------------------------------------------------------------*/
<терминал> ::= (<*<символ_5>*><пробельные_символы><степень>_
| '\'' + <*<символ_6>*> '\'' <степень>_
| '!' <спец_терминал>
| '[' <элементы>_']'
| '{' <элементы> '}'
| 'char' '{' <элементы>_ '}'
| 'string' '{' <элементы> '}'
) '_'_
<спец_терминал>
- 40 -
::= '[' <элементы>_']'
||= '{' <элементы> '}'
||= 'char' '{' <элементы>_ '}'
||= 'string' '{' <элементы> '}'
<пробельные_символы>
::= char{\11,\12,\15,\40}
||= *
/*------------------------------------------------------------------*/
<степень> ::= '^'string{09}_
/*------------------------------------------------------------------*/
<элемент> ::= <символ> <символ>_ <пробельные_символы>
||= '\''+ <*<символ_6>*> '\''
/*------------------------------------------------------------------*/
<элементы> ::= <элемент> <* ',' <элемент> *>_
/*------------------------------------------------------------------*/
<символ> ::= \\ + <управляющая_последовательность>
||= ! char{'\11','\12','\15','\40',',','\'',']','}'}+
/* любой символ ASCII кроме ' , ] } */
<символ_5> ::= \\ + <управляющая_последовательность>
||= ! char{'\11','\12','\15','\40',#,:,^,'\'',(,),*,
{,|,<}+
/* любой символ ASCII кроме
# : ^ ( ) * { | < */
<символ_6> ::= \\ + <управляющая_последовательность>
- 41 -
||= ! char{'\''} + /* любой символ ASCII кроме
' */
/*------------------------------------------------------------------*/
<управляющая_последовательность>
::= char{a,b,f,n,r,t,v,'\\','\'','\"','\?'}+
||= string{07}+
||= char{x,X} + string{09,AF} +
/*------------------------------------------------------------------*/