re2c — бесплатный генератор лексеров с открытым исходным кодом для C , C++ , Go и Rust . Он компилирует декларативные спецификации регулярных выражений в детерминированные конечные автоматы . Первоначально написанный Питером Бумбулисом и описанный в его статье, [1] re2c был выложен в открытый доступ и с тех пор поддерживается добровольцами. [3] Это генератор лексеров, принятый в таких проектах, как PHP , [4] SpamAssassin , [5] Система сборки Ninja [6] и других. Вместе с генератором парсера Lemon в BRL-CAD используется re2c . [7] Эта комбинация также используется со STEPcode, реализацией стандарта ISO 10303 . [8]
Основная цель re2c — создание быстрых лексеров: [1] по крайней мере так же быстро, как разумно оптимизированные лексеры C , написанные вручную. Вместо использования традиционного табличного подхода re2c кодирует сгенерированный конечный автомат непосредственно в форме условных переходов и сравнений. Полученная программа работает быстрее, чем ее аналог, управляемый таблицами [1] , и ее гораздо проще отлаживать и понимать. Более того, этот подход часто приводит к уменьшению лексеров [1] , поскольку re2c применяет ряд оптимизаций, таких как минимизация DFA и построение туннельного автомата. [9] Еще одной отличительной особенностью re2c является его гибкий интерфейс: вместо использования фиксированного шаблона программы re2c позволяет программисту писать большую часть кода интерфейса и адаптировать сгенерированный лексер к любой конкретной среде. Основная идея заключается в том, что re2c должен быть абстракцией с нулевой стоимостью для программиста: его использование никогда не должно приводить к более медленной программе, чем соответствующая реализация, написанная вручную.
Программа re2c может содержать любое количество /*!re2c ... */
блоков. Каждый блок состоит из последовательности правил , определений и конфигураций
(их можно смешивать, но обычно лучше сначала помещать конфигурации, затем определения и только потом правила). Правила имеют форму REGEXP { CODE }
или REGEXP := CODE;
где REGEXP
— регулярное выражение и CODE
представляет собой блок кода C. При REGEXP
совпадении с входной строкой поток управления передается связанному файлу CODE
. Существует одно специальное правило: правило по умолчанию с *
вместо REGEXP
; он срабатывает, если никакие другие правила не совпадают. re2c имеет жадную семантику сопоставления: если совпадают несколько правил, предпочтение отдается правилу, которое соответствует более длинному префиксу; если конфликтующие правила соответствуют одному и тому же префиксу, приоритет имеет более раннее правило. Определения имеют форму NAME = REGEXP;
(и также NAME { REGEXP }
в режиме совместимости с Flex ). Конфигурации имеют форму, re2c:CONFIG = VALUE;
где CONFIG
— имя конкретной конфигурации и VALUE
число или строка. Для более продвинутого использования см. официальное руководство по re2c. [22]
re2c использует следующий синтаксис для регулярных выражений:
"foo"
строковый литерал с учетом регистра'foo'
строковый литерал без учета регистра[a-xyz]
, [^a-xyz]
класс символов (возможно, отрицательный).
любой символ, кроме новой строкиR \ S
разница классов персонажейR*
ноль или более вхожденийR
R+
одно или несколько вхожденийR
R?
необязательныйR
R{n}
повторение R
ровно n
разR{n,}
повторение R
минимум n
разR{n,m}
повторение от R
и n
до m
раз(R)
только R
; круглые скобки используются для переопределения приоритета или для соответствия в стиле POSIX.R S
конкатенация: R
за которой следуетS
R | S
альтернатива: R
илиS
R / S
просмотр вперед: R
за ним следует S
, но S
не потребляетсяname
регулярное выражение, определенное как name
(кроме режима совместимости Flex )@stag
s -тег : сохраняет последнюю входную позицию, в которой @stag
есть совпадения, в переменной с именемstag
#mtag
m -тег : сохраняет все входные позиции, в которых #mtag
есть совпадения, в переменной с именемmtag
Классы символов и строковые литералы могут содержать следующие escape-последовательности: \a
, \b
, \f
, \n
, \r
, \t
, \v
, \\
, восьмеричные escape-последовательности и \ooo
шестнадцатеричные escape-последовательности \xhh
и \uhhhh
.\Uhhhhhhhh
Вот очень простая программа на re2c (example.re). Он проверяет, что все входные аргументы являются шестнадцатеричными числами. Код re2c заключен в комментарии /*!re2c ... */
, все остальное — простой код на C. Более сложные примеры см. на официальном сайте re2c. [23]
#include <stdio.h> static int lex ( const char * YYCURSOR ) { const char * YYMARKER ; /*!re2c re2c:define:YYCTYPE = char; re2c: yyfill: включить = 0; конец = "\x00"; шестнадцатеричный = "0x" [0-9a-fA-F]+; * { printf("ошибка\n"); возврат 1; } hex end { printf("hex\n"); вернуть 0; } */ }int main ( int argc , char ** argv ) { for ( int i = 1 ; i < argc ; ++ i ) { lex ( argv [ i ]); } вернуть 0 ; }
Учитывая это, re2c -is -o example.c example.re
генерируется приведенный ниже код (example.c). Содержимое комментария /*!re2c ... */
подменяется детерминированным конечным автоматом
, закодированным в виде условных переходов и сравнений; остальная часть программы дословно копируется в выходной файл. Существует несколько вариантов генерации кода; обычно re2c использует switch
операторы, но он может использовать вложенные if
операторы (как в этом примере с -s
опцией) или генерировать растровые изображения и таблицы переходов. Какой вариант лучше, зависит от компилятора C; Пользователям re2c рекомендуется экспериментировать.
/* Сгенерировано re2c 1.2.1 в пятницу, 23 августа, 21:59:00 2019 г. */ #include <stdio.h> static int lex ( const char * YYCURSOR ) { const char * YYMARKER ; { чар yych ; yych = * YYКУРСОР ; if ( yych == '0' ) перейти к yy4 ; ++ YYКУРСОР ; yy3 : { printf ( «ошибка \n » ); вернуть 1 ; } yy4 : yych = * ( YYMARKER = ++ YYCURSOR ); if ( yych != 'x' ) перейти к yy3 ; yych = *++ YYCURSOR ; если ( yych >= 0x01 ) перейти к yy8 ; yy6 : YYCURSOR = YYMARKER ; перейти к гг3 ; yy7 : yych = *++ YYCURSOR ; yy8 : if ( yych <= '@' ) { if ( yych <= 0x00 ) перейти к yy9 ; if ( yych <= '/' ) перейти к yy6 ; if ( yych <= '9' ) перейти к yy7 ; перейти к yy6 ; } else { if ( yych <= 'F' ) перейти к yy7 ; if ( yych <= '`' ) перейти к yy6 ; if ( yych <= 'f' ) перейти к yy7 ; перейти к yy6 ; } yy9 : ++ YYCURSOR ; { printf ( "шестнадцатеричный \n " ); вернуть 0 ; } } }int main ( int argc , char ** argv ) { for ( int i = 1 ; i < argc ; ++ i ) { lex ( argv [ i ]); } вернуть 0 ; }