Haskell Parsec eine Reihe von Elementen Parsen
Frage
Ich habe eine Liste, die ich brauche zu analysieren, wo die alle, aber das letzte Element Bedürfnisse von einem Parser analysiert werden, und das letzte Element Bedarf durch einen anderen Parser analysiert werden.
a = "p1 p1b ... p2"
or
a = "p2"
Ursprünglich habe ich versucht,
parser = do parse1 <- many parser1
parse2 <- parser2
return AParse parse1 parse2
Das Problem ist, dass Parse1 einen parse2 Eingang verbrauchen kann. So Parse1 verbraucht immer die gesamte Liste und lassen parse2 mit nichts.
Gibt es eine Möglichkeit zu sagen, Parse1 alles außer dem letzten Element in einen String anwenden, und dann parse2 anwenden?
Lösung
Wie wäre:
parseTrain car caboose = choice
[ fmap (:[]) $ try (caboose `endBy` eof),
, liftM2 (:) car (parseTrain car caboose)
[
Die eof nervt mich, denn das macht diesen Parser nicht kompositorische. D. h Sie konnte nicht sagen:
char '(' >> parseTrain p1 p2 >> char ')'
Dieses compsitionally zu tun, ist sehr schwer für einen Parser. Wie ist es denn zu wissen, auf dem Weg zu char ‚)‘, ohne bei jeder Gelegenheit zu versuchen und zu sehen, wenn es scheitert? Andernfalls kann es exponentieller Zeit.
Wenn Sie es brauchen kompositorischen zu sein, ist Ihr Problem einige zusätzliche Struktur haben Sie nutzen können? Können Sie zum Beispiel eine Liste aller Elemente analysieren und dann die letzten nach der Tat verarbeiten?
Andere Tipps
Wenn Sie Faktor parser1
so dass wie so definiert sind:
parser1 = (try parser2) <|> parser1extra
Dann wird das Problem eine Liste von parser1extra
oder parser2
, die in der später enden. Sie können Code, wie:
parserList =
liftM2 (:) (try parser1extra) parserList
<|>
liftM2 (:) (try parser2) (option [] parserList)
Sie können oder müssen nicht die try
Anrufe je nachdem, ob diese Parser haben keine Präfix überlappen.
Wenn Sie nicht den Rückgabewert sollen eine Liste sein, sondern Ihr AParse Datum, dann könnten Sie neu zu schreiben es auf diese Weise:
parserList =
do
a <- try parser1extra
prefix a parserList
<|>
do
a <- try parser2
option (AParse [] a) (prefix a parserList)
where prefix a p = do
(AParse as t) <- p
return $ (AParse (a:as) t)
Oder ein vollständiges Beispiel:
import Control.Monad
import Text.ParserCombinators.Parsec
parseNum = do { v <- many1 digit; spaces; return v }
parseWord = do { v <- many1 letter; spaces; return v }
parsePart = parseNum <|> parseWord
parsePartListEndingInWord =
liftM2 (:) (try parseNum) parsePartListEndingInWord
<|>
liftM2 (:) (try parseWord) (option [] parsePartListEndingInWord)
Eigentlich werden die Anrufe zu versuchen, in diesem Fall nicht erforderlich, da parseNum
und parseWord
Spiel keine gemeinsamen Präfix. Beachten Sie, dass parsePartListEndingInWord
nicht wirklich Referenz parsePart
, sondern die beiden Optionen, die parsePart
Definition
(Original Antwort, eine etwas andere Situation zu lösen:)
Wie wäre es so etwas wie:
parserTest = between (char '[') (char ']') $ do
p1s <- try parser1 `endBy` char ','
p2 <- parser2
return $ AParse p1s p2
Unter der Interpunktion aus Ihrem Parsern und bis in parseTest können Sie die combinators between
und endBy
verwenden die Arbeit für Sie zu tun. Schließlich ist die try
ist es so, dass, wenn parser1
und parser2
einen gemeinsamen Präfix übereinstimmen, endBy
die korrekte vollständige Sicherung durchführen wird von dem gemeinsamen Präfix beginnen.
Je nach Parsern ist es möglich, dass Sie die Zeichensetzung Anpassung in Ihrem Unter Parsern verlassen können, und alle müssen Sie vielleicht die eine try
um parser1
:
parseTest = do parse1 <- many (try parser1)
parse2 <- parser2
return AParse parse1 parse2
I kombinierter Art der zwei Ansätze:
parserList = try (do a <- parser2
eof
return $ AParse [] a)
<|>
do a <- parser1
prefix a parserList
where
prefix a p = do
(AParse as t) <- p
return $ AParse a:as t
ich glaube, dass dies für meine Zwecke arbeiten. Dank!
Dies wird den Trick tun:
parser1 `manyTill` (try parser2)