You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
172 lines
6.3 KiB
172 lines
6.3 KiB
7 years ago
|
{-# LANGUAGE OverloadedStrings #-}
|
||
|
|
||
|
module PhoneNumber where
|
||
|
|
||
|
import Control.Applicative ((<|>))
|
||
|
import Text.Trifecta
|
||
|
import Test.Hspec
|
||
|
|
||
|
-- This is an exercise for the Belgian Phone numbers system
|
||
|
-- From wikipedia
|
||
|
{-
|
||
|
Belgian telephone numbers consist of two major parts: Firstly '0', secondly the
|
||
|
"zone prefix" (A) which is 1 or 2 digits long for landlines and 3 digits long
|
||
|
for mobile phones and thirdly the "subscriber's number" (B).
|
||
|
|
||
|
Land lines are always 9 digits long. They are prefixed by a zero, followed by
|
||
|
the zone prefix. Depending on the length of the zone prefix, the subscriber's
|
||
|
number consists of either 6 or 7 digits. Hence land line numbers are written
|
||
|
either 0AA BB BB BB or 0A BBB BB BB.
|
||
|
|
||
|
Mobile Phone numbers always consist of 10 digits. The first digit of the "zone
|
||
|
prefix" of a mobile number is always '4'. Then follows 2 digits indicating to
|
||
|
which Mobile Operator's pool the number originally belonged when it was taken
|
||
|
into usage. The fourth digit represents a "sub-group" of this pool and has no
|
||
|
additional meaning other than increasing the amount of possible numbers. The
|
||
|
subscriber's number consists of 6 digits. Hence, mobile phone numbers are
|
||
|
written 04AA BB BB BB. Sometimes, the last 6 digits are written in two groups
|
||
|
of 3 digits to increase readability: 04AA BBB BBB.
|
||
|
|
||
|
Numbers are sometimes written with a slash in between the zone prefix and the
|
||
|
subscriber's number. This is the case for both land lines and mobile phone
|
||
|
numbers. Sometimes, dots are written between the blocks of the subscriber's
|
||
|
number. Examples: 0AA/BB BB BB, 0AA/BB.BB.BB; for mobile numbers:
|
||
|
04AA/BB BB BB, 04AA/BB.BB.BB or 04AA/BBB.BBB.
|
||
|
|
||
|
The international country code prefix for Belgium is "+32". When dialing a
|
||
|
number with the prefix, the 0 can be dropped, e.g.: +32 4AA BB BB BB.
|
||
|
-}
|
||
|
|
||
|
-- see also https://en.wikipedia.org/wiki/Telephone_numbers_in_Belgium
|
||
|
|
||
|
|
||
|
-- Will only look at landlines and phonelines
|
||
|
-- not special numbers or non-geographic numbers
|
||
|
data ZonePrefix = MobileZP String
|
||
|
| LandOneZP String
|
||
|
| LandTwoZP String
|
||
|
deriving (Eq)
|
||
|
instance Show ZonePrefix where
|
||
|
show (MobileZP s) = "0" ++ s
|
||
|
show (LandOneZP s) = " 0" ++ s
|
||
|
show (LandTwoZP s) = " 0" ++ s
|
||
|
|
||
|
data SubscribersNumber = MobileSN String
|
||
|
| LandOneSN String
|
||
|
| LandTwoSN String
|
||
|
deriving (Eq)
|
||
|
instance Show SubscribersNumber where
|
||
|
show (LandOneSN (a:b:c:d:e:f:g:[])) = a:b:c:" " ++ d:e:" " ++ f:g:[]
|
||
|
show (LandOneSN s) = "Invalid format: " ++ s
|
||
|
show (MobileSN (a:b:c:d:e:f:[])) = " " ++ a:b:" " ++ c:d:" " ++ e:f:[]
|
||
|
show (MobileSN s) = "Invalid format: " ++ s
|
||
|
show (LandTwoSN (a:b:c:d:e:f:[])) = " " ++ a:b:" " ++ c:d:" " ++ e:f:[]
|
||
|
show (LandTwoSN s) = "Invalid format: " ++ s
|
||
|
|
||
|
data PhoneNumberBE = PhoneNumberBE ZonePrefix SubscribersNumber deriving Eq
|
||
|
instance Show PhoneNumberBE where
|
||
|
show (PhoneNumberBE z s) = "(PhoneNumberBE " ++ show z
|
||
|
++ " "
|
||
|
++ show s
|
||
|
++ ")"
|
||
|
|
||
|
|
||
|
-- Mobile numbers are: 046x 047x 048x 049x
|
||
|
-- Land lines are most other things where:
|
||
|
-- 02,03,04 and 09 are single digit land line area codes
|
||
|
-- the rest are two digit land line phone
|
||
|
parseZonePrefix :: Parser ZonePrefix
|
||
|
parseZonePrefix =
|
||
|
let mobile = MobileZP <$> do
|
||
|
one <- char '4'
|
||
|
two <- oneOf ['6', '7', '8', '9']
|
||
|
three <- digit
|
||
|
return $ [one, two, three]
|
||
|
landOne = LandOneZP . (:[]) <$> oneOf ['2', '3', '4', '9']
|
||
|
landTwo = LandTwoZP <$> count 2 digit
|
||
|
-- Order is important, we want to check mobile first, followed by
|
||
|
-- landOne. Everything else is assumed to be land two
|
||
|
in mobile <|> landOne <|> landTwo
|
||
|
|
||
|
parseMobileSN :: Parser SubscribersNumber
|
||
|
parseMobileSN = do
|
||
|
let p = count 2 digit
|
||
|
skip = skipOptional $ char ' ' <|> char '.'
|
||
|
p1 <- p
|
||
|
skip
|
||
|
p2 <- p
|
||
|
skip
|
||
|
p3 <- p
|
||
|
return . MobileSN $ p1 ++ p2 ++ p3
|
||
|
|
||
|
parseLandTwoSN :: Parser SubscribersNumber
|
||
|
parseLandTwoSN = do
|
||
|
let p = count 2 digit
|
||
|
skip = skipOptional $ char ' ' <|> char '.'
|
||
|
p1 <- p
|
||
|
skip
|
||
|
p2 <- p
|
||
|
skip
|
||
|
p3 <- p
|
||
|
return . LandTwoSN $ p1 ++ p2 ++ p3
|
||
|
|
||
|
parseLandOneSN :: Parser SubscribersNumber
|
||
|
parseLandOneSN = do
|
||
|
let p = count 2 digit
|
||
|
skip = skipOptional $ char ' ' <|> char '.'
|
||
|
p1 <- count 3 digit
|
||
|
skip
|
||
|
p2 <- p
|
||
|
skip
|
||
|
p3 <- p
|
||
|
return . LandTwoSN $ p1 ++ p2 ++ p3
|
||
|
|
||
|
parsePhone :: Parser PhoneNumberBE
|
||
|
parsePhone = do
|
||
|
-- Country code
|
||
|
m <- optional $ string "+32"
|
||
|
-- white space
|
||
|
skipMany $ char ' '
|
||
|
-- Leading zero depends on country code
|
||
|
skipOptional $ char ' '
|
||
|
_ <- case m of
|
||
|
Just _ -> do
|
||
|
a <- optional $ char '0'
|
||
|
case a of
|
||
|
Nothing -> return Nothing
|
||
|
Just _ -> unexpected "0 not expected here"
|
||
|
Nothing -> Just <$> char '0'
|
||
|
z <- parseZonePrefix
|
||
|
skipOptional $ char ' ' <|> char '/'
|
||
|
s <- case z of
|
||
|
MobileZP _ -> parseMobileSN
|
||
|
LandOneZP _ -> parseLandOneSN
|
||
|
LandTwoZP _ -> parseLandTwoSN
|
||
|
return $ PhoneNumberBE z s
|
||
|
|
||
|
maybeSuccess :: Result a -> Maybe a
|
||
|
maybeSuccess (Success a) = Just a
|
||
|
maybeSuccess _ = Nothing
|
||
|
|
||
|
-- Should test all options for mobile and single-digit land lines,
|
||
|
-- but that is just copy-paste... QuickCheck could also help.
|
||
|
main :: IO ()
|
||
|
main = hspec $ do
|
||
|
describe "Parse Zone Prefix" $ do
|
||
|
it "Mobile starts with 4[6-9]x " $ do
|
||
|
let m = parseString parseZonePrefix mempty "472"
|
||
|
r = maybeSuccess m
|
||
|
r `shouldBe` Just (MobileZP "472")
|
||
|
it "Incorrect mobile zone prefix should fail" $ do
|
||
|
let m = parseString parseZonePrefix mempty "47"
|
||
|
r = maybeSuccess m
|
||
|
r `shouldBe` Nothing
|
||
|
it "Land line with single digit" $ do
|
||
|
let m = parseString parseZonePrefix mempty "2"
|
||
|
r = maybeSuccess m
|
||
|
r `shouldBe` Just (LandOneZP "2")
|
||
|
it "Land line with double digit" $ do
|
||
|
let m = parseString parseZonePrefix mempty "55"
|
||
|
r = maybeSuccess m
|
||
|
r `shouldBe` Just (LandTwoZP "55")
|
||
|
-- ...
|