JavaCC: Как можно исключить строку из токена? (A.k.a. понимание токен неоднозначность.) - PullRequest
2 голосов
/ 03 июня 2010

У меня уже было много проблем с пониманием того, как неоднозначно (или как-то вообще) можно обрабатывать неоднозначные токены в JavaCC. Давайте возьмем этот пример:

Я хочу разобрать инструкцию по обработке XML.

Формат: "<?" <target> <data> "?>": target - это имя XML, data может быть любым , за исключением ?>, поскольку это закрывающий тег.

Итак, давайте определим это в JavaCC:
(Я использую лексические состояния, в данном случае DEFAULT и PROC_INST)

TOKEN : <#NAME : (very-long-definition-from-xml-1.1-goes-here) >
TOKEN : <WSS : (" " | "\t")+ >   // WSS = whitespaces
<DEFAULT> TOKEN : {<PI_START : "<?" > : PROC_INST}
<PROC_INST> TOKEN : {<PI_TARGET : <NAME> >}
<PROC_INST> TOKEN : {<PI_DATA : ~[] >}   // accept everything
<PROC_INST> TOKEN : {<PI_END : "?>" > : DEFAULT}

Теперь часть, которая распознает инструкции обработки:

void PROC_INSTR() : {} {
(
    <PI_START>
    (t=<PI_TARGET>){System.out.println("target: " + t.image);}
    <WSS>
    (t=<PI_DATA>){System.out.println("data: " + t.image);}
    <PI_END>
) {}
}

Давайте проверим это с <?mytarget here-goes-some-data?>:

Цель распознается: "target: mytarget". Но теперь я получаю любимую ошибку синтаксического анализа JavaCC:

!!  procinstparser.ParseException: Encountered "" at line 1, column 15.
!!  Was expecting one of:
!!      

Ничего не найдено? Ничего не ожидал? Или что? Спасибо, JavaCC!

Я знаю, что мог бы использовать ключевое слово MORE JavaCC, но это дало бы мне всю инструкцию по обработке как один токен, поэтому мне пришлось бы еще раз анализировать / токенизировать его самостоятельно , Почему я должен делать это? Я пишу парсер, который не анализирует?

Проблема в том (я думаю): следовательно, <PI_DATA> распознает «все», мое определение неверно. Я должен сказать JavaCC распознавать «все, кроме ?>» как данные инструкции обработки.

Но как это сделать?

ПРИМЕЧАНИЕ. Я могу исключить только одиночные символы , используя ~["a"|"b"|"c"], я не могу исключить строки , например ~["abc"] или ~["?>"]. Еще одна замечательная анти-функция JavaCC.

Спасибо.

Ответы [ 2 ]

4 голосов
/ 08 февраля 2011

Слово о токенизаторе

Tokenizer (* TokenManager) соответствует как можно большему количеству вводимых символов. PI_DATA - это «~ []» (1 символ), поэтому он будет соответствовать любому вводимому символу , если , он не сможет найти более длинное соответствие. PI_END - это «?>» (2 символа), поэтому он всегда будет сопоставляться вместо PI_DATA. Эта часть вашей грамматики верна.

Неожиданный подозреваемый

Проблема на самом деле может исходить от ИМЕНИ. Вы не написали фактическое определение этого токена, поэтому я могу только делать предположения об этом. Если определение NAME слишком жадное , оно будет соответствовать слишком большому количеству входных символов в состоянии PROC_INST, и вы можете никогда не встретить PI_DATA или PI_END.

Остерегайтесь "(...) +" с пробелами или злом "(~ []) *", который съедает все до EOF.

Другие подозреваемые

Потенциальная проблема, которую я вижу, состоит в том, что PI_TARGET, вероятно, будет сопоставляться несколько раз, хотя можно ожидать, что PI_DATA будет сопоставляться. Еще раз, я могу только догадываться, потому что у меня нет определения ИМЯ.

Еще один момент, который вы можете уточнить, заключается в следующем: вы определяете токен WSS, но не используете его в состоянии PROC_INST. Это должно быть частью PI_DATA? Если нет, вы можете пропустить его.

Не злоупотребляйте токенизатором

Если вы обнаружите, что не можете заставить токенизатор подчиняться вам, вы можете вместо этого переместить сложную часть в анализатор. В вашем случае, вероятно, трудно провести различие между PI_TARGET и PI_DATA (как упомянуто выше).

Анализатор может ожидать данных PI после цели PI, в то время как токенизатор не может (или вряд ли) иметь ожидания от токена до следующего.

Еще одним преимуществом парсера является то, что вы даже можете написать код Java, который просматривает следующие токены и реагирует соответствующим образом. Это следует рассматривать как последнее средство, но может быть полезно, когда необходимо выполнить такие действия, как объединение нескольких токенов до общеизвестного. Это может быть то, что вы ищете здесь (с PI_END в качестве маркера терминатора ).

Наконец, трюк

Вот трюк, чтобы немного упростить вашу грамматику:

  1. Пропустите PI_START, но все же измените состояние на PROC_INST
  2. В PROC_INST определите PI_DATA как MORE (и переименуйте его в PI_DATA_CHAR, или просто не называйте его вообще)
  3. В PROC_INST удалите последние два символа из образа токена, введите PI_DATA и измените состояние на ПО УМОЛЧАНИЮ
  4. В ваших продуктах синтаксического анализа определите инструкцию обработки просто как, где образ токена PI_DATA готов к использованию

Подробная информация об управлении изображением токена в действиях токенизатора приведена в документации JavaCC (sparse ...). Это так же просто, как установить длину StringBuffer.

0 голосов
/ 04 мая 2013

Одна проблема с вашей грамматикой в ​​том, что WSS применяется только в состоянии по умолчанию. Переписать как

<DEFAULT, PROC_INST> TOKEN : {< WSS: (" " | "\t")+ > \}

Сообщение об ошибке состоит в том, что он ожидал WSS, но обнаружил "".

Что касается исключения целых строк, есть несколько способов сделать это, изложенные в FAQ.

...