Забавное использование Ragel State Machine Compiler для создания функции разбора строки на int argc, char *argv[].
Все началось с того, что понадобилась функция buildargv, чтобы разбирать строку для последующей передачи в
int main (int argc, char *argv[]) { body }
Ну ладно подумал я, не может быть, чтобы нигде нельзя было позаимствовать, сейчас найдем... И не нашёл...
Ну не то что бы совсем не нашёл, вот например https://github.com/gcc-mirror/gcc/blob/master/libiberty/argv.c (GPLv2 - это всегда хорошо), брать сразу сходу на себя такие обязательства я был не готов. Точно есть такая функция в bash (GPLv3 - это еще лучше). zsh ? - иди найди (я нашёл... - не хочу).
В общем, что хотел не нашёл, а что нашёл не понравилось. Ну в конце концов имею на это право, всё таки делаю для себя и жажду развлечений в процессе.
Писать это дело конвенционным способом мне не захотелось от слова совсем, я даже на этой почве расстроился.
В общем встречаем Ragel State Machine Compiler.
Инструментарий
- gcc 😉
- ragel
- make
- lcov
- libcheck
С проектом можно ознакомиться тут: JOYFUL CMDLINE PARSER WRITTEN IN RAGEL
Постановка задачи
На входе имеем строку произвольного вида, задача получить из строки массив аргументов разделённых между собой сиволами пробела или табуляции, причем:
- Необходимо игнорировать любой символ следующий за символом экранирования
\
- Любые символы, который находятся между двумя парным
"
или'
должны
считаться одним элементом - В случае незакрытых
'
или"
должна возвращаться ошибка
В общем не так много условий. И Ragel вполне подходит для данной задачи.
Реализация с разъяснениями
Объявим машину с именем "buildargv" и попросим Ragel расположить свои данные в начале файла (5.8.1 Write Data).
%%{
machine buildargv;
write data;
}%%
Далее объявим машину lineElement
, которая в свою очередь состоит из объединения (2.5.1 Union) двух машин: arg
и whitespace
.
lineElement = arg >start_arg %end_arg | whitespace;
main := blineElements**;
На входе и выходе машины arg
выполняются действия start_arg
и end_arg
соответсвенно.
action start_arg {
argv_s = p;
}
action end_arg {
nargv = (char**)realloc((argv), (argc_ + 1)sizeof(char));
(argv) = nargv;
(*argv)[argc_] = strndup(argv_s, p - argv_s);
argc_++;
}
Причем задача start_arg
сохранить позицию символа на входе, а задача end_arg
добавить новый элемент в массив argv
, в случае успешного выхода из машины arg
.
Теперь подробнее рассмотрим arg
.
arg = '\''> { fcall squote; } | '"'>{ fcall dquote; } | ( '\'>{fcall skip;} | ^[ \t"'] )+;
Он состоит из объединения трёх машин '
, "
и (\ | ^[ \t"'\])
, последняя в свою очередь это объединение \
и ^[ \t"'\]
соответвенно.
При нахождении символа '
мы вызываем squote
, "
вызываем dquote
, или если текущий символ равен \
вызываем skip
, который пропускает любой следующий за ним символ, и любой символ не 0x20
(пробел), 0x09
(табуляция), '
, "
или \
считается правильным.
Осталось рассмотреть совсем небольшую часть:
skip := any @{ fret; };
dquote := ( '\'>{ fcall skip; } | ^[] )+ :> ["] @{ fret; } @err(dquote_err);
squote := ( '\'>{ fcall skip; } | ^[] )+ :> ['] @{ fret; } @err(squote_err);
Со skip
мы уже разобрались, что делает ^['\\]
тоже не должно вызывать вопросов. А вот :>
это Entry-Guarded Concatenation
(4.2 Guarded Operators that Encapsulate Priorities) её смысл заключается в том, что машина ( '\\'>{ fcall skip; } | ^['\\] )+
завершает выполнение когда ["]
переходит в начальное состояние.
И наконец в случае ошибки конца строки при незакрытых ковычках вызываются dquote_err
и squote_err
для индикации и выставления соответсвующего кода ошибки.
action dquote_err {
ret = -1;
errsv = BUILDARGV_EDQUOTE;
}
action squote_err {
ret = -1;
errsv = BUILDARGV_ESQUOTE;
}
Генерация кода осуществляется командой:
ragel -e -L -F0 -o buildargv.c buildargv.rl
Со списком тестовых строк можно ознакомиться в test_cmdline.c
.
Заключение
Поставленная задача решена.
Быстрее ли получилось ? Сомневаюсь. Понятнее ? Если только вы знаток Ragel.
На абсолютизм не претендую, буду признателен за конструктивные замечания по коду Ragel.
Список материалов:
Adrian Thurston. Ragel State Machine Compiler. http://www.colm.net/files/ragel/ragel-guide-6.10.pdf