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 Answer 1

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\$
2
  • \$\begingroup\$ thanks. Why do you uncurry your yield rather than making it accept a pair right away? \$\endgroup\$
    – akonsu
    Commented Jul 13, 2015 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
    Commented Jul 13, 2015 at 17:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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