Современные информационные технологии/ 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.