Если я пытаюсь разобрать следующее в lines
и fields
. Строки ограничены '\n'
, а поля - '|'
.
abcd|efgh|ijkl
mnopq\|rst|uvwxy
za|bcd
efg|hijk|lmnop
Я могу определить следующее:
let displayCharacter = satisfy (fun c -> ' ' <= c && c <= '~')
let escapedDC = pchar '\\' >>. displayCharacter
let test1 =
run (manyChars (escapedDC <|> displayCharacter)) "asdf\|efgh|ijkl"
// Success: "asdf|efgh|ijkl"
Но let fields = sepBy (manyChars (escapedDC <|> displayCharacter)) (pchar '|')
не может работать, чтобы исключить '|'
из поля. Эти разделители являются контекстно-зависимыми, поэтому я хочу избежать жесткого кодирования их в displayCharacter
, поскольку '|'
является отображаемым символом, но в некоторых контекстах может потребоваться экранирование.
Если я попытаюсь определить один field
с помощью manyCharsTill
, то мне нужно учесть конечный элемент в строке с anyOf "|\n"
, но это будет читать все строки в одну line
.
Возможно, у меня есть и другие дополнительные ограничители, кроме '|'
, которые поддерживаются в определенных контекстах. По этой причине кажется беспорядочным определять версии displayCharacter и escapedDC для каждого случая. Скорее, использование опережающих функций выглядит чище. Или, возможно, синтаксический анализатор с именем both
, который так или иначе требует совпадения для двух анализаторов одновременно.
manyCharsSepBy (escapedDC <|> displayCharacter) (pchar '|')
или
let contextualDisplayCharacter1 = both displayCharacter (satisfy ((<>) '|'))
Есть ли более простой способ сделать это? Возможно, это только мой подразумеваемый BNF, который ошибочен - что если исправить, легко переведет?
============
Это лучшее, что я могу придумать, но я бы хотел узнать от экспертов, является ли это наиболее гибким способом.
let displayCharacter (excludeDelimiters : string) = satisfy (fun c -> ' ' <= c && c <= '~' && not (Seq.exists ((=) c) excludeDelimiters))
let escapedDisplayCharacter = pchar '\\' >>. displayCharacter ""
let field =
manyChars (escapedDisplayCharacter <|> displayCharacter "|")