Существует два основных решения этой проблемы:
- Определите действие, чтобы его можно было безопасно выполнить несколько раз,
- Измените синтаксис, чтобы действие выполнялось толькоодин раз.
В этом случае я бы выбрал гибридный подход.Используйте действия для записи начальной и конечной позиции name
: эти действия можно безопасно выполнять много раз, поскольку они просто записывают местоположения.Как только вы убедитесь, что вы вышли из имени, выполните другое действие, которое будет выполнено только один раз.
/* C code */
char *name_start, *name_end;
/* Ragel code */
action markNameStart { name_start = p; }
action markNameEnd { name_end = p; }
action nameAction {
/* Clumsy since name is not nul-terminated */
fputs("Name = ", stdout);
fwrite(name_start, 1, name_end - name_start, stdout);
fputc('\n', stdout);
}
name = space* %markNameStart
(alnum+ %markNameEnd <: space*)+
%nameAction ;
main := name ":" name ;
Здесь синтаксис для name
включает произвольные пробелы и хотя бы один буквенно-цифровой символ.Когда встречается первый буквенно-цифровой символ, его местоположение сохраняется в name_start
.Всякий раз, когда выполнение буквенно-цифровых символов заканчивается, местоположение следующего символа сохраняется в name_end
.<:
технически не нужен, но он уменьшает частоту выполнения действия * 1017. *
Только убедитесь, что такое выражение не ставится рядом с пробелами.
IВы не проверяли приведенный выше код. Вы должны посмотреть на визуализацию конечного автомата Graphviz перед использованием.
Что делает Ragel
С вашим исходным кодом, давайте предположим, что вводвыглядит следующим образом:
Hello world : Goodbye world
Машина Ragel сканирует слева направо, находит начало name
и просматривает буквенно-цифровые символы.
Hello world : Goodbye world
↑
Следующий символэто пространство.Так что либо мы встретили пробел внутри слова, либо первый пробел после конца слова.Как выбирает Ragel?
Ragel выбирает оба варианта одновременно. Это очень важно.Ragel пытается симулировать недетерминированный конечный автомат, но поскольку ваш компьютер является детерминированным, самый простой способ сделать это - преобразовать NFA в DFA, который параллельно имитирует неограниченное количество NFA.Поскольку NFA имеют конечное число состояний (отсюда и название), DFA также имеют конечное число состояний, поэтому этот метод работает.
После обнаружения пространства у вас есть один NFA в следующем состоянии,ищет остальную часть name
:
identifier = alnum (space* alnum)*;
↑
main := name sep name;
↑
Второй NFA находится в следующем состоянии и предполагает, что name
уже завершен (и этот NFA выполняет действие fName
"преждевременно "):
sep = space* ":" space*;
↑
main := name sep name;
↑
Это очевидно для вас и для меня очевидно, что только первый NFA является правильным.Но машины, созданные с помощью Ragel, смотрят только на одного персонажа за раз, они не смотрят в будущее, чтобы увидеть, какой вариант правильный.Второй NFA в конечном итоге встретится с буквенно-цифровым символом, где он ожидал увидеть ":"
, и поскольку это не разрешено, второй NFA исчезнет.
Посмотрите документацию по Ragel
Вотописание %
:
expr % action
Оператор оставляющего действия ставит в очередь действие для встраивания в переходы, которые выходят из машины через конечное состояние.
Действие выполняется для переходов, которые не обязательно способствуют успешному анализу.Обратитесь к руководству по Ragel, глава 4, «Управление недетерминизмом» для получения дополнительной информации о недетерминизме в Ragel, хотя методы в главе 4 не помогут вам в этом конкретном случае, поскольку действия на вашем компьютере могут быть устранены только с помощью несвязанного просмотра,не допускается в конечных автоматах.