--- /dev/null
+-- | An IPv4 address pattern has four fields separated by ".". Each
+-- field is either a decimal number, or a sequence inside "[]" that
+-- contains one or more ";"-separated decimal numbers or
+-- number..number ranges.
+--
+-- Thus, any pattern field can be a sequence inside "[]", but a "[]"
+-- sequence cannot span multiple address fields, and a pattern field
+-- cannot contain both a number and a "[]" sequence at the same
+-- time.
+--
+-- This means that the pattern 1.2.[3.4] is not valid (the sequence
+-- [3.4] cannot span two address fields) and the pattern
+-- 1.2.3.3[6..9] is also not valid (the last field cannot be both
+-- number 3 and sequence [6..9] at the same time).
+--
+-- The syntax for IPv4 patterns is as follows:
+--
+-- v4pattern = v4field "." v4field "." v4field "." v4field
+-- v4field = v4octet | "[" v4sequence "]"
+-- v4octet = any decimal number in the range 0 through 255
+-- v4sequence = v4seq_member | v4sequence ";" v4seq_member
+-- v4seq_member = v4octet | v4octet ".." v4octet
+--
+module IPv4Pattern
+where
+
+import Test.Tasty ( TestTree, testGroup )
+import Test.Tasty.HUnit ( (@?=), testCase )
+import Text.Parsec
+import Text.Parsec.String ( Parser )
+
+
+newtype IPv4Octet = IPv4Octet Int
+ deriving (Eq, Show)
+
+data IPv4SequenceMember =
+ IPv4SequenceMemberOctet IPv4Octet
+ | IPv4SequenceMemberOctetRange IPv4Octet IPv4Octet
+ deriving (Eq, Show)
+
+data IPv4Sequence =
+ IPv4SequenceSingleMember IPv4SequenceMember
+ | IPv4SequenceOptions IPv4SequenceMember IPv4Sequence
+ deriving (Eq, Show)
+
+
+data IPv4Field = IPv4FieldOctet IPv4Octet | IPv4FieldSequence IPv4Sequence
+ deriving (Eq, Show)
+
+data IPv4Pattern =
+ IPv4Pattern IPv4Field IPv4Field IPv4Field IPv4Field
+ deriving (Eq, Show)
+
+
+-- | Parse an IPv4 \"sequence member\". A sequence member is either an
+-- octet, or a start..end sequence (like an enumeration, in Haskell).
+--
+-- ==== _Examples_
+--
+-- >>> parse v4seq_member "" "127"
+-- Right (IPv4SequenceMemberOctet (IPv4Octet 127))
+--
+-- >>> parse v4seq_member "" "1..5"
+-- Right (IPv4SequenceMemberOctetRange (IPv4Octet 1) (IPv4Octet 5))
+--
+v4seq_member :: Parser IPv4SequenceMember
+v4seq_member = try both <|> just_one
+ where
+ both = do
+ oct1 <- v4octet
+ _ <- string ".."
+ oct2 <- v4octet
+ return $ IPv4SequenceMemberOctetRange oct1 oct2
+
+ just_one = fmap IPv4SequenceMemberOctet v4octet
+
+
+-- | Parse an IPv4 \"sequence\". A sequence is whatever is allowed
+-- within square brackets. Basically it can be three things:
+--
+-- * An octet (number).
+-- * A range of addresses in start..end format.
+-- * An alternative, separated by a semicolon, where each side
+-- contains one of the previous two options.
+--
+-- ==== _Examples_
+--
+-- >>> parse v4sequence "" "1"
+-- Right (IPv4SequenceSingleMember (IPv4SequenceMemberOctet (IPv4Octet 1)))
+--
+-- >>> parse v4sequence "" "1..2"
+-- Right (IPv4SequenceSingleMember (IPv4SequenceMemberOctetRange (IPv4Octet 1) (IPv4Octet 2)))
+--
+-- >>> parse v4sequence "" "1..2;8"
+-- Right (IPv4SequenceOptions (IPv4SequenceMemberOctetRange (IPv4Octet 1) (IPv4Octet 2)) (IPv4SequenceSingleMember (IPv4SequenceMemberOctet (IPv4Octet 8))))
+--
+v4sequence :: Parser IPv4Sequence
+v4sequence = try both <|> just_one -- Maybe sepBy is appropriate here?
+ where
+ both = do
+ sm <- v4seq_member
+ _ <- char ';'
+ s <- v4sequence
+ return $ IPv4SequenceOptions sm s
+
+ just_one = fmap IPv4SequenceSingleMember v4seq_member
+
+
+-- | Parse an IPv4 \"field\", which is either a boring old octet, or a
+-- 'v4sequence' within square brackets.
+--
+-- ==== _Examples_
+--
+-- >>> parse v4field "" "127"
+-- Right (IPv4FieldOctet (IPv4Octet 127))
+--
+-- >>> parse v4field "" "[127]"
+-- Right (IPv4FieldSequence (IPv4SequenceSingleMember (IPv4SequenceMemberOctet (IPv4Octet 127))))
+--
+v4field :: Parser IPv4Field
+v4field = just_octet <|> brackets
+ where
+ just_octet = fmap IPv4FieldOctet v4octet
+
+ brackets = do
+ _ <- char '['
+ s <- v4sequence
+ _ <- char ']'
+ return $ IPv4FieldSequence s
+
+
+-- | Parse an IPv4 octet, which should contain a string of digits.
+--
+-- ==== _Examples_
+--
+-- >>> parse v4octet "" "127"
+-- Right (IPv4Octet 127)
+--
+v4octet :: Parser IPv4Octet
+v4octet = fmap (IPv4Octet . read) $ many1 digit
+
+v4pattern :: Parser IPv4Pattern
+v4pattern = do
+ field1 <- v4field
+ _ <- char '.'
+ field2 <- v4field
+ _ <- char '.'
+ field3 <- v4field
+ _ <- char '.'
+ field4 <- v4field
+ return $ IPv4Pattern field1 field2 field3 field4
+
+
+sequence_members :: IPv4SequenceMember -> [String]
+sequence_members (IPv4SequenceMemberOctet (IPv4Octet i)) = [show i]
+sequence_members (IPv4SequenceMemberOctetRange (IPv4Octet start) (IPv4Octet end)) =
+ [show x | x <- [start..end]]
+
+sequences :: IPv4Sequence -> [String]
+sequences (IPv4SequenceSingleMember sm) = sequence_members sm
+sequences (IPv4SequenceOptions sm s) =
+ (sequence_members sm) ++ (sequences s)
+
+
+fields :: IPv4Field -> [String]
+fields (IPv4FieldOctet (IPv4Octet i)) = [show i]
+fields (IPv4FieldSequence s) = sequences s
+
+
+addresses :: IPv4Pattern -> [String]
+addresses (IPv4Pattern field1 field2 field3 field4) = do
+ f1 <- fields field1
+ f2 <- fields field2
+ f3 <- fields field3
+ f4 <- fields field4
+ return $ f1 ++ "." ++ f2 ++ "." ++ f3 ++ "." ++ f4
+
+
+-- Tests
+
+v4octet_tests :: TestTree
+v4octet_tests =
+ testGroup
+ "v4octet tests"
+ [ test_v4octet_single_digit_parsed ]
+
+test_v4octet_single_digit_parsed :: TestTree
+test_v4octet_single_digit_parsed =
+ testCase "a single digit is parsed as a v4octet" $ do
+ -- Whatever, it's a test.
+ let (Right actual) = parse v4octet "" "1"
+ let expected = IPv4Octet 1
+ actual @?= expected
module Main
where
-import Text.Parsec
-import Text.Parsec.String ( Parser )
-
-newtype IPv4Octet = IPv4Octet Int
- deriving (Show)
-
-data IPv4SequenceMember =
- IPv4SequenceMemberOctet IPv4Octet
- | IPv4SequenceMemberOctetRange IPv4Octet IPv4Octet
- deriving (Show)
-
-data IPv4Sequence =
- IPv4SequenceSingleMember IPv4SequenceMember
- | IPv4SequenceOptions IPv4SequenceMember IPv4Sequence
- deriving (Show)
-
-data IPv4Field = IPv4FieldOctet IPv4Octet | IPv4FieldSequence IPv4Sequence
- deriving (Show)
-
-data IPv4Pattern =
- IPv4Pattern IPv4Field IPv4Field IPv4Field IPv4Field
- deriving (Show)
-
--- An IPv4 address pattern has four fields separated by ".". Each
--- field is either a decimal number, or a sequence inside "[]" that
--- contains one or more ";"-separated decimal numbers or number..number
--- ranges.
---
--- Thus, any pattern field can be a sequence inside "[]", but a "[]"
--- sequence cannot span multiple address fields, and a pattern field
--- cannot contain both a number and a "[]" sequence at the same time.
---
--- This means that the pattern 1.2.[3.4] is not valid (the sequence
--- [3.4] cannot span two address fields) and the pattern 1.2.3.3[6..9]
--- is also not valid (the last field cannot be both number 3 and
--- sequence [6..9] at the same time).
---
--- The syntax for IPv4 patterns is as follows:
---
--- v4pattern = v4field "." v4field "." v4field "." v4field
--- v4field = v4octet | "[" v4sequence "]"
--- v4octet = any decimal number in the range 0 through 255
--- v4sequence = v4seq_member | v4sequence ";" v4seq_member
--- v4seq_member = v4octet | v4octet ".." v4octet
-
-v4seq_member :: Parser IPv4SequenceMember
-v4seq_member = try both <|> just_one
- where
- both = do
- oct1 <- v4octet
- _ <- string ".."
- oct2 <- v4octet
- return $ IPv4SequenceMemberOctetRange oct1 oct2
-
- just_one = fmap IPv4SequenceMemberOctet v4octet
-
-
-v4sequence :: Parser IPv4Sequence
-v4sequence = try both <|> just_one
- where
- both = do
- sm <- v4seq_member
- _ <- char ';'
- s <- v4sequence
- return $ IPv4SequenceOptions sm s
-
- just_one = fmap IPv4SequenceSingleMember v4seq_member
-
-
-v4field :: Parser IPv4Field
-v4field = just_octet <|> brackets
- where
- just_octet = fmap IPv4FieldOctet v4octet
-
- brackets = do
- _ <- char '['
- s <- v4sequence
- _ <- char ']'
- return $ IPv4FieldSequence s
-
-v4octet :: Parser IPv4Octet
-v4octet = fmap (IPv4Octet . read) $ many1 digit
-
-v4pattern :: Parser IPv4Pattern
-v4pattern = do
- field1 <- v4field
- _ <- char '.'
- field2 <- v4field
- _ <- char '.'
- field3 <- v4field
- _ <- char '.'
- field4 <- v4field
- return $ IPv4Pattern field1 field2 field3 field4
-
-
-sequence_members :: IPv4SequenceMember -> [String]
-sequence_members (IPv4SequenceMemberOctet (IPv4Octet i)) = [show i]
-sequence_members (IPv4SequenceMemberOctetRange (IPv4Octet start) (IPv4Octet end)) =
- [show x | x <- [start..end]]
-
-sequences :: IPv4Sequence -> [String]
-sequences (IPv4SequenceSingleMember sm) = sequence_members sm
-sequences (IPv4SequenceOptions sm s) =
- (sequence_members sm) ++ (sequences s)
-
-
-fields :: IPv4Field -> [String]
-fields (IPv4FieldOctet (IPv4Octet i)) = [show i]
-fields (IPv4FieldSequence s) = sequences s
-
-
-addresses :: IPv4Pattern -> [String]
-addresses (IPv4Pattern field1 field2 field3 field4) = do
- f1 <- fields field1
- f2 <- fields field2
- f3 <- fields field3
- f4 <- fields field4
- return $ f1 ++ "." ++ f2 ++ "." ++ f3 ++ "." ++ f4
+import IPv4Pattern
main :: IO ()
main = do