country 0.1.2 → 0.1.3
raw patch · 5 files changed
+168/−20 lines, 5 filesdep +attoparsecPVP ok
version bump matches the API change (PVP)
Dependencies added: attoparsec
API changes (from Hackage documentation)
+ Country: parser :: Parser Country
+ Country: parserUtf8 :: Parser Country
Files
- country.cabal +4/−1
- src/Country.hs +38/−19
- src/Country/Unexposed/Names.hs +1/−0
- src/Country/Unexposed/Trie.hs +62/−0
- src/Country/Unexposed/TrieByte.hs +63/−0
country.cabal view
@@ -1,5 +1,5 @@ name: country-version: 0.1.2+version: 0.1.3 synopsis: Country data type and functions description: The `country` library provides a data type for dealing with@@ -38,6 +38,8 @@ Country.Unexposed.Enumerate Country.Unexposed.ExtraNames Country.Unexposed.Names+ Country.Unexposed.Trie+ Country.Unexposed.TrieByte build-depends: base >= 4.7 && < 4.10 , text >= 1.2 && < 1.3@@ -48,6 +50,7 @@ , hashable >= 1.2 && < 1.3 , aeson >= 0.11 && < 1.3 , scientific >= 0.3 && < 0.4+ , attoparsec >= 0.13 && < 0.14 default-language: Haskell2010 test-suite test
src/Country.hs view
@@ -11,6 +11,8 @@ -- * Name , encodeEnglish , decode+ , parser+ , parserUtf8 -- * Alpha-2 and Alpha-3 , alphaTwoUpper , alphaThreeUpper@@ -22,14 +24,12 @@ import Country.Unsafe (Country(..)) import Country.Unexposed.Encode.English (countryNameQuads)-import Country.Unexposed.Names (englishCountryNamesText,numberOfPossibleCodes,alphaTwoHashMap,alphaThreeHashMap,decodeMap,decodeNumeric,encodeEnglish)-import Country.Unexposed.Enumerate (enumeratedCountries)+import Country.Unexposed.Names (numberOfPossibleCodes,alphaTwoHashMap,alphaThreeHashMap,decodeMap,decodeNumeric,encodeEnglish)+import Country.Unexposed.Trie (Trie,trieFromList,trieParser)+import Country.Unexposed.TrieByte (TrieByte,trieByteFromList,trieByteParser) import Data.Text (Text)-import Data.ByteString (ByteString)-import Data.Word (Word16,Word8)-import Data.Primitive (indexArray,newArray,unsafeFreezeArray,writeArray,- writeByteArray,indexByteArray,unsafeFreezeByteArray,newByteArray)-import Data.HashMap.Strict (HashMap)+import Data.Word (Word16)+import Data.Primitive (indexArray,writeByteArray,indexByteArray,unsafeFreezeByteArray,newByteArray) import Data.Primitive.Array (Array(..)) import Data.Primitive.ByteArray (ByteArray(..)) import GHC.Prim (sizeofByteArray#,sizeofArray#)@@ -38,10 +38,13 @@ import Control.Monad import Data.Char (ord,chr,toLower) import Data.Bits (unsafeShiftL,unsafeShiftR)-import qualified Data.List as L+import Data.Coerce (coerce) import qualified Data.HashMap.Strict as HM import qualified Data.Text.Array as TA+import qualified Data.Text.Encoding as TE import qualified Data.Text.Internal as TI+import qualified Data.Attoparsec.Text as AT+import qualified Data.Attoparsec.ByteString as AB -- | Convert a country to its numeric code. This is a -- three-digit number and will consequently be less than 1000.@@ -86,27 +89,35 @@ decode :: Text -> Maybe Country decode = flip HM.lookup decodeMap +-- | Parse a country from its name using an attoparsec text parser. This+-- function is language-agnostic and can handle any source language.+-- In the case that one possible country name is a prefix of another+-- possible name (for example, United States vs United States of America),+-- the longest possible will be parsed.+parser :: AT.Parser Country+parser = coerce (trieParser decodeTrie)++parserUtf8 :: AB.Parser Country+parserUtf8 = coerce (trieByteParser decodeTrieUtf8)+ word16ToInt :: Word16 -> Int word16ToInt = fromIntegral -intToWord16 :: Int -> Word16-intToWord16 = fromIntegral- charToWord16 :: Char -> Word16 charToWord16 = fromIntegral . ord word16ToChar :: Word16 -> Char word16ToChar = chr . fromIntegral -arrayFoldl' :: (a -> b -> a) -> a -> Array b -> a-arrayFoldl' f z a = go 0 z- where- go i !acc | i < sizeofArray a = go (i+1) (f acc $ indexArray a i)- | otherwise = acc+-- arrayFoldl' :: (a -> b -> a) -> a -> Array b -> a+-- arrayFoldl' f z a = go 0 z+-- where+-- go i !acc | i < sizeofArray a = go (i+1) (f acc $ indexArray a i)+-- | otherwise = acc -sizeofArray :: Array a -> Int-sizeofArray (Array a) = I# (sizeofArray# a)-{-# INLINE sizeofArray #-}+-- sizeofArray :: Array a -> Int+-- sizeofArray (Array a) = I# (sizeofArray# a)+-- {-# INLINE sizeofArray #-} numberOfCountries :: Int numberOfCountries = length countryNameQuads@@ -168,5 +179,13 @@ else return () go 0 return m++decodeTrie :: Trie+decodeTrie = trieFromList (map (\(a,Country x) -> (a,x)) (HM.toList decodeMap))+{-# NOINLINE decodeTrie #-}++decodeTrieUtf8 :: TrieByte+decodeTrieUtf8 = trieByteFromList (map (\(a,Country x) -> (TE.encodeUtf8 a,x)) (HM.toList decodeMap))+{-# NOINLINE decodeTrieUtf8 #-}
src/Country/Unexposed/Names.hs view
@@ -71,6 +71,7 @@ , (mexico,"México") , (mexico,"Méjico") , (ålandIslands,"Aland Islands")+ , (ålandIslands,"Aaland Islands") , (venezuelaBolivarianRepublicOf,"Venezuela") , (venezuelaBolivarianRepublicOf,"Bolivarian Republic of Venezuela") , (boliviaPlurinationalStateOf,"Bolivia")
+ src/Country/Unexposed/Trie.hs view
@@ -0,0 +1,62 @@+module Country.Unexposed.Trie+ ( Trie+ , trieFromList+ , trieParser+ ) where++import Data.HashMap.Strict (HashMap)+import Data.Word (Word16)+import Data.Text (Text)+import Control.Applicative ((<|>))+import qualified Data.Text as T+import qualified Data.HashMap.Strict as HM+import qualified Data.Attoparsec.Text as AT++-- | If the value is not the max Word16 (65535), there +-- is a match. This means that 65535 cannot be used, which +-- is fine for this since 65535 is not used as a country code.+data Trie = Trie+ { trieValue :: {-# UNPACK #-} !Word16+ , trieChildren :: !(HashMap Char Trie)+ }++empty :: Trie+empty = Trie placeholder HM.empty++append :: Trie -> Trie -> Trie+append (Trie v1 c1) (Trie v2 c2) = Trie (min v1 v2) (HM.unionWith append c1 c2)++placeholder :: Word16+placeholder = 0xFFFF++singleton :: Text -> Word16 -> Trie+singleton fullName code = go fullName where+ go :: Text -> Trie+ go name = case T.uncons name of+ Just (char,nameNext) -> Trie placeholder (HM.singleton char (go nameNext))+ Nothing -> Trie code HM.empty++instance Monoid Trie where+ mempty = empty+ mappend = append++trieFromList :: [(Text,Word16)] -> Trie+trieFromList = foldMap (uncurry singleton)++-- it seems like attoparsec should have some kind of convenience+-- for being able to commit to consuming a certain amount of+-- input once your certain that it will be consumed, but I cannot+-- find a way to use the api to do this.+trieParser :: Trie -> AT.Parser Word16+trieParser = go where+ go :: Trie -> AT.Parser Word16+ go (Trie value children) = do+ let keepGoing = do+ c <- AT.anyChar+ case HM.lookup c children of+ Nothing -> fail "did not recognize country name"+ Just trieNext -> go trieNext+ if value == placeholder+ then keepGoing+ else keepGoing <|> return value+
+ src/Country/Unexposed/TrieByte.hs view
@@ -0,0 +1,63 @@+module Country.Unexposed.TrieByte+ ( TrieByte+ , trieByteFromList+ , trieByteParser+ ) where++import Data.HashMap.Strict (HashMap)+import Data.Word (Word16,Word8)+import Data.ByteString (ByteString)+import Control.Applicative ((<|>))+import qualified Data.ByteString as B+import qualified Data.HashMap.Strict as HM+import qualified Data.Attoparsec.ByteString as AB++-- | If the value is not the max Word16 (65535), there +-- is a match. This means that 65535 cannot be used, which +-- is fine for this since 65535 is not used as a country code.+data TrieByte = TrieByte+ { trieValue :: {-# UNPACK #-} !Word16+ , trieChildren :: !(HashMap Word8 TrieByte)+ }++empty :: TrieByte+empty = TrieByte placeholder HM.empty++append :: TrieByte -> TrieByte -> TrieByte+append (TrieByte v1 c1) (TrieByte v2 c2) = TrieByte (min v1 v2) (HM.unionWith append c1 c2)++placeholder :: Word16+placeholder = 0xFFFF++singleton :: ByteString -> Word16 -> TrieByte+singleton fullName code = go fullName where+ go :: ByteString -> TrieByte+ go name = case B.uncons name of+ Just (char,nameNext) -> TrieByte placeholder (HM.singleton char (go nameNext))+ Nothing -> TrieByte code HM.empty++instance Monoid TrieByte where+ mempty = empty+ mappend = append++trieByteFromList :: [(ByteString,Word16)] -> TrieByte+trieByteFromList = foldMap (uncurry singleton)++-- it seems like attoparsec should have some kind of convenience+-- for being able to commit to consuming a certain amount of+-- input once your certain that it will be consumed, but I cannot+-- find a way to use the api to do this.+trieByteParser :: TrieByte -> AB.Parser Word16+trieByteParser = go where+ go :: TrieByte -> AB.Parser Word16+ go (TrieByte value children) = do+ let keepGoing = do+ c <- AB.anyWord8+ case HM.lookup c children of+ Nothing -> fail "did not recognize country name"+ Just trieNext -> go trieNext+ if value == placeholder+ then keepGoing+ else keepGoing <|> return value++