3
\$\begingroup\$

I am trying to parse escape sequences as well as plain characters. Can this be made more succinct?

import Control.Applicative
import Data.Char
import Numeric
import Text.Parsec hiding ((<|>))

echar :: Parsec String () Char
echar = (char '\\' >>
         ((char 'b' >> return '\b')
          <|> (char 'f' >> return '\f')
          <|> (char 'n' >> return '\n')
          <|> (char 'r' >> return '\r')
          <|> (char 't' >> return '\t')
          <|> (char 'u' >> count 4 hexDigit >>= return . chr . fst . head . readHex)
          <|> (char 'v' >> return '\v')
          <|> (noneOf "u")))
        <|> noneOf "\\"
\$\endgroup\$
1
\$\begingroup\$

You could cut down on the repetitive elements with a local function binding.

echar =
  let
    yield :: Char -> Char -> Parsec String () Char
    yield c d = char c >> return d
  in
    (char '\\' >>
       (  yield 'b' '\b'
      <|> yield 'f' '\f'
       -- ...

Then perhaps cut out the repeated applications with a fold.

import Data.Foldable

-- ...
  in
    (char '\\' >>
       (  (asum . map (uncurry yield) $ [('b', '\b'), ('f', '\f') -- ...
      <|> (char 'u' -- ...

I'm not sure you need the Numeric import either, I would write that line as—

char 'u' >> count 4 hexDigit >>= return . chr . read . ("0x" ++)

And then to tie it all together with a bow.

import Control.Applicative        ((<|>))
import Data.Char                  (chr)
import Data.Foldable              (asum)
import Text.Parsec         hiding ((<|>))

echar :: Parsec String () Char
echar =
  let
    escapeCharacters = [('b', '\b'), ('f', '\f'), ('r', '\r'), ('t', '\t'), ('v', '\v')]

    yield :: Char -> Char -> Parsec String () Char
    yield c d = char c >> return d
  in
    (char '\\' >>
       (  (asum . map (uncurry yield) $ escapeCharacters)
      <|> (char 'u' >> count 4 hexDigit >>= return . chr . read . ("0x" ++))
      <|> (noneOf "u")
       )
    ) <|> noneOf "\\"
\$\endgroup\$
  • \$\begingroup\$ thanks. Why do you uncurry your yield rather than making it accept a pair right away? \$\endgroup\$ – akonsu Jul 13 '15 at 13:35
  • \$\begingroup\$ 1) Curried functions are the default. 2) You might raise yield to the top-level and use it elsewhere, where the uncurried version doesn't make sense. 3) It just feels right. \$\endgroup\$ – R B Jul 13 '15 at 17:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.