haskell-postal (empty) → 0.1.0.0
raw patch · 6 files changed
+288/−0 lines, 6 filesdep +basedep +bytestringdep +containerssetup-changed
Dependencies added: base, bytestring, containers, inline-c, text
Files
- LICENSE +21/−0
- README.md +8/−0
- Setup.hs +2/−0
- haskell-postal.cabal +46/−0
- src/NLP/Postal.hs +174/−0
- src/main.hs +37/−0
+ LICENSE view
@@ -0,0 +1,21 @@+MIT License++Copyright (c) 2018 Fábián Tamás László++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+SOFTWARE.
+ README.md view
@@ -0,0 +1,8 @@+# haskell-postal++Haskell binding for the libpostal library++You have to install libpostal. Get it from here: https://github.com/openvenues/libpostal++This binding is in alpha state, expect more functionality to come soon. Currently+it can only be used to do basic parsing and normalization.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ haskell-postal.cabal view
@@ -0,0 +1,46 @@+name: haskell-postal+version: 0.1.0.0+synopsis: Haskell binding for the libpostal library+description: Provides an interface for the libpostal NLP library+homepage: https://github.com/netom/haskell-postal#readme+license: MIT+license-file: LICENSE+author: Fábián Tamás László+maintainer: giganetom@gmail.com+copyright: 2018 Fábián Tamás László+category: Natural Language Processing+build-type: Simple+extra-source-files: README.md+cabal-version: >=1.10++library+ hs-source-dirs: src+ exposed-modules: NLP.Postal+ default-language: Haskell2010+ pkgconfig-depends: libpostal+ ghc-options: -Wall -fPIC+ build-depends:+ base >= 4.7 && < 5+ , containers >= 0.5.11 && < 0.6+ , bytestring >= 0.10.8 && < 0.11+ , text >= 1.2.3 && < 1.3+ , inline-c >= 0.6.1 && < 0.7++executable haskell-postal+ hs-source-dirs: src+ main-is: main.hs+ default-language: Haskell2010+ pkgconfig-depends: libpostal+ ghc-options: -Wall+ build-depends:+ base >= 4.7 && < 5+ , containers+ , bytestring+ , text+ , inline-c+ other-modules:+ NLP.Postal++source-repository head+ type: git+ location: https://github.com/netom/haskell-postal
+ src/NLP/Postal.hs view
@@ -0,0 +1,174 @@+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}++module NLP.Postal+ ( AddressParserOptions+ , NormalizeOptions+ , setup+ , setupParser+ , setupLanguageClassifier+ , getAddressParserDefaultOptions+ , getDefaultNormalizeOptions+ , parseAddress+ , expandAddress+ , tearDownParser+ , tearDownLanguageClassifier+ , tearDown+ ) where++import Data.Monoid ((<>))+import Foreign.C.Types+import qualified Language.C.Inline as C+import Language.C.Inline.Context as CX++import Foreign.Ptr++import Data.ByteString.Internal (fromForeignPtr)++import Foreign.ForeignPtr+import Control.Monad (forM, when)++import qualified Data.Text as T+import qualified Data.Text.Encoding as TE++C.context (CX.baseCtx <> CX.bsCtx)++C.include "<libpostal/libpostal.h>"+C.include "<string.h>"++-- |Phantom type for a Ptr containing address parser options+data AddressParserOptions++-- |Phantom type for a Ptr containing options for the normalizer+data NormalizeOptions++foreign import ccall "stdlib.h &free" p_free :: FunPtr (Ptr a -> IO ())++-- |Calls libpostal_setup() to set up the library+-- Make sure you call this before anything else+setup :: IO Int+setup = fromIntegral <$> [C.exp| int { libpostal_setup() } |]++-- |Calls libpostal_setup_parser() to set up the address parser+-- Call this before trying to parse addresses+setupParser :: IO Int+setupParser = fromIntegral <$> [C.exp| int { libpostal_setup_parser() } |]++-- |Calls libpostal_setup_language_classifier() to set up the classifier+-- Call this before doing address normaliation+setupLanguageClassifier :: IO Int+setupLanguageClassifier = fromIntegral <$> [C.exp| int { libpostal_setup_language_classifier() } |]++-- |Returns default parser options+-- returned by libpostal_get_address_parser_default_options()+getAddressParserDefaultOptions :: IO (Ptr AddressParserOptions)+getAddressParserDefaultOptions = castPtr <$> [C.block|+ void * {+ libpostal_address_parser_options_t options = libpostal_get_address_parser_default_options();+ void * hoptions = malloc(sizeof(options));+ memcpy(hoptions, &options, sizeof(options));+ return hoptions;+ } |]++-- |Returns default normalizer options+-- returned by libpostal_get_default_options()+getDefaultNormalizeOptions :: IO (Ptr NormalizeOptions)+getDefaultNormalizeOptions = castPtr <$> [C.block|+ void * {+ libpostal_normalize_options_t options = libpostal_get_default_options();+ void * hoptions = malloc(sizeof(options));+ memcpy(hoptions, &options, sizeof(options));+ return hoptions;+ } |]++-- |Parse an address+-- Calls libpostal_parse_address() to parse an address and returns the parsed+-- parts as a list of key-value tuples (an association list).+parseAddress :: Ptr AddressParserOptions -> T.Text -> IO [(T.Text, T.Text)]+parseAddress options address = do+ response <- [C.exp| void * { libpostal_parse_address($bs-ptr:bsAddress, * (libpostal_address_parser_options_t *) $(void * castedOptions)) } |]++ when (response == nullPtr) $ fail "libpostal_parse_address returned NULL a pointer"++ numComponents <- [C.exp| int { ((libpostal_address_parser_response_t *) $(void * response))->num_components } |]++ -- TODO: zero length label+ -- TODO: NULL == malloc()+ -- TODO: put C functions in actual C files, call those with C.exp-s+ result <- forM [0..numComponents-1] $ \i -> do+ (labelLen, labelPtr) <- C.withPtr $ \len -> [C.block| char * {+ char * srcbuf = ((libpostal_address_parser_response_t *) $(void * response))->labels[$(int i)];+ *$(size_t * len) = strlen(srcbuf);+ char * dstbuf = malloc(*$(size_t * len));+ memcpy(dstbuf, srcbuf, *$(size_t * len));+ return dstbuf;+ } |]++ labelFPtr <- newForeignPtr p_free labelPtr++ (compLen, compPtr) <- C.withPtr $ \len -> [C.block| char * {+ char * srcbuf = ((libpostal_address_parser_response_t *) $(void * response))->components[$(int i)];+ *$(size_t * len) = strlen(srcbuf);+ char * dstbuf = malloc(*$(size_t * len));+ memcpy(dstbuf, srcbuf, *$(size_t * len));+ return dstbuf;+ } |]++ compFPtr <- newForeignPtr p_free compPtr++ return+ ( TE.decodeUtf8 $ fromForeignPtr (castForeignPtr labelFPtr) 0 (fromIntegral labelLen)+ , TE.decodeUtf8 $ fromForeignPtr (castForeignPtr compFPtr) 0 (fromIntegral compLen)+ )++ [C.exp| void { libpostal_address_parser_response_destroy((libpostal_address_parser_response_t *) $(void * response)) } |]++ return result++ where+ castedOptions = castPtr options+ bsAddress = TE.encodeUtf8 address++-- |Returns the expansion of an address+-- Calls libpostal_expand_address() to normalize an address and return the list+-- of normalized addresses.+expandAddress :: Ptr NormalizeOptions -> T.Text -> IO [T.Text]+expandAddress options address = do+ (numExpansions, expansions) <- C.withPtr $ \numExpansions ->+ [C.exp| char * * { libpostal_expand_address($bs-ptr:bsAddress, * (libpostal_normalize_options_t *) $(void * cOptions), $(size_t * numExpansions) ) } |]++ -- TODO: this also turns an array of (char *) to a list of Texts.+ result <- forM [0..numExpansions-1] $ \i -> do+ (xpLen, xpPtr) <- C.withPtr $ \len -> [C.block| char * {+ char * srcbuf = $(char * * expansions)[$(size_t i)];+ *$(size_t * len) = strlen(srcbuf);+ char * dstbuf = malloc(*$(size_t * len));+ memcpy(dstbuf, srcbuf, *$(size_t * len));+ return dstbuf;+ } |]++ xpFPtr <- newForeignPtr p_free xpPtr++ return $ TE.decodeUtf8 $ fromForeignPtr (castForeignPtr xpFPtr) 0 (fromIntegral xpLen)++ [C.exp| void { libpostal_expansion_array_destroy($(char * * expansions), $(size_t numExpansions)) } |]++ return result++ where+ bsAddress = TE.encodeUtf8 address+ cOptions = castPtr options++-- |Calls libpostal_teardown_parser()+tearDownParser :: IO ()+tearDownParser = [C.exp| void { libpostal_teardown_parser() } |]++-- |Calls libpostal_teardown_language_classifier()+tearDownLanguageClassifier :: IO ()+tearDownLanguageClassifier = [C.exp| void { libpostal_teardown_language_classifier() } |]++-- |Calls libpostal_teardown()+tearDown :: IO ()+tearDown = [C.exp| void { libpostal_teardown() } |]
+ src/main.hs view
@@ -0,0 +1,37 @@+{-# LANGUAGE OverloadedStrings #-}++module Main where++import Control.Monad+import System.Environment+import System.Exit+import NLP.Postal++import qualified Data.Text as T++main :: IO ()+main = do+ args <- getArgs++ when (length args /= 1) $ die "Usage: haskell-postal <address>"++ let address = T.pack $ head args++ putStrLn "Setting up libpostal..."+ setup >>= \x -> when (x == 0) $ die "FAIL"++ putStrLn "Setting up the parser..."+ setupParser >>= \x -> when (x == 0) $ die "FAIL"++ putStrLn "Setting up the language classifier..."+ setupLanguageClassifier >>= \x -> when (x == 0) $ die "FAIL"++ defParserOpts <- getAddressParserDefaultOptions+ defNormalizeOptions <- getDefaultNormalizeOptions++ parseAddress defParserOpts address >>= print+ expandAddress defNormalizeOptions address >>= print++ tearDownLanguageClassifier+ tearDownParser+ tearDown