Современные
информационные технологии/ 3.Программное обеспечение
к.филол.н. В. В.
Лидовский
“МАТИ” ―
Российский Государственный Технологический Университет имени
К. Э. Циолковского, Российская федерация
Актуальность
использования канонических LR(1)-таблиц на современных ПК
Как
и LALR-разбор, LR-разбор имеет линейную временную сложность. LR-разбор имеет
несколько теоретических преимуществ. Использование LR(1)-разбора вместо
LALR(1)-разбора может устранить некоторые конфликты типа свёртка-свёртка,
которые, как отмечается в [8], «обозначают серьёзную проблему» при
разработке компилятора: LR(1)-разбор успешно справляется с ситуациями,
порождающими «таинственные конфликты свёртка-свёртка» [1] при
LALR(1)-разборе. Кроме того, использование LR(1)-разбора позволяет наиболее
точно специфицировать ошибки при компиляции. При LALR(1)-разборе диагностика
ошибок менее точна, так как многие ошибочные ситуации объединяются.
После
опубликования Д. Кнутом работы [2], определяющей LR(k)-алгоритм, прошло
более 10 лет прежде чем его упрощённый вариант, LALR(1), чрезвычайно удачно
реализованный С. Джонсоном в программе Як (yacc), стал вытеснять прежде
преобладавшие методы разбора сверху-вниз. Необходимость упрощения вытекала из
аппаратных ограничений вычислительных машин того времени. В фундаментальном
труде [7] отмечается, что «построение полной системы LR(1)-множеств
пунктов требует слишком много памяти и времени, чтобы использоваться на
практике».
Компьютер
с 512–2048 мегабайтами оперативной памяти уже стал фактическим стандартном и
можно попытаться усомниться в верности приведённой цитаты, сформулированной
тогда, когда стандартным объёмом памяти считались 64–512 килобайт. Однако, в
новом учебнике для вузов [10] отмечается «высокая трудоемкость построения
управляющей таблицы для LR(1)-грамматик».
Автором
и М. Молокановым на кафедре «Моделирование систем и информационные
технологии» было проведено соответствующее исследование в течении семестра. На
языке программирования Си++ со стандартной библиотекой была реализована
компьютерная программа lr-lalr-test, которая по заданной грамматике строит
соответствующую ей полную каноническую систему LR(1)-множеств пунктов, а также
полную систему LALR(1)-множеств пунктов. Последнее построение позволяет, в
частности, верифицировать результаты программой Бизон. Программа также
вычисляет количества терминалов, метасимволов, правил, LR- и LALR-множеств
пунктов и другие численные характеристики грамматики. Выбор языка
программирования был обусловлен прежде всего необходимостью проведения больших
объемов расчетов за приемлемое время и поддержкой быстрой работы с множествами.
Языки Лисп и Пролог, а также Перл, Питон, Рубин и некоторые другие, имеющие
необходимые средства, показали значительно меньшую скорость работы.
Использовалась система программирования GNU Си++: компилятор, отладчик,
профайлер и компоновщик. Алгоритм для построения канонической системы множеств
пунктов взят из [7].
Для
анализа использовались следующие языки в их свободно-распространяемых вариантах
с открытыми текстами исходников (далее следует список языков с указанием
названия, года версии исходников, разработчика, сетевого адреса, примечаний):
·
Бизон
― bison (2008, FSF, http://www.gnu.org/software/bison/, GNU вариант
синтаксического сканера Як);
·
Биси
― bc (1997, FSF, http://www.gnu.org/software/bc/, консольный калькулятор
произвольной точности);
·
Бэш
― bash (2009, FSF ― Free Software Foundation,
http://tiswww.case.edu/php/chet/bash/bashtop.html, язык стандартной оболочки
Linux);
·
Майэскьюэль
― mysql (2008, MySQL AB и Sun Microsystems, http://www.mysql.com/,
вариант Эскьюэль ― SQL);
·
Обджектив
Си ― Objective C (2001, FSF, http://gcc.gnu.org/, GNU вариант похожего на
Си языка фирмы Apple);
·
Оок
― awk (2009, FSF, http://www.gnu.org/software/gawk/, gawk ― GNU
вариант);
·
Паскаль
(2006, FSF, http://www.gnu-pascal.de/gpc/h-index.html, gpc ― GNU вариант
расширенного Паскаля);
·
Перл ― perl (2008, Larry Wall and others, http://www.perl.org/);
·
Питон
― python (2008, PSF ― Python Software Foundation,
http://www.python.org, исходники из расширенных НФБН, принятой в PSF, были
преобразованы к обычным НФБН);
·
Постгрескьюэль ― postgreSQL (2010, Global Development Group,
http://www.postgresql.org/, вариант Эскьюэль);
·
ПХП ― php (2010, Zend Technologies, http://www.php.net);
·
Рубин ― ruby (2007, Юкихиро Мацумото
― Yukihiro Matsumoto, http://www.ruby-lang.org/en/);
·
Си
(2001, FSF, http://gcc.gnu.org/, gcc ― GNU вариант Си);
·
Си++
(2001, FSF, http://gcc.gnu.org/, g++ ― GNU вариант Си++);
·
Флекс ― flex (1990, The Regents of the University of California ,
http://flex.sourceforge.net/, GNU вариант лексического
сканера
Лекс ― lex);
·
Хок
― hoc (1984, Керниган и Пайк, http://litwr.atspace.com, расширение
описанного в [8] программируемого консольного калькулятора);
·
Ява
― java (2001, FSF, http://gcc.gnu.org/, GNU вариант языка Ява);
·
PL/pgSQL
(2010, Global Development Group, http://www.postgresql.org/, встроенный в
Постгрескьюэль процедурный язык).
Грамматика
для программы lr-lalr-test задаётся файлом в особом формате, получаемом из
исходного файла в формате Як/Бизон обработкой специально разработанной
программой-препроцессором. Цель препроцессора ― получить «чистую»
грамматику, в частности, без информации о приоритетах знаков и правил.
Полученные результаты приводятся в следующей таблице.
Количество
LALR-состояний получается на единицу меньшим, чем у программ Як/Бизон, из-за
отсутствия избыточного финального сдвига по особому конечному символу входной
цепочки. Количество терминалов на два, а нетерминалов на n+1, где
n ― это количество неявных пустых правил (правил, порождаемых
семантическими действиями между символами правил грамматики), больше, чем в
исходной грамматике. Добавленные терминалы ― это уже упомянутый
конечный символ входной цепочки и символ-ошибка, а добавленный нетерминал ―
это начальный символ расширенной грамматики.
Язык |
(1) |
(2) |
(3) |
(4) |
(5) |
(6) |
|
Бэш |
59 |
38 (0) |
167 |
680 |
3684 |
343 |
0.84 |
Бизон |
56 |
33 (3) |
105 |
267 |
239 |
142 |
0.04 |
Биси |
49 |
35 (13) |
107 |
324 |
1149 |
184 |
0.41 |
Майэскьюэль |
588 |
837 (176) |
2377 |
6937 |
4823743 |
4076 |
? |
Обджектив Си |
87 |
237 (65) |
589 |
1814 |
4353 |
991 |
13.57 |
Оок |
66 |
56 (5) |
165 |
526 |
3217 |
307 |
4.98 |
Паскаль |
138 |
294 (65) |
797 |
2394 |
39355 |
1329 |
20.22 |
Перл |
89 |
66 (2) |
209 |
727 |
3600 |
418 |
18.37 |
Питон |
85 |
174 (83) |
333 |
876 |
4758 |
529 |
1.62 |
Постгрескьюэль |
430 |
515(0) |
2090 |
6926 |
873573 |
3837 |
2426.76 |
ПХП |
153 |
181 (68) |
463 |
1512 |
10986 |
892 |
33.4 |
Рубин |
148 |
170 (32) |
561 |
1730 |
180954 |
970 |
306.55 |
Си |
87 |
167 (40) |
426 |
1357 |
2888 |
728 |
7.29 |
Си++ |
112 |
294 (57) |
918 |
3030 |
31683 |
1789 |
109.25 |
Флекс |
68 |
27 (0) |
97 |
275 |
232 |
139 |
0.05 |
Хок |
40 |
17 (2) |
63 |
210 |
692 |
119 |
0.93 |
Ява |
110 |
163 (12) |
504 |
1721 |
5594 |
774 |
17.01 |
PL/pgSQL |
99 |
78 (2) |
179 |
446 |
684 |
249 |
0.26 |
(1)
― число терминалов, (2) ― число метасимволов (неявных),
(3)
― количество правил, (4) ― вес грамматики,
(5)
― число LR(1)/LALR(1)-множеств, (6) ― время, сек
Очевидно, что общее количество вариантов LR(1)-пунктов
не превосходит wt, где w ― это суммарное количество символов, кроме
пустых, во всех правилах ― вес грамматики, а t ― количество
терминалов. Следовательно, общее количество LR(1)-состояний не превосходит 2wt.
Из-за того, что в LALR-пунктах FIRST-часть пунктов теряет различающее значение,
максимум количества LALR-пунктов в t раз меньше и, соответственно, максимум
общего количества подмножеств всех LALR-пунктов ― 2w.
Время работы по сравнению с программой Бизон увеличилось
значительно ― на несколько порядков ― соответственно росту
количества множеств-состояний. Однако, время построения LR(1)-системы вполне
сопоставимо с временем компиляции файлов исходников языков программирования в
исполняемые файлы. Кроме того, возможна лучшая оптимизация кода. Как уже
отмечалось, разница в количестве LALR- и LR-состояний, в общем случае,
определяется экспоненциальной зависимостью, что делает LR-разбор в крайних
ситуациях фундаментально неэффективным. Эта общая теоретическая неэффективность
практически проявила себя только к языку Майэскьюэль, для которого даже 16
гигабайт оперативной памяти оказались недостаточными и, частично, к языкам
Рубин и Постгрескьюэль, потребовавшим соответственно 1 и 4 гигабайт для
вычислений без привлечения медленной виртуальной (дисковой) памяти. Поэтому
можно предположить, что использование LR-разбора, как одной из опций восходящего
анализа, является вполне приемлемым для ресурсов существующих компьютеров во
многих случаях. Кроме того, необязательно использовать именно
канонические таблицы. Существуют несколько алгоритмов [3–6,9], позволяющих
получать только существенные для работы анализатора состояния, что позволяет
значительно сократить общее количество LR-состояний и намного увеличить скорость
работы программы. Хотя в этом случае будет теряться часть информации об
ошибках разбора.
Помимо использования программы Бизон результаты
верифицировались LR-сканерами Мста (Msta) и Hyacc. Для Майэскьюэль результат
удалось получить только Мстой.
Учитывая идентичность алгоритмов LALR- и LR-разборов
(они отличаются только наполнением таблиц) можно естественно интегрировать
LR-разбор в существующие программы для генерации компиляторов, права
копирования на которые позволяют производить такую разработку. Можно, например,
добавить как опцию LR-разбор программам Як или Бизон. В последнем случае можно
использовать возможность подключения к проекту Бизон в отдельной svn-ветке.
Уже существует несколько программ для LR(1)-разбора
(CSP, Dragon, Hyacc, Lisa, Yooparse, Whale, ...), но они либо вводят
собственный не вполне совместимый c Бизон синтаксис для определения грамматик,
либо не реализуют всех возможностей программы Бизон, что, учитывая широкую
распространённость исходников и прочих материалов для Як/Бизон, не вполне
удобно. В частности, при тестировании программы Yooparse были обнаружены ошибки
в построенной системе множеств пунктов для языка Перл ― точнее не всегда
вычисляются правильно FIRST-части пунктов. Новая программа Hyacc показала не
полную совместимость с форматом грамматики программ Як/Бизон и слишком
расточительное обращение с памятью ― она не смогла вычислить каноническую
таблицу LR-анализа на компьютере с 8 гигабайтами памяти для языка Рубин. Кроме
того, часть LR(1)-анализаторов ― это далее не сопровождаемые проекты,
использование которых проблематично.
Исходники программы lr-lalr-test и документация к ней
доступны по адресу http://litwr.atspace.com ― они распространяются на
условиях лицензии GNU GPL (http://www.gnu.org/copyleft/gpl.html).
Среди побочных результатов исследования было обнаружено
значительное расхождение с [11] по численным характеристикам синтаксиса,
что свидетельствует о том, что грамматики для разных версий языка, а также
грамматики, написанные для генерации синтаксических анализаторов, и грамматики,
теоретически определяющие язык, могут весьма значительно различаться по этим
характеристикам.
Литература
1. C. Donnely, R. Stallman Bison:
2 April 2009, version 2.4.1 ― Free Software Foundation, ISBN
1-882114-44-2 (http://www.gnu.org). 174 pages.
2.
D. Knuth On the
translation of languages from left to right //Information and
control 8, 1965. P. 607–639.
3.
D. Pager A
Practical General Method for Constructing LR(k) Parsers //Acta
Informatica 7, 1977. P. 249–268.
4.
D. Pager Eliminating
Unit Productions from LR Parsers //Acta Informatica 9, 1977.
P. 31–59.
5.
D. Pager The
Lane-Tracing Algorithm for Constructing LR(k) Parsers and Ways of Enhancing Its
Efficiency //Information Sciences 12, 1977. P. 19–42.
6. D. Pager The lane tracing algorithm for
constructing LR(k) parsers //Proceedings of the fifth annual ACM
symposium on Theory of computing, 1973. P. 172–181.
7. А. Ахо,
Р. Сети, Д. Ульман Компиляторы: принципы, технологии и инструменты: Пер. с
англ. М.: Издательский дом «Вильямс», 2003. 768 c.
8. Б. Керниган,
Р. Пайк UNIX ― универсальная среда программирования:
Пер. с англ. М.: Финансы и статистика, 1992. 304 c.
9. В. Н. Макаров
Практичный метод оптимизации LR(1)-анализаторов
//Программирование. 1988. № 3. C. 38–48.
10. А. Ю. Молчанов
Системное программное обеспечение СПб.: Питер, 2010. 400 с.
11. С. Свердлов Арифметика
синтаксиса //PC Week. 1998. № 42–43 (166-167). C. 84–87.