Лицензирование
Код распространяется под лицензией BSD 2clause. Участники, делающие запросы на исправление, должны согласиться с тем, что они могут и хотят разместить свой вклад под этой лицензией.
Цели и нецели Pygments
Поддержка Python
Pygments поддерживает все поддерживаемые версии Python согласно Python Developer's Guide. Кроме того, поддерживается версия Python по умолчанию в последней стабильной версии RHEL, Ubuntu LTS и Debian, даже если они официально устарели. Поддержка других устаревших версий не является целью Pygments.
Валидация
Pygments не пытается проверить входные данные. Принятие кода, который не является законным для данного языка, допустимо, если это упрощает кодовую базу и не приводит к неожиданному поведению. Например, в C89, принятие комментариев на основе //
было бы нормально, потому что де-факто все компиляторы поддерживают это, и создание отдельного лексера для этого не стоит того.
Контрольный список вкладов
Посмотрите в документации, как написать новый лексер, новый форматтер или новый фильтр
Обязательно добавьте тест для вашей новой функциональности и, где это необходимо, напишите документацию.
При написании правил старайтесь объединять простые правила. Например, объедините:
_PUNCTUATION = [ (r"\(", token.Punctuation), (r"\)", token.Punctuation), (r"\[", token.Punctuation), (r"\]", token.Punctuation), ("{", token.Punctuation), ("}", token.Punctuation), ]
в:
(r"[\(\)\[\]{}]", token.Punctuation)
Будьте осторожны с
.*
. В этом случае совпадения будут настолько жадными, насколько это возможно. Например, правило@.*@
будет соответствовать всей строке@first@ second @third@
, вместо того, чтобы соответствовать@first@
и@second@
. В этом случае вы можете использовать@.*?@
, чтобы остановиться раньше.?
пытается совпасть как можно меньше раз.Остерегайтесь так называемого "катастрофического отката". В качестве первого примера рассмотрим регулярное выражение
(A+)*C
. Оно эквивалентноA*B
в отношении того, чему оно соответствует, но non-поиск займет очень много времени. Это связано с тем, как работает механизм регулярных выражений. Предположим, вы передаете ему 50 'A' и 'C' в конце. Сначала он жадно ищет 'A' вA+
, но обнаруживает, что не может найти конец, поскольку 'B' не совпадает с 'C'. Тогда он отступает назад, удаляя одно 'A' из первогоA+
и пытаясь подобрать оставшееся как еще одно(A+)*
. Это снова не удается, поэтому он отступает дальше влево во входной строке и т.д. По сути, он пробует все комбинации(AAAAAAAAAAAAAAAAA) (AAAAAAAAAAAAAAAA)(A) (AAAAAAAAAAAAAAA)(AA) (AAAAAAAAAAAAAAA)(A)(A) (AAAAAAAAAAAAAA)(AAA) (AAAAAAAAAAAAAA)(AA)(A) ...
Таким образом, согласование имеет экспоненциальную сложность. В лексере это приводит к тому, что Pygments, похоже, зависнет при разборе некорректного ввода.
>>> import re >>> re.match('(A+)*B', 'A'*50 + 'C') # hangs
В качестве более тонкого и реального примера приведем плохо написанное регулярное выражение для сопоставления строк:
r'"(\\?.)*?"'
Если завершающая кавычка отсутствует, механизм регулярного выражения обнаружит, что он не может найти совпадение в конце, и попытается вернуться назад с меньшим количеством совпадений в
*?
. Когда он находит обратную косую черту, поскольку он уже пробовал возможность.
, он пробует.
(распознавая его как простой символ без значения), что приводит к той же экспоненциальной проблеме отката, если в (неправильной) входной строке много обратных косых черт. Хорошим способом записать это было быr'"([^\\]|\\.)*?"'
, где внутренняя группа может совпадать только одним способом. Еще лучше использовать специальное состояние, которое не только позволяет обойти проблему без головной боли, но и дает возможность выделять экранирование строк.'root': [ ..., (r'"', String, 'string'), ... ], 'string': [ (r'\\.', String.Escape), (r'"', String, '#pop'), (r'[^\\"]+', String), ]
При написании правил для шаблонов, например, комментариев или строк, сопоставляйте как можно больше символов в каждом токене. Вот пример того, чего делать не следует:
'comment': [ (r'\*/', Comment.Multiline, '#pop'), (r'.', Comment.Multiline), ]
В этом случае генерируется по одному токену на каждый символ в комментарии, что замедляет процесс лексирования, а также делает вывод необработанных токенов (и особенно тестовый вывод) трудночитаемым. Вместо этого сделайте следующее:
'comment': [ (r'\*/', Comment.Multiline, '#pop'), (r'[^*]+', Comment.Multiline), (r'\*', Comment.Multiline), ]
Не добавляйте импорт вашего лексера нигде в кодовой базе. (В случае, если вам интересно
compiled.py
-- этот файл существует по причинам обратной совместимости)Используйте стандартное соглашение об импорте:
from token import Punctuation
Для тестовых примеров, которые проверяют лексемы, произведенные лексером, используйте инструменты:
Вы можете использовать
testcase
форматер для создания фрагмента кода, который можно вставить в файл unittest:python -m pygments -l lua -f testcase <<< "local a = 5"
Большинство сниппетов вместо этого следует поместить в файл с образцом под
tests/snippets/<lexer_alias>/*.txt
. Эти файлы автоматически подхватываются как отдельные тесты, утверждающие, что на входе получаются ожидаемые лексемы.Чтобы добавить новый тест, создайте файл с фрагментом кода в подкаталоге, основанном на основном псевдониме вашего лексера. Затем запустите
pytest --update-goldens <filename.txt>
для автоматического заполнения ожидаемых в данный момент лексем. Проверьте, что они выглядят хорошо, и занесите их в файл.Также выполняйте ту же команду всякий раз, когда вам нужно обновить тест, если фактические произведенные токены изменились (при условии, что изменения ожидаются).
Большие файлы тестов следует помещать в
tests/examplefiles
. Это работает аналогичноsnippets
, но вывод токенов хранится в отдельном файле. Вывод также может быть регенерирован с помощью--update-goldens
.