import Test.Tasty.HUnit ( (@?=), testCase )
import Text.Parsec
import Text.Parsec.String ( Parser )
+import Text.Read ( readMaybe )
newtype IPv4Octet = IPv4Octet Int
--
-- ==== _Examples_
--
--- >>> parse v4seq_member "" "127"
--- Right (IPv4SequenceMemberOctet (IPv4Octet 127))
+-- >>> parseTest v4seq_member "127"
+-- IPv4SequenceMemberOctet (IPv4Octet 127)
--
--- >>> parse v4seq_member "" "1..5"
--- Right (IPv4SequenceMemberOctetRange (IPv4Octet 1) (IPv4Octet 5))
+-- >>> parseTest v4seq_member "1..5"
+-- IPv4SequenceMemberOctetRange (IPv4Octet 1) (IPv4Octet 5)
--
v4seq_member :: Parser IPv4SequenceMember
v4seq_member = try both <|> just_one
--
-- ==== _Examples_
--
--- >>> parse v4sequence "" "1"
--- Right (IPv4SequenceSingleMember (IPv4SequenceMemberOctet (IPv4Octet 1)))
+-- >>> parseTest v4sequence "1"
+-- IPv4SequenceSingleMember (IPv4SequenceMemberOctet (IPv4Octet 1))
--
--- >>> parse v4sequence "" "1..2"
--- Right (IPv4SequenceSingleMember (IPv4SequenceMemberOctetRange (IPv4Octet 1) (IPv4Octet 2)))
+-- >>> parseTest v4sequence "1..2"
+-- IPv4SequenceSingleMember (IPv4SequenceMemberOctetRange (IPv4Octet 1) (IPv4Octet 2))
--
--- >>> parse v4sequence "" "1..2;8"
--- Right (IPv4SequenceOptions (IPv4SequenceMemberOctetRange (IPv4Octet 1) (IPv4Octet 2)) (IPv4SequenceSingleMember (IPv4SequenceMemberOctet (IPv4Octet 8))))
+-- >>> parseTest v4sequence "1..2;8"
+-- IPv4SequenceOptions (IPv4SequenceMemberOctetRange (IPv4Octet 1) (IPv4Octet 2)) (IPv4SequenceSingleMember (IPv4SequenceMemberOctet (IPv4Octet 8)))
--
v4sequence :: Parser IPv4Sequence
v4sequence = try both <|> just_one -- Maybe sepBy is appropriate here?
--
-- ==== _Examples_
--
--- >>> parse v4field "" "127"
--- Right (IPv4FieldOctet (IPv4Octet 127))
+-- >>> parseTest v4field "127"
+-- IPv4FieldOctet (IPv4Octet 127)
--
--- >>> parse v4field "" "[127]"
--- Right (IPv4FieldSequence (IPv4SequenceSingleMember (IPv4SequenceMemberOctet (IPv4Octet 127))))
+-- >>> parseTest v4field "[127]"
+-- IPv4FieldSequence (IPv4SequenceSingleMember (IPv4SequenceMemberOctet (IPv4Octet 127)))
--
v4field :: Parser IPv4Field
v4field = just_octet <|> brackets
-- | Parse an IPv4 octet, which should contain a string of digits.
+-- Should fail if the parsed integer does not lie between @0@ and
+-- @255@ inclusive.
--
-- ==== _Examples_
--
--- >>> parse v4octet "" "127"
--- Right (IPv4Octet 127)
+-- Standard octets are parsed correctly:
+--
+-- parseTest v4octet "0"
+-- IPv4Octet 0
+--
+-- >>> parseTest v4octet "127"
+-- IPv4Octet 127
+--
+-- >>> parseTest v4octet "255"
+-- IPv4Octet 255
+--
+-- Non-digit input throws an error:
+--
+-- >>> parseTest v4octet "Hello, World!"
+-- parse error at (line 1, column 1):
+-- unexpected "H"
+-- expecting digit
+--
+-- If we're given an integer outside the range @0..255@ (i.e. not a
+-- valid octet), we fail:
+--
+-- >>> parseTest v4octet "9000"
+-- parse error at (line 1, column 5):
+-- unexpected end of input
+-- expecting digit
+-- Octet "9000" must be between 0 and 255.
--
v4octet :: Parser IPv4Octet
-v4octet = fmap (IPv4Octet . read) $ many1 digit
+v4octet = do
+ s <- many1 digit
+ case ( readMaybe s :: Maybe Int ) of
+ -- If "many1 digit" gives us a list of digits, we should be able
+ -- to convert that to an Int! It will overflow rather than fail
+ -- if the input is too big/small, so it should really always
+ -- succeed.
+ Nothing -> unexpected "readMaybe failed on a sequence of digits!"
+ -- If we got an Int, make sure it's actually a representation of
+ -- an octet.
+ Just k -> if 0 <= k && k <= 255
+ then return (IPv4Octet k)
+ else fail ("Octet \"" ++ (show k)
+ ++ "\" must be between 0 and 255.")
+
+
+-- | Parse an ipv4 address pattern. This consists of four fields,
+-- separated by periods, where a field is either a simple octet or a
+-- sequence.
+--
+-- See also: 'v4field', 'v4sequence'.
+--
+-- ==== _Examples_
+--
+-- >>> parseTest v4pattern "127.0.0.1"
+-- IPv4Pattern (IPv4FieldOctet (IPv4Octet 127)) (IPv4FieldOctet (IPv4Octet 0)) (IPv4FieldOctet (IPv4Octet 0)) (IPv4FieldOctet (IPv4Octet 1))
+--
v4pattern :: Parser IPv4Pattern
v4pattern = do
field1 <- v4field
return $ IPv4Pattern field1 field2 field3 field4
+-- | Enumerate the members of an 'IPv4SequenceMember'. A sequence
+-- member is either an octet, which is easy to enumerate -- we just
+-- print it -- or an octet range whose members can be enumerated
+-- from least to greatest.
+--
+-- We enumerate strings instead of integers because the big picture
+-- is that we will be listing out patterns of ipv4 addresses, and
+-- those are represented as strings (dotted quad format).
+--
+-- ==== _Examples_
+--
+-- >>> let (Right r) = parse v4seq_member "" "127"
+-- >>> sequence_members r
+-- ["127"]
+--
+-- >>> let (Right r) = parse v4seq_member "" "127..135"
+-- >>> sequence_members r
+-- ["127","128","129","130","131","132","133","134","135"]
+--
sequence_members :: IPv4SequenceMember -> [String]
sequence_members (IPv4SequenceMemberOctet (IPv4Octet i)) = [show i]
-sequence_members (IPv4SequenceMemberOctetRange (IPv4Octet start) (IPv4Octet end)) =
- [show x | x <- [start..end]]
+sequence_members (IPv4SequenceMemberOctetRange (IPv4Octet s) (IPv4Octet t)) =
+ [show x | x <- [s .. t]]
+
+-- | Enumerate the members of an ipv4 sequence. These consist of
+-- either a single sequence member (in which case we delegate to
+-- 'sequence_members'), or an \"option\" which is enumerated
+-- recursively.
+--
+-- ==== _Examples_
+--
+-- >>> let (Right r) = parse v4sequence "" "1"
+-- >>> sequences r
+-- ["1"]
+--
+-- >>> let (Right r) = parse v4sequence "" "1..2"
+-- >>> sequences r
+-- ["1","2"]
+--
+-- >>> let (Right r) = parse v4sequence "" "1..3;4;5..9"
+-- >>> sequences r
+-- ["1","2","3","4","5","6","7","8","9"]
+--
sequences :: IPv4Sequence -> [String]
-sequences (IPv4SequenceSingleMember sm) = sequence_members sm
+sequences (IPv4SequenceSingleMember sm) =
+ sequence_members sm
sequences (IPv4SequenceOptions sm s) =
(sequence_members sm) ++ (sequences s)