HiveBrain v1.2.0
Get Started
← Back to all entries
patternMinor

Multiple base numbers parser

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
parsernumbersmultiplebase

Problem

This code is a Parser that parses numbers according to R5RS.

  • #b1001 - binary



  • #o2127 - octal



  • #h02d - hexadecimal



  • #d1231 - decimal



  • 3923 - decimal



It is working at the moment, the only problem is the parseNumberBase. I am really new to haskell, but it does not look very good to me.

  • How could I improve it? (readability wise)



  • It would also be nice to see a more "idiomatic" approach



import Data.Char (digitToInt)
import Numeric (readInt, readOct, readHex)
import Data.Maybe (listToMaybe, fromJust)

parseNumber :: Parser LispVal
parseNumber = parseNumberBase 'd'
           do char '#'
                 base  Parser LispVal
parseNumberBase 'b' =
    do digits  Maybe Integer
readBinary =
    fmap fst . listToMaybe . readInt 2 (`elem` "01") digitToInt

Solution

do
   digits <- someParser
   return $ someFunction digits


is just

someFunction  someParser


We can make parseNumberBase a lot shorter with that information:

-- | Parses a number at a specific base
parseNumberBase :: Char -> Parser LispVal
parseNumberBase 'b' = Number . fromJust . readBinary  many1 (oneOf "01")
parseNumberBase 'o' = Number . fst . head . readOct   many1 octDigit     -- see below
parseNumberBase 'd' = Number . read                   many1 digit
parseNumberBase 'h' = Number . fst . head . readHex   many1 hexDigit     -- see below
parseNumberBase _   = error "Wrong number base"


readFunc digits !! 0 is head (readFunc digits), so we were able to get rid of !! too.

That being said, parseNumberBase has a lot of responsibility. Split it into multiple parsers, and it's suddenly a lot easier to grasp:

parseBinary :: Parser LispVal
parseBinary = Number . fromJust . readBinary  many1 (oneOf "01")

parseDecimal :: Parser LispVal
parseDecimal = Number . read  many1 digit

parseHexadecimal :: Parser LispVal
parseHexadecimal = Number . fst . head . readHex  many1 hexDigit

parseOct :: Parser LispVal
parseOct = Number . fst . head . readOct   many1 octDigit


We can now test all those functions in isolation. We can now write parseNumberBase as another parser:

parseNumber :: Parser LispVal
parseNumber = parseDecimal  char '#' *> parseNumberBase

parseNumberBase :: Parser LispVal
parseNumberBase =  char 'h' *> parseHexadecimal 
                char 'd' *> parseDecimal 
                char 'o' *> parseOct
                char 'b' *> parseBinary

Code Snippets

do
   digits <- someParser
   return $ someFunction digits
someFunction <$> someParser
-- | Parses a number at a specific base
parseNumberBase :: Char -> Parser LispVal
parseNumberBase 'b' = Number . fromJust . readBinary <$> many1 (oneOf "01")
parseNumberBase 'o' = Number . fst . head . readOct  <$> many1 octDigit     -- see below
parseNumberBase 'd' = Number . read                  <$> many1 digit
parseNumberBase 'h' = Number . fst . head . readHex  <$> many1 hexDigit     -- see below
parseNumberBase _   = error "Wrong number base"
parseBinary :: Parser LispVal
parseBinary = Number . fromJust . readBinary <$> many1 (oneOf "01")

parseDecimal :: Parser LispVal
parseDecimal = Number . read <$> many1 digit

parseHexadecimal :: Parser LispVal
parseHexadecimal = Number . fst . head . readHex <$> many1 hexDigit

parseOct :: Parser LispVal
parseOct = Number . fst . head . readOct  <$> many1 octDigit
parseNumber :: Parser LispVal
parseNumber = parseDecimal <|> char '#' *> parseNumberBase

parseNumberBase :: Parser LispVal
parseNumberBase =  char 'h' *> parseHexadecimal 
               <|> char 'd' *> parseDecimal 
               <|> char 'o' *> parseOct
               <|> char 'b' *> parseBinary

Context

StackExchange Code Review Q#118397, answer score: 2

Revisions (0)

No revisions yet.