Стратегия Parsec - это, по сути, LL (1), что означает, что он «фиксирует» текущую ветвь всякий раз, когда используется какой-либо вход. Ваш синтаксический анализатор openTag
потребляет <
с его char '<'
, что означает, что, если он видит >
вместо /
, весь анализ завершается неудачей вместо попытки нового выбора. Если openTag
не потребляет никакого ввода и потерпит неудачу, будет выбран другой вариант. Parsec делает это для эффективности (альтернатива - экспоненциальное время!) И для разумных сообщений об ошибках.
У вас есть два варианта. Предпочтительный вариант, когда это целесообразно, состоит в том, чтобы учесть грамматику так, чтобы все варианты выполнялись без затрат на ввод, например ::
tag = word <|> (char '<' >> tagbody)
where
tagbody = do
content <- tagcontent
choice [ string "/>", char '>' ]
Ошибки и стиль по модулю (мой мозг сейчас немного жарен :-P).
Другой способ, который локально изменяет семантику parsec (за счет вышеупомянутых сообщений об ошибках и эффективности - но обычно это не так уж и плохо, поскольку он локальный), - использовать комбинатор try
, который позволяет анализатору потреблять ввод и по-прежнему не «мягко», поэтому можно попробовать другой выбор:
nulltag = try $ between (char '<') (string "/>") word
-- etc.
Иногда использование try проще и проще, чем факторинг, как описано выше, что может скрыть «глубокую структуру» языка. Это стилистический компромисс.