Чтение онлайн

на главную - закладки

Жанры

Давайте создадим компилятор!
Шрифт:

IF ((((((A + B + C) < 0 ) AND....

Когда синтаксический анализатор анализирует этот код он знает, что после того, как он рассмотрит токен IF следующим должно быть булево выражение. Поэтому он может приступить к началу вычисления такого выражения. Но первое выражение в примере является арифметическим выражением A + B + C. Хуже того, в точке, в которой анализатор прочитал значительную часть входной строки:

IF ((((((A ,

он все еще не имеет способа узнать с каким видом выражения он имеет дело. Так не пойдет, потому что мы должны иметь две различных программы распознавания для этих двух

случаев. Ситуация может быть обработана без изменения наших определений но только если мы захотим принять произвольное количество возвратов (backtracking) чтобы избавить наш путь от неверных предположений. Ни один из создателей компиляторов в здравом уме не согласился бы на это.

Происходит то, что красота и элегантность грамматики БНФ столкнулась лицом к лицу с реальностью технологии компиляции.

Чтобы работать с этой ситуацией создатели компиляторов должны идти на компромиссы, так чтобы один анализатор мог бы поддерживать грамматику без возвратов.

Исправление грамматики

Проблема, с которой мы столкнулись, возникает потому, что наше определение и арифметических и булевых показателей позволяет использовать выражения в скобках. Так как определения рекурсивны, мы можем закончить с любым числом уровней скобок и синтаксический анализатор не может знать с каким видом выражения он имеет дело.

Решение просто, хотя и приводит к глубоким изменениям нашей грамматики. Мы можем разрешить круглые скобки только в одном виде показателей. Способ сделать это значительно изменяется от языка к языку. Это то место, где не существует соглашения или договора способного нам помочь.

Когда Никлаус Вирт разработал Паскаль, его желанием было ограничить количество уровней приоритета (меньше подпрограмм синтаксического анализа, в конце концов). Так операторы OR и исключающее OR рассматриваются просто как Addop и обрабатываются на уровне математического выражения. Аналогично AND рассматривается подобно Mulop и обрабатывается с Term. Уровни приоритета:

Заметьте, что имеется только один набор синтаксических правил, применимый к обоим видам операторов. Тогда согласно этой грамматике выражения типа:

x + (y AND NOT z) DIV 3

являются совершенно допустимыми. И, фактически, они таковыми являются... настолько, насколько синтаксический анализатор в этом заинтересован. Паскаль не позволяет смешивать арифметические и логические переменные, и подобные вещи скорее перехватываются на семантическом уровне, когда придет время генерировать для них код, чем на синтаксическом уровне.

Авторы C взяли диаметрально противоположный метод: они обрабатывают операторы как разные и C имеет что-то гораздо более похожее на наши семь уровней приоритета. Фактически, в C имеется не менее 17 уровней! Дело в том, что C имеет также операторы '=', '+=' и их родственников '<<', '>>', '++', '–' и т.д. Как ни странно, хотя в C арифметические и булевые операторы обрабатываются раздельно, то переменные нет... в C нет никаких булевых или логических переменных, так что логическая проверка может быть сделана на любом целочисленном значении.

Мы

сделаем нечто среднее. Я склонен обычно придерживаться Паскалевского подхода, так как он кажется самым простым с точки зрения реализации, но это приводит к некоторым странностям, которые я никогда очень сильно не любил, как например в выражении:

IF (c >= 'A') and (c <= 'Z') then ...

скобки обязательны. Я никогда не мог понять раньше почему, и ни мой компилятор, ни любой человек также не объясняли этого достаточно хорошо. Но сейчас мы все можем видеть, что оператор «and», имеющий приоритет как у оператора умножения, имеет более высокий приоритет, чем у операторов отношения, поэтому без скобок выражение эквивалентно:

IF c >= ('A' and c) <= 'Z' then

что не имеет смысла.

В любом случае, я решил разделить операторы на различные уровни, хотя и не столько много как в C.

<b-expression> ::= <b-term> [<orop> <b-term>]*

<b-term> ::= <not-factor> [AND <not-factor>]*

<not-factor> ::= [NOT] <b-factor>

<b-factor> ::= <b-literal> | <b-variable> | <relation>

<relation> ::= | <expression> [<relop> <expression]

<expression> ::= <term> [<addop> <term>]*

<term> ::= <signed factor> [<mulop> factor]*

<signed factor>::= [<addop>] <factor>

<factor> ::= <integer> | <variable> | (<b-expression>)

Эта грамматика приводит к тому же самому набору семи уровней, которые я показал ранее. Действительно, это почти таже самая грамматика... я просто исключил заключенное в скобки b-выражение как возможный b-показатель и добавил отношение как допустимую форму b-показателя.

Есть одно тонкое, но определяющее различие, которое заставляет все это работать. Обратите внимание на квадратные скобки в определении отношения. Это означает, что relop и второе выражение являются необязательными.

Странным последствием этой грамматики (которое присутствует и в C) является то, что каждое выражения потенциально является булевым выражение. Синтаксический анализатор всегда будет искать булевское выражение, но «уладит» все до арифметического. Честно говоря, это будет замедлять синтаксический анализатор потому что он должен пройти через большее количество вызовов процедур. Это одна из причин, почему компиляторы Паскаля обычно быстрее выполяют компиляцию, чем компиляторы C. Если скорость для вас – больное место, придерживайтесь синтаксиса Паскаля.

Синтаксический анализатор

Теперь, когда мы прошли через процесс принятия решений, мы можем поспешить с разработкой синтаксического анализатора. Вы делали это со мной несколько раз до этого, поэтому вы знаете последовательность: мы начнем с новой копии Cradle и будем добавлять процедуры одна за другой. Так что давайте сделаем это.

Мы начинаем, как и в случае с арифметикой, работая с булевыми литералами а не с переменными. Это дает нам новый вид входного токена, поэтому нам также нужна новая программа распознавания и новая процедура для чтения экземпляров этого типа токенов. Давайте начнем, определив эти две новые процедуры:

Поделиться:
Популярные книги

Проданная невеста

Wolf Lita
Любовные романы:
любовно-фантастические романы
5.80
рейтинг книги
Проданная невеста

Сумеречный Стрелок 10

Карелин Сергей Витальевич
10. Сумеречный стрелок
Фантастика:
рпг
аниме
фэнтези
5.00
рейтинг книги
Сумеречный Стрелок 10

Картофельное счастье попаданки

Иконникова Ольга
Фантастика:
фэнтези
5.00
рейтинг книги
Картофельное счастье попаданки

Совершенно несекретно

Иванов Дмитрий
15. Девяностые
Фантастика:
попаданцы
альтернативная история
5.00
рейтинг книги
Совершенно несекретно

Нечто чудесное

Макнот Джудит
2. Романтическая серия
Любовные романы:
исторические любовные романы
9.43
рейтинг книги
Нечто чудесное

Истребители. Трилогия

Поселягин Владимир Геннадьевич
Фантастика:
альтернативная история
7.30
рейтинг книги
Истребители. Трилогия

Кротовский, может, хватит?

Парсиев Дмитрий
3. РОС: Изнанка Империи
Фантастика:
попаданцы
альтернативная история
аниме
7.50
рейтинг книги
Кротовский, может, хватит?

Прометей: каменный век II

Рави Ивар
2. Прометей
Фантастика:
альтернативная история
7.40
рейтинг книги
Прометей: каменный век II

Сердце для стража

Каменистый Артем
5. Девятый
Фантастика:
фэнтези
боевая фантастика
9.20
рейтинг книги
Сердце для стража

Сделай это со мной снова

Рам Янка
Любовные романы:
современные любовные романы
5.00
рейтинг книги
Сделай это со мной снова

Господин следователь 6

Шалашов Евгений Васильевич
6. Господин следователь
Фантастика:
попаданцы
альтернативная история
5.00
рейтинг книги
Господин следователь 6

Ваше Сиятельство 10

Моури Эрли
10. Ваше Сиятельство
Фантастика:
боевая фантастика
технофэнтези
фэнтези
эпическая фантастика
5.00
рейтинг книги
Ваше Сиятельство 10

Имперский Курьер. Том 5

Бо Вова
5. Запечатанный мир
Фантастика:
попаданцы
аниме
фэнтези
5.00
рейтинг книги
Имперский Курьер. Том 5

Егерь

Астахов Евгений Евгеньевич
1. Сопряжение
Фантастика:
боевая фантастика
попаданцы
рпг
7.00
рейтинг книги
Егерь