title: Parsing string with parsec that needs to end with particular words?
body: <p>I am working on some programming exercises. The one I am working on has following input format:</p>
  
  <pre><code>Give xxxxxxxxx as yyyy.
  </code></pre>
  
  <p>xxxxxxxx can be in several formats that repeatedly show up during these exercises.  In particular its either binary (groups of 8 separated by spaces),  hexadecimal (without spaces) or octal (groups of up to 3 numbers).  I have already written parsers for these formats - however they all stumble over the "as". They looked like this</p>
  
  <pre><code>binaryParser = BinaryQuestion  &lt;$&gt; (count 8 ( oneOf "01") ) `sepBy1` space
  </code></pre>
  
  <p>I solved using this monstrosity (trimmed unnecessary code)</p>
  
  <pre><code>{-# LANGUAGE OverloadedStrings #-}
  import Text.Parsec.ByteString
  import Text.Parsec
  import Text.Parsec.Char
  import Data.ByteString.Char8 (pack, unpack, dropWhile, drop, snoc)
  import qualified Data.ByteString as B 
  
  data Input = BinaryQuestion [String] 
             | HexQuestion [String]
             | OctalQuestion [String]
    deriving Show
  data Question = Question {input :: Input, target :: Target} deriving Show
  data Target = Word deriving Show
  
  test1 :: B.ByteString
  test1 = "Give 01110100 01110101 01110010 01110100 01101100 01100101 as a word."
  test2 :: B.ByteString
  test2 = "Give 646f63746f72 as a word."
  test3 :: B.ByteString
  test3 = "Give 164 151 155 145 as a word."
  
  targetParser :: Parser Target
  targetParser = string "word" &gt;&gt; return Word
  
  wrapAs :: Parser a -&gt; Parser [a]
  wrapAs kind = manyTill kind (try (string " as"))
  inputParser :: Parser Input
  inputParser = choice [try binaryParser, try (space &gt;&gt; hexParser), try octParser]
  binaryParser :: Parser Input
  binaryParser = BinaryQuestion  &lt;$&gt; wrapAs (space &gt;&gt; count 8 ( oneOf "01") )
  hexParser :: Parser Input
  hexParser = HexQuestion &lt;$&gt; wrapAs (count 2 hexDigit)
  octParser :: Parser Input
  octParser = OctalQuestion  &lt;$&gt; wrapAs (many1 space &gt;&gt; many1 (oneOf ['0'..'7']))
  
  questionParser :: Parser Question
  questionParser = do
    string "Give"
    inp &lt;- inputParser 
    string " a "
    tar &lt;- targetParser
    char '.'
    eof
    return $ Question inp tar
  </code></pre>
  
  <p>I don't like that I need to use the following string "as" inside the parsing of Input, and they generally are less readable.  I mean using regex it would be trivial to have a trailing string.  So I am not satisfied with my solution.</p>
  
  <p>Is there a way I can reuse the 'nice' parsers - or at least use more readable parsers?</p>
  
  <p><strong>additional notes</strong></p>
  
  <p>The code I along the lines I wish I could get working would look like this:</p>
  
  <pre><code>{-# LANGUAGE OverloadedStrings #-}
  
  import Text.Parsec.ByteString
  import Text.Parsec
  import Text.Parsec.Char
  import Data.ByteString.Char8 (pack, unpack, dropWhile, drop, snoc)
  import qualified Data.ByteString as B 
  
  data Input = BinaryQuestion [String] 
             | HexQuestion [String]
             | OctalQuestion [String]
    deriving Show
  data Question = Question {input :: Input, target :: Target} deriving Show
  data Target = Word deriving Show
  
  test1 :: B.ByteString
  test1 = "Give 01110100 01110101 01110010 01110100 01101100 01100101 as a word."
  test2 :: B.ByteString
  test2 = "Give 646f63746f72 as a word."
  test3 :: B.ByteString
  test3 = "Give 164 151 155 145 as a word."
  
  targetParser :: Parser Target
  targetParser = string "word" &gt;&gt; return Word
  
  inputParser :: Parser Input
  inputParser = choice [try binaryParser, try hexParser, try octParser]
  binaryParser :: Parser Input
  binaryParser = BinaryQuestion  &lt;$&gt; count 8 ( oneOf "01") `sepBy1` space
  hexParser :: Parser Input
  hexParser = HexQuestion &lt;$&gt; many1 (count 2 hexDigit)
  octParser :: Parser Input
  octParser = OctalQuestion  &lt;$&gt;  (many1 (oneOf ['0'..'7'])) `sepBy1` space
  
  questionParser :: Parser Question
  questionParser = do
    string "Give"
    many1 space
    inp &lt;- inputParser 
    many1 space
    string "as a"
    many1 space
    tar &lt;- targetParser
    char '.'
    eof
    return $ Question inp tar
  </code></pre>
  
  <p>but <code>parseTest questionParser test3</code> will return me <code>parse error at (line 1, column 22):
  unexpected "a"</code></p>
  
  <p>I suppose the problem is that space is used as separator inside the input but also comes in the <code>as a</code> string.  I don't see any function inside parsec that would fit.  In frustration I tried adding <code>try</code> in various places - however no success.</p>