Вопрос в том, "Есть ли способ дать какой-то приоритет тому токену, который я хочу получить [match]?"
Есть несколько способов.
Первый способ это
- Перечислите правила токена, чтобы на первом месте было правило с наивысшим приоритетом.
Но это работает только тогда, когда оба правила соответствуют строкам одинаковой длины.
В вашем случае вы хотите, чтобы «1.this» соответствовало <LI>
, за которым следует <TEXT>
. Но текстовое правило будет соответствовать 6 символам, в то время как правило <LI>
будет соответствовать только 2. Таким образом, как объяснено в FAQ и ответе @ sarath, правило <TEXT>
победит.
Второй способ.
- Используйте лексические состояния, чтобы отключить правило, которое вы не хотите выигрывать.
Этот подход не подходит для вашей проблемы, потому что вам нужно, чтобы оба правила были активными одновременно.
Третий путь.
- Переписать правила, чтобы правила с более низким приоритетом не могли применяться к более длинной строке, когда применяется правило с более высоким приоритетом.
В вашем случае вы хотите, чтобы значение "1234.this" не совпадало с <TEXT>
, чтобы можно было применить правило <LI>
. Мы перепишем правило <TEXT>
, чтобы оно не распространялось ни на одну из этих строк
"1234.this"
"1234.thi"
"1234.th"
"1234.t"
"1234."
(На самом деле, мы могли бы разрешить последнюю строку, а затем упорядочить правила так, чтобы <LI>
был первым. Но я обнаружил, что также проще исключить последнюю строку.)
Новый токен будет называться <TEXT1>
, чтобы избежать путаницы. Таким образом, наше правило для <TEXT1>
будет соответствовать любой строке, которая
- соответствует
<TEXT>
и
- не соответствует
<LI>
и
- не имеет префикса, соответствующего
<LI>
.
Было бы очень хорошо, если бы JavaCC предоставил хороший синтаксис для такого рода ситуаций, но это не так. Мы должны найти регулярное выражение.
Способ, которым мне нравится получать регулярные выражения в подобных случаях, состоит в том, чтобы начать с распознавателя конечных состояний регулярного выражения (REFR), а затем извлечь из него регулярное выражение. Определение REFR приведено в наборе слайдов 1.2 здесь , как и алгоритм для превращения REFR в регулярное выражение.
Первый REFR для TEXT1
выглядит следующим образом.
0s ---P|S|L---> 1f
0s ---D-------> 2f
1f --P|S|L|D--> 1f
2f --D--------> 2f
2f --S|L------> 1f
L означает букву, D означает цифру. P означает период. S означает любой символ, отличный от точки, то есть знак "at". Есть 3 состояния: 0s, 1f и 2f. Конечные (т.е. принимающие) состояния имеют в своем имени букву "f". Начальное состояние имеет в своем имени "s".
Надеюсь, правильность машины очевидна.
В частности, обратите внимание, что после ввода D, DD, DDD и т. Д. Набор состояний, в которых может находиться машина, равен {2f}; и, если машина находится в состоянии 2f, а P является следующим символом, у машины нет состояния для перехода, и поэтому строка отклоняется. Таким образом, любая строка, которая соответствует LI
или имеет префикс, который соответствует LI
, будет отклонена.
Первым шагом является уменьшение количества принимающих состояний до 1 и обеспечение отсутствия исходящих переходов из принимающего состояния. (Мы также хотим убедиться, что нет входящих переходов в начальное состояние, но это уже имеет место.) Новая машина
0s ---P|S|L---> 1
0s ---D-------> 2
1 --P|S|L|D--> 1
1 --epsilon--> 3f
2 --D--------> 2
2 --S|L-----> 1
2 --epsilon--> 3f
Далее устранить состояние 2.
0s ---P|S|L----> 1
0s --D+(S|L)---> 1
0s ---D+-------> 3f
1 --P|S|L|D---> 1
1 --epsilon---> 3f
Объедините два перехода от 0 до 1
0s ---P|S|L|D+(S|L)----> 1
0s ---D+---------------> 3f
1 ---P|S|L|D----------> 1
1 ---epsilon----------> 3f
Устранить состояние 1.
0s ---(P|S|L|D+(S|L)) (P|S|L|D)*---> 3f
0s ---D+---------------------------> 3f
Объедините переходы, чтобы получить
(P|S|L|D+(S|L)) (P|S|L|D)* | D+
0s--------------------------------------> 3f
Наконец, мы посмотрим на RE, чтобы увидеть, что это действительно имеет смысл, и искать любые упрощения.
Мне это кажется правильным, и я не вижу способа упростить его.
И мы закончили.