I am using Parsec as my parsing library and quite liking it: though, I am unsure on how to properly tackle a 'free element' problem. Let me explain: In my file, there are some elements which look like this: this is some <red - formatted> text. I call this a FormatString. They can be found inside many elements, like: [ someconditions | this is some <red - formatted> text.] + this is some <red - formatted> text. -> somenumberhere To parse a FormatString I call 'generalText', which looks like this: 56 -- TEXT -- 57 58 generalText = many anyText >>= \fss -> 59 return (foldl (+:+%) (toFString "" []) fss) 60 61 anyText = try ( formattedText ) 62 <|> plainText 63 64 formattedText = char '<' >> 65 sepEndBy1 format spacebar >>= \fs -> -- format parser 66 string "- " >> 67 manyTill anyChar (char '>') >>= \cs -> 68 return (toFString cs fs) 69 70 plainText = many1 (noneOf "<") >>= \xs -> 71 return (toFString xs []) It works, but causes quite some headaches in using it: - I would like to call a "read with generalText until you find a ']' character. - or a "read with generalText until you find a " -> " (do not consume it) And the like, but I do not know how. If it were a plain string I would call manyTill1 anyChar myP. I thought of first parsing a raw string searching for a terminator and then feeding it to generalText. But isn't that cluncky codewise? Thanks for your help -F
Hi Franco The best "solution" is really to work out a grammar of text strings and write simpler productions that handle it. Otherwise you can treat it as a "lexing" problem but then the results get messy as you have found out. It's a bit late in the UK and I though I've looked at the code I haven't worked out an answer yet, I'll have a proper look tomorrow if no one else has answered but here is my first step, this is a "lexing" solution but written directly rather than with Parsec. It is easier to write a "lexing" solution this as two mutually recursive functions for the lexer states - consuming free text, or consuming a format string. data Text1 = FreeText String | Formatted String deriving (Eq,Ord,Show) type Text = [Text1] -- The type of /accumulator/. type Acc = ShowS -- We want to grow Strings from the right. snoc :: Acc -> Char -> Acc snoc ss c = ss . (c:) toString :: Acc -> String toString = ($ "") empty :: Acc empty = id runText :: String -> Text runText = text empty -- Minor problem - generates empty FreeText if the accumulator is -- empty, this can be easily fixed at some loss of clarity. -- text :: Acc -> String -> Text text ac [] = [FreeText (toString ac)] text ac ('<':cs) = FreeText (toString ac) : formatted empty cs text ac (c:cs) = text (ac `snoc` c) cs formatted :: Acc -> String -> Text formatted _ [] = error "missing terminator for formatting" formatted ac ('>':cs) = Formatted (toString ac) : text empty cs formatted ac (c:cs) = formatted (ac `snoc` c) cs demo01 = runText "[ someconditions | this is some <red - formatted> text.]"
Hi Franco Actually the simple case of finding formatting tags in free text was easier in Parsec than my email last night suggested. Perhaps it is artificially easy because you can identify tag start and ends with a single character so you can use `satisfy`. import Text.Parsec import Text.Parsec.String import Control.Applicative hiding ( (<|>), many ) data Text1 = FreeText String | Formatted String deriving (Eq,Ord,Show) type Text = [Text1] runText :: String -> Either ParseError Text runText = runP lexer () "no-input" notLAngle :: Char -> Bool notLAngle = (/= '<') notRAngle :: Char -> Bool notRAngle = (/= '>') lexer :: Parser Text lexer = many (formatted <|> free) formatted :: Parser Text1 formatted = Formatted <$> between (char '<') (char '>') (many1 (satisfy notRAngle)) free :: Parser Text1 free = FreeText <$> many1 (satisfy notLAngle) demo01 = runText "[ someconditions | this is some <red - formatted> text.]" demo02 = runText "<red - formatted> more text."
Indeed the second solution is more elegant (and the examples very simple to follow, they should be added to Parsec's documentation!). For the records, before reading this I was using the ductape solution below: 161 162 -- take a string till t, on that runs parser p. 163 -- The last parameters sets wheter t will be consumed or 164 -- not. FILE HANDLING FOR ERRORS? 165 parseTill :: Parser a -> Parser b -> Parser b 166 parseTill ter p = manyTill anyChar ter >>= \sndPar -> 167 case parse p "" sndPar of 168 Left a -> fail "todo: check how error msg are propagted" 169 Right b -> return b Thanks again -F On Sun, 11 Mar 2012 10:53:50 +0000 Stephen Tetley <stephen.tetley@gmail.com> wrote:
Hi Franco
Actually the simple case of finding formatting tags in free text was easier in Parsec than my email last night suggested. Perhaps it is artificially easy because you can identify tag start and ends with a single character so you can use `satisfy`.
import Text.Parsec import Text.Parsec.String import Control.Applicative hiding ( (<|>), many )
data Text1 = FreeText String | Formatted String deriving (Eq,Ord,Show)
type Text = [Text1]
runText :: String -> Either ParseError Text runText = runP lexer () "no-input"
notLAngle :: Char -> Bool notLAngle = (/= '<')
notRAngle :: Char -> Bool notRAngle = (/= '>')
lexer :: Parser Text lexer = many (formatted <|> free)
formatted :: Parser Text1 formatted = Formatted <$> between (char '<') (char '>') (many1 (satisfy notRAngle))
free :: Parser Text1 free = FreeText <$> many1 (satisfy notLAngle)
demo01 = runText "[ someconditions | this is some <red - formatted> text.]" demo02 = runText "<red - formatted> more text."
-- Franco <franco00@gmx.com>
participants (2)
-
Franco -
Stephen Tetley