packages feed

WordNet (empty) → 0.1.1

raw patch · 10 files changed

+1240/−0 lines, 10 filesdep +basesetup-changed

Dependencies added: base

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) Hal Daume III++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions+are met:++1. Redistributions of source code must retain the above copyright+   notice, this list of conditions and the following disclaimer.++2. Redistributions in binary form must reproduce the above copyright+   notice, this list of conditions and the following disclaimer in the+   documentation and/or other materials provided with the distribution.++3. Neither the name of the author nor the names of his contributors+   may be used to endorse or promote products derived from this software+   without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS+OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,+STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE+POSSIBILITY OF SUCH DAMAGE.
+ NLP/WordNet.hs view
@@ -0,0 +1,338 @@+-----------------------------------------------------------------------------+-- |+-- Module      :  NLP.WordNet+-- Copyright   :  (c) Hal Daume III 2003-2004+-- License     :  BSD-style+--+-- Maintainer  :  hdaume@isi.edu+-- Stability   :  experimental+-- Portability :  non-portable (H98 + implicit parameters)+--+-- This is the top level module to the Haskell WordNet interface.+--+-- This module is maintained at:+--    <http://www.isi.edu/~hdaume/HWordNet/>.+--+-- This is the only module in the WordNet package you need to import.+-- The others provide utility functions and primitives that this+-- module is based on.+--+-- More information about WordNet is available at:+--    <http://http://www.cogsci.princeton.edu/~wn/>.+-----------------------------------------------------------------------------+module NLP.WordNet+    (+     -- * The basic type system+     module NLP.WordNet.Types,++     -- * Top level execution functions+     runWordNet,+     runWordNetQuiet,+     runWordNetWithOptions,++     -- * Functions to manually initialize the WordNet system; these are not+     --   needed if you use one of the "runWordNet" functions above.+     initializeWordNet,+     initializeWordNetWithOptions,+     closeWordNet,+     runs,++     -- * The basic database access functions.+     getOverview,+     searchByOverview,+     search,+     lookupKey,++     -- * The agglomeration functions+     relatedBy,+     closure,+     closureOn,++     -- * Computing lowest-common ancestor functions; the implementation+     --   of these can be tuned by providing a different "Bag" implementation.+     --   use "emptyQueue" for breadth-first-search (recommended) or "emptyStack"+     --   for depth-first-search, or write your own.+     meet,+     meetPaths,+     meetSearchPaths,+     Bag(..),+     emptyQueue,+     emptyStack,+    )+    where++import Prelude hiding (catch)+import Data.Array+import GHC.Arr (unsafeIndex)+import GHC.Handle+import Data.Tree+import Data.IORef+import Data.Dynamic+import qualified Data.Set as Set+import Numeric (readHex, readDec)+import System.IO.Unsafe++import NLP.WordNet.Common+import NLP.WordNet.Consts+import NLP.WordNet.Util+import NLP.WordNet.Types++import qualified NLP.WordNet.PrimTypes as T+import qualified NLP.WordNet.Prims     as P++-- | Takes a WordNet command, initializes the environment+-- and returns the results in the 'IO' monad.  WordNet+-- warnings are printed to stderr.+runWordNet :: WN a -> IO a+runWordNet = runWordNetWithOptions Nothing Nothing++-- | Takes a WordNet command, initializes the environment+-- and returns the results in the 'IO' monad.  WordNet+-- warnings are ignored.+runWordNetQuiet :: WN a -> IO a+runWordNetQuiet = runWordNetWithOptions Nothing (Just (\_ _ -> return ()))++-- | Takes a FilePath to the directory holding WordNet and+-- a function to do with warnings and a WordNet command, initializes +-- the environment and returns the results in the 'IO' monad.+runWordNetWithOptions :: +    Maybe FilePath ->                          -- word net data directory+    Maybe (String -> Exception -> IO ()) ->    -- warning function (by default, warnings go to stderr)+    WN a ->                                    -- what to run+      IO a+runWordNetWithOptions dd warn wn = do+  wne <- P.initializeWordNetWithOptions dd warn+  let a = let ?wne = wne in wn+  -- P.closeWordNet wne+  return a++-- | Gives you a 'WordNetEnv' which can be passed to 'runs' or used+-- as the implicit parameter to the other WordNet functions.+initializeWordNet :: IO WordNetEnv+initializeWordNet = P.initializeWordNet++-- | Takes a FilePath to the directory holding WordNet and+-- a function to do with warnings, initializes +-- the environment and returns a 'WordNetEnv' as in 'initializeWordNet'.+initializeWordNetWithOptions :: Maybe FilePath -> Maybe (String -> Exception -> IO ()) -> IO WordNetEnv+initializeWordNetWithOptions = P.initializeWordNetWithOptions++-- | Closes all the handles associated with the 'WordNetEnv'.  Since+-- the functions provided in the "NLP.WordNet.WordNet" module+-- are /lazy/, you shouldn't do this until you're really done.+-- Or perhaps not at all (GC will eventually kick in).+closeWordNet :: WordNetEnv -> IO ()+closeWordNet = P.closeWordNet++-- | This simply takes a 'WordNetEnv' and provides it as the+-- implicit parameter to the WordNet command.+runs :: WordNetEnv -> WN a -> a+runs wne x = let ?wne = wne in x+++-- | This takes a word and returns an 'Overview' of all its senses+-- for all parts of speech.+getOverview :: WN (Word -> Overview)+getOverview word = unsafePerformIO $ do+  idxN <- unsafeInterleaveIO $ getOverview' Noun+  idxV <- unsafeInterleaveIO $ getOverview' Verb+  idxA <- unsafeInterleaveIO $ getOverview' Adj+  idxR <- unsafeInterleaveIO $ getOverview' Adv+  return (T.Overview idxN idxV idxA idxR)+  where+    getOverview' pos = do+      strM <- P.getIndexString ?wne word pos+      case strM of+        Nothing -> return Nothing+        Just  s -> unsafeInterleaveIO $ P.indexLookup ?wne word pos++-- | This takes an 'Overview' (see 'getOverview'), a 'POS' and a 'SenseType' and returns+-- a list of search results.  If 'SenseType' is 'AllSenses', there will be one+-- 'SearchResult' in the results for each valid sense.  If 'SenseType' is+-- a single sense number, there will be at most one element in the result list.+searchByOverview :: WN (Overview -> POS -> SenseType -> [SearchResult])+searchByOverview overview pos sense = unsafePerformIO $ +  case (case pos of { Noun -> T.nounIndex ; Verb -> T.verbIndex ; Adj -> T.adjIndex ; Adv -> T.advIndex })+          overview of+    Nothing  -> return []+    Just idx -> do+      let numSenses = T.indexSenseCount idx+      skL <- mapMaybe id `liftM` +             unsafeInterleaveIO (+               mapM (\sense -> do+                     skey <- P.indexToSenseKey ?wne idx sense+                     return (liftM ((,) sense) skey)+                    ) (sensesOf numSenses sense)+             )+      r <- unsafeInterleaveIO $ mapM (\ (snum, skey) ->+                 unsafeInterleaveIO (P.getSynsetForSense ?wne skey) >>= \v ->+                 case v of+                   Nothing -> return Nothing+                   Just ss -> return $ Just (T.SearchResult +                                               (Just skey) +                                               (Just overview) +                                               (Just idx)+                                               (Just (SenseNumber snum)) +                                               ss)+                ) skL+      return (mapMaybe id r)++-- | This takes a 'Word', a 'POS' and a 'SenseType' and returns+-- the equivalent of first running 'getOverview' and then 'searchByOverview'.+search :: WN (Word -> POS -> SenseType -> [SearchResult])+search word pos sense = searchByOverview (getOverview word) pos sense++-- | This takes a 'Key' (see 'srToKey' and 'srFormKeys') and looks it+-- up in the databse.+lookupKey :: WN (Key -> SearchResult)+lookupKey (T.Key (o,p)) = unsafePerformIO $ do+  ss <- unsafeInterleaveIO $ P.readSynset ?wne p o ""+  return $ T.SearchResult Nothing Nothing Nothing Nothing ss++-- | This takes a 'Form' and a 'SearchResult' and returns all+-- 'SearchResult' related to the given one by the given 'Form'.+--+-- For example:+--+-- > relatedBy Antonym (head (search "happy" Adj 1))+-- > [<unhappy>]+-- >+-- > relatedBy Hypernym (head (search "dog" Noun 1))+-- > [<canine canid>]+relatedBy :: WN (Form -> SearchResult -> [SearchResult])+relatedBy form sr = map lookupKey $ srFormKeys sr form++-- | This is a utility function to build lazy trees from a function and a root.+closure :: (a -> [a]) -> a -> Tree a+closure f x = Node x (map (closure f) $ f x)++-- | This enables 'Form'-based trees to be built.+--+-- For example:+--+-- > take 5 $ flatten $ closureOn Antonym (head (search "happy" Adj AllSenses)))+-- > [<happy>,<unhappy>,<happy>,<unhappy>,<happy>]+--+-- > closureOn Hypernym (head (search "dog" Noun 1)))+-- > - <dog domestic_dog Canis_familiaris> --- <canine canid> --- <carnivore>\\+-- >   --- <placental placental_mammal eutherian eutherian_mammal> --- <mammal>\\+-- >   --- <vertebrate craniate> --- <chordate> --- <animal animate_being beast\\+-- >   brute creature fauna> --- <organism being> --- <living_thing animate_thing>\\+-- >   --- <object physical_object> --- <entity> +closureOn :: WN (Form -> SearchResult -> Tree SearchResult)+closureOn form = closure (relatedBy form)++-- | A simple bag class for our 'meet' implementation.+class Bag b a where+  emptyBag :: b a+  addToBag :: b a -> a -> b a+  addListToBag :: b a -> [a] -> b a+  isEmptyBag :: b a -> Bool+  splitBag :: b a -> (a, b a)+  addListToBag = foldr (flip addToBag)++instance Bag [] a where+  emptyBag = []+  addToBag = flip (:)+  isEmptyBag = null+  splitBag (x:xs) = (x, xs)++-- | A very slow queue based on lists.+newtype Queue a = Queue [a] deriving (Show)++instance Bag Queue a where+  emptyBag = Queue []+  addToBag (Queue l) a = Queue (l++[a])+  isEmptyBag (Queue l) = null l+  splitBag (Queue (x:xs)) = (x, Queue xs)+  addListToBag (Queue l) l' = Queue (l ++ l')++-- | An empty stack.+emptyStack :: [a]+emptyStack = []++-- | An empty queue.+emptyQueue :: Queue a+emptyQueue = Queue []++-- | This function takes an empty bag (in particular, this is to specify+-- what type of search to perform), and the results of two search.+-- It returns (maybe) the lowest point at which the two terms+-- meet in the WordNet hierarchy.+--+-- For example:+--+-- > meet emptyQueue (head $ search "cat" Noun 1) (head $ search "dog" Noun 1)+-- > Just <carnivore>+--+-- > meet emptyQueue (head $ search "run" Verb 1) (head $ search "walk" Verb 1)+-- > Just <travel go move locomote>+meet :: Bag b (Tree SearchResult) => WN (b (Tree SearchResult) -> SearchResult -> SearchResult -> Maybe SearchResult)+meet emptyBg sr1 sr2 = srch Set.empty Set.empty (addToBag emptyBg t1) (addToBag emptyBg t2)+  where+    t1 = closureOn Hypernym sr1+    t2 = closureOn Hypernym sr2++    srch v1 v2 bag1 bag2+        | isEmptyBag bag1 && isEmptyBag bag2 = Nothing+        | isEmptyBag bag1                    = srch v2 v1 bag2 bag1+        | otherwise = +            let (Node sr chl, bag1') = splitBag bag1+            in  if v2 `containsResult` sr +                  then Just sr+                  else srch v2 (addResult v1 sr) bag2 (addListToBag bag1' chl) -- flip the order :)++    containsResult v sr = srWords sr AllSenses `Set.member` v+    addResult v sr = Set.insert (srWords sr AllSenses) v++-- | This function takes an empty bag (see 'meet'), and the results of two searches.+-- It returns (maybe) the lowest point at which the two terms+-- meet in the WordNet hierarchy, as well as the paths leading from each+-- term to this common term.+--+-- For example:+--+-- > meetPaths emptyQueue (head $ search "cat" Noun 1) (head $ search "dog" Noun 1)+-- > Just ([<cat true_cat>,<feline felid>],<carnivore>,[<canine canid>,<dog domestic_dog Canis_familiaris>])+--+-- > meetPaths emptyQueue (head $ search "run" Verb 1) (head $ search "walk" Verb 1)+-- > Just ([<run>,<travel_rapidly speed hurry zip>],<travel go move locomote>,[<walk>])+--+-- This is marginally less efficient than just using 'meet', since it uses+-- linear-time lookup for the visited sets, whereas 'meet' uses log-time+-- lookup.+meetPaths :: Bag b (Tree SearchResult) => +             WN (+                 b (Tree SearchResult) ->       -- bag implementation+                 SearchResult ->                -- word 1+                 SearchResult ->                -- word 2+                 Maybe ([SearchResult], SearchResult, [SearchResult]))   -- word 1 -> common,+                                                                         -- common+                                                                         -- common -> word 2+meetPaths emptyBg sr1 sr2 = meetSearchPaths emptyBg t1 t2+  where+    t1 = closureOn Hypernym sr1+    t2 = closureOn Hypernym sr2++meetSearchPaths emptyBg t1 t2 =+  let srch b v1 v2 bag1 bag2+        | isEmptyBag bag1 && isEmptyBag bag2 = Nothing+        | isEmptyBag bag1                    = srch (not b) v2 v1 bag2 bag1+        | otherwise = +            let (Node sr chl, bag1') = splitBag bag1+                sl = srWords sr AllSenses+            in  if v2 `containsResult` sl+                  then Just $ if b +                                then (reverse v1, sr, drop 1 $ dropWhile ((/=sl) . flip srWords AllSenses) v2) +                                else (reverse $ drop 1 $ dropWhile ((/=sl) . flip srWords AllSenses) v2, sr, v1)+                  else srch (not b)+                            v2 (addResult v1 sr) +                            bag2 (addListToBag bag1' chl) -- flip the order :)+  in  srch True [] [] (addToBag emptyBg t1) (addToBag emptyBg t2)+  where+    containsResult v sl = sl `elem` map (flip srWords AllSenses) v+    addResult v sr = sr:v++personTree       = runWordNetQuiet (closureOn Hypernym (head $ search "person"       Noun AllSenses))+organizationTree = runWordNetQuiet (closureOn Hypernym (head $ search "organization" Noun AllSenses))+
+ NLP/WordNet/Common.hs view
@@ -0,0 +1,16 @@+module NLP.WordNet.Common+    (+     module Data.Char,+     module Data.List,+     module Data.Maybe,+     module Control.Exception,+     module Control.Monad,+    )+    where++import Prelude hiding (catch)+import Data.Char+import Data.List+import Data.Maybe+import Control.Exception+import Control.Monad
+ NLP/WordNet/Consts.hs view
@@ -0,0 +1,30 @@+module NLP.WordNet.Consts where++import Data.List (intersperse)++#if defined (UNIX)+dictDir         = "/dict"+defaultPath     = "/usr/local/WordNet-2.0/dict"+defaultBin      = "/usr/local/WordNet-2.0/bin"++makePath = concat . intersperse "/"+#elif defined (PC)+dictDir         = "\\dict"+defaultPath	= "c:\\WordNet 2.0\\dict"+defaultBin      = "c:\\WordNet 2.0\\bin"++makePath = concat . intersperse "\\"+#elif defined (MAC)+dictDir         = ":Database"+defaultPath     = ":Database"+defaultBin      = ":"++makePath = concat . intersperse ":"+#else+-- guess unix style+dictDir         = "/dict"+defaultPath     = "/usr/local/WordNet-2.0/dict"+defaultBin      = "/usr/local/WordNet-2.0/bin"++makePath = concat . intersperse "/"+#endif
+ NLP/WordNet/PrimTypes.hs view
@@ -0,0 +1,205 @@+module NLP.WordNet.PrimTypes where++import Data.Array+import System.IO+import Control.Exception+import Data.Dynamic (Typeable)++type Offset = Integer++-- | The basic part of speech type, either a 'Noun', 'Verb', 'Adj'ective or 'Adv'erb.+data POS = Noun | Verb | Adj | Adv+         deriving (Eq, Ord, Show, Ix, Typeable)+allPOS = [Noun ..]++data EPOS = POS POS | Satellite | AdjSatellite | IndirectAnt | DirectAnt | UnknownEPos | Pertainym+          deriving (Eq, Ord, Typeable)++fromEPOS (POS p) = p+fromEPOS _ = Adj++allEPOS = [POS Noun ..]++instance Enum POS where+  toEnum 1 = Noun+  toEnum 2 = Verb+  toEnum 3 = Adj+  toEnum 4 = Adv+  fromEnum Noun = 1+  fromEnum Verb = 2+  fromEnum Adj  = 3+  fromEnum Adv  = 4+  enumFrom i = enumFromTo i Adv+  enumFromThen i j = enumFromThenTo i j Adv++instance Enum EPOS where+  toEnum 1 = POS Noun+  toEnum 2 = POS Verb+  toEnum 3 = POS Adj+  toEnum 4 = POS Adv+  toEnum 5 = Satellite+  toEnum 6 = AdjSatellite+  fromEnum (POS Noun) = 1+  fromEnum (POS Verb) = 2+  fromEnum (POS Adj)  = 3+  fromEnum (POS Adv)  = 4+  fromEnum Satellite  = 5+  fromEnum AdjSatellite = 6+  enumFrom i = enumFromTo i AdjSatellite+  enumFromThen i j = enumFromThenTo i j AdjSatellite++instance Show EPOS where+  showsPrec i (POS p) = showsPrec i p+  showsPrec i (Satellite) = showString "Satellite"+  showsPrec i (AdjSatellite) = showString "AdjSatellite"+  showsPrec i (IndirectAnt) = showString "IndirectAnt"+  showsPrec i (DirectAnt) = showString "DirectAnt"+  showsPrec i (Pertainym) = showString "Pertainym"+  showsPrec i (UnknownEPos) = showString "UnknownEPos"++instance Ix EPOS where+  range (i,j) = [i..j]+  index (i,j) a = fromEnum a - fromEnum i+  inRange (i,j) a = a `elem` [i..j]+++readEPOS :: String -> EPOS+readEPOS "n" = POS Noun+readEPOS "v" = POS Verb+readEPOS "a" = POS Adj+readEPOS "r" = POS Adv+readEPOS "s" = Satellite++data WordNetEnv =+     WordNetEnv {+       dataHandles :: Array POS (Handle, Handle), -- index, real+       excHandles  :: Array POS Handle, -- for morphology+       senseHandle, +       countListHandle,+       keyIndexHandle,+       revKeyIndexHandle :: Maybe Handle,  -- these are all optional+       vSentHandle :: Maybe (Handle, Handle), -- index, real+       wnReleaseVersion :: Maybe String,+       dataDirectory :: FilePath,+       warnAbout :: String -> Exception -> IO ()+     }++wordNetEnv0 = WordNetEnv { +                dataHandles = undefined,+                excHandles  = undefined,+                senseHandle = Nothing, +                countListHandle = Nothing,+                keyIndexHandle = Nothing,+                revKeyIndexHandle = Nothing,+                vSentHandle = Nothing,+                wnReleaseVersion = Nothing,+                dataDirectory = "",+                warnAbout = \_ _ -> return ()+              }++data SenseKey = +     SenseKey {+       senseKeyPOS :: POS, +       senseKeyString :: String,+       senseKeyWord :: String+     } deriving (Show, Typeable)+++-- | A 'Key' is a simple pointer into the database, which can be+-- followed using 'lookupKey'.+newtype Key = Key (Offset, POS) deriving (Eq, Typeable)++data Synset =+     Synset {+       hereIAm :: Offset,+       ssType :: EPOS,+       fnum :: Int,+       pos :: EPOS,+       ssWords :: [(String, Int, SenseType)], -- (word, lex-id, sense)+       whichWord :: Maybe Int,+       forms :: [(Form, Offset, EPOS, Int, Int)],+       frames :: [(Int, Int)],+       defn :: String,+       key :: Maybe Offset,++       searchType :: Int,+       headWord :: String,+       headSense :: SenseType+     } -- deriving (Show)++synset0 = Synset 0 UnknownEPos (-1) undefined [] Nothing [] [] "" Nothing (-1) "" AllSenses++-- | The basic type which holds search results.  Its 'Show' instance simply+-- shows the string corresponding to the associated WordNet synset.+data SearchResult =+     SearchResult {+       srSenseKey  :: Maybe SenseKey,+       -- | This provides (maybe) the associated overview for a SearchResult.+       -- The 'Overview' is only available if this 'SearchResult' was+       -- derived from a real search, rather than 'lookupKey'.+       srOverview  :: Maybe Overview,+       srIndex     :: Maybe Index,+       -- | This provides (maybe) the associated sense number for a SearchResult.+       -- The 'SenseType' is only available if this 'SearchResult' was+       -- derived from a real search, rather than 'lookupKey'.+       srSenseNum  :: Maybe SenseType,+       srSynset    :: Synset+     } deriving (Typeable)++data Index = +     Index {+       indexWord :: String,+       indexPOS :: EPOS,+       indexSenseCount :: Int,+       indexForms :: [Form],+       indexTaggedCount :: Int,+       indexOffsets :: [Offset]+     } deriving (Eq, Ord, Show, Typeable)++index0 = Index "" (POS Noun) (-1) [] (-1) []++-- | The different types of relations which can hold between WordNet Synsets.+data Form = Antonym | Hypernym | Hyponym | Entailment | Similar+          | IsMember | IsStuff | IsPart+          | HasMember | HasStuff | HasPart+          | Meronym | Holonym | CauseTo | PPL | SeeAlso+          | Attribute | VerbGroup | Derivation | Classification | Class | Nominalization+          -- misc:+          | Syns | Freq | Frames | Coords | Relatives | HMeronym | HHolonym | WNGrep | OverviewForm+          | Unknown+          deriving (Eq, Ord, Show, Enum, Typeable)+++-- | A 'SenseType' is a way of controlling search.  Either you specify+-- a certain sense (using @SenseNumber n@, or, since 'SenseType' is an+-- instance of 'Num', you can juse use @n@) or by searching using all+-- senses, through 'AllSenses'.  The 'Num' instance performs standard+-- arithmetic on 'SenseNumber's, and 'fromInteger' yields a 'SenseNumber' (always),+-- but any arithmetic involving 'AllSenses' returns 'AllSenses'.+data SenseType = AllSenses | SenseNumber Int deriving (Eq, Ord, Show, Typeable)++instance Num SenseType where+  fromInteger = SenseNumber . fromInteger+  SenseNumber n + SenseNumber m = SenseNumber $ n+m+  _ + _ = AllSenses+  SenseNumber n - SenseNumber m = SenseNumber $ n-m+  _ - _ = AllSenses+  SenseNumber n * SenseNumber m = SenseNumber $ n*m+  _ * _ = AllSenses+  negate (SenseNumber n) = SenseNumber $ negate n+  negate x = x+  abs (SenseNumber n) = SenseNumber $ abs n+  abs x = x+  signum (SenseNumber n) = SenseNumber $ signum n+  signum x = x+  ++-- | The 'Overview' type is the return type which gives you an+-- overview of a word, for all sense and for all parts of speech.+data Overview =+     Overview {+       nounIndex,+       verbIndex,+       adjIndex,+       advIndex :: Maybe Index+     } deriving (Eq, Ord, Show, Typeable)
+ NLP/WordNet/Prims.hs view
@@ -0,0 +1,309 @@+-- | NLP.WordNet.Prims provides primitive operations over the word net database.+-- The general scheme of things is to call 'initializeWordNet' to get a 'WordNetEnv'.+-- Once you have this, you can start querying.  A query usually looks like (suppose+-- we want "Dog" as a Noun:+--+-- 'getIndexString' on "Dog".  This will give us back a cannonicalized string, in this+-- case, still "dog".  We then use 'indexLookup' to get back an index for this string.+-- Then, we call 'indexToSenseKey' to with the index and the sense number (the Index+-- contains the number of senses) to get back a SenseKey.  We finally call+-- 'getSynsetForSense' on the sense key to get back a Synset.+--+-- We can continue to query like this or we can use the offsets provided in the+-- various fields of the Synset to query directly on an offset.  Given an offset+-- and a part of speech, we can use 'readSynset' directly to get a synset (instead+-- of going through all this business with indices and sensekeys.+module NLP.WordNet.Prims+    (+     initializeWordNet,+     initializeWordNetWithOptions,+     closeWordNet,+     getIndexString,+     getSynsetForSense,+     readSynset,+     indexToSenseKey,+     indexLookup+    )+    where++import System.IO -- hiding (try, catch)+import System.Environment+import Numeric (readHex, readDec)+import Data.Char (toLower, isSpace)+import Data.Array+import Control.Exception+import Control.Monad (when, liftM, mplus)+import Data.List (findIndex, find)+import Data.Maybe (isNothing, fromJust, isJust, fromMaybe)+import GHC.Handle -- (openFileEx, BinaryMode(..))++import NLP.WordNet.PrimTypes+import NLP.WordNet.Util+import NLP.WordNet.Consts++-- | initializeWordNet looks for the word net data files in the+-- default directories, starting with environment variables WNSEARCHDIR+-- and WNHOME, and then falling back to 'defaultPath' as defined in+-- NLP.WordNet.Consts.+initializeWordNet :: IO WordNetEnv+initializeWordNet =+  initializeWordNetWithOptions Nothing Nothing+++-- | initializeWordNetWithOptions looks for the word net data files in the+-- specified directory.  Use this if wordnet is installed in a non-standard+-- place on your machine and you don't have the appropriate env vars set up.+initializeWordNetWithOptions :: +    Maybe FilePath ->                          -- word net data directory+    Maybe (String -> Exception -> IO ()) ->    -- "warning" function (by default, warnings go to stderr)+      IO WordNetEnv+initializeWordNetWithOptions mSearchdir mWarn = do+  searchdir <- case mSearchdir of { Nothing -> getDefaultSearchDir ; Just d -> return d }+  let warn = fromMaybe (\s e -> hPutStrLn stderr (s ++ "\n" ++ show e)) mWarn+  version <- tryMaybe $ getEnv "WNDBVERSION"+  dHands <- mapM (\pos -> do+                  idxH  <- openFileEx+                             (makePath [searchdir, "index." ++ partName pos])+                             (BinaryMode ReadMode)+                  dataH <- openFileEx +                             (makePath [searchdir, "data." ++ partName pos])+                             (BinaryMode ReadMode)+                  return (idxH, dataH)+                 ) allPOS+  -- the following are unnecessary+  sense   <- tryMaybeWarn (warn "Warning: initializeWordNet: cannot open file index.sense")+                        $ openFileEx (makePath [searchdir, "index.sense"]) (BinaryMode ReadMode)+  cntlst  <- tryMaybeWarn (warn "Warning: initializeWordNet: cannot open file cntlist.rev")+                        $ openFileEx (makePath [searchdir, "cntlist.rev"]) (BinaryMode ReadMode)+  keyidx  <- tryMaybeWarn (warn "Warning: initializeWordNet: cannot open file index.key")+                        $ openFileEx (makePath [searchdir, "index.key"  ]) (BinaryMode ReadMode)+  rkeyidx <- tryMaybeWarn (warn "Warning: initializeWordNet: cannot open file index.key.rev")+                        $ openFileEx (makePath [searchdir, "index.key.rev"]) (BinaryMode ReadMode)+  vsent   <- tryMaybeWarn (warn "Warning: initializeWordNet: cannot open sentence files (sentidx.vrb and sents.vrb)")+                        $ do+               idx <- openFileEx (makePath [searchdir, "sentidx.vrb"]) (BinaryMode ReadMode)+               snt <- openFileEx (makePath [searchdir, "sents.vrb"  ]) (BinaryMode ReadMode)+               return (idx, snt)+  mHands <- mapM (\pos -> openFileEx +                            (makePath [searchdir, partName pos ++ ".exc"])+                            (BinaryMode ReadMode)) allPOS+  return $ WordNetEnv +              { dataHandles = listArray (Noun, Adv) dHands,+                excHandles  = listArray (Noun, Adv) mHands,+                senseHandle = sense,+                countListHandle = cntlst,+                keyIndexHandle = keyidx,+                revKeyIndexHandle = rkeyidx,+                vSentHandle = vsent,+                wnReleaseVersion = version,+                dataDirectory = searchdir,+                warnAbout = warn+              }+  where+    getDefaultSearchDir = do+      Just searchdir <- tryMaybe (getEnv "WNSEARCHDIR") >>= \m1 ->+                        tryMaybe (getEnv "WNHOME") >>= \m2 ->+                        return (m1 `mplus` +                                liftM (++dictDir) m2 `mplus` +                                Just defaultPath)+      return searchdir++-- | closeWordNet is not strictly necessary.  However, a 'WordNetEnv' tends to+-- hog a few Handles, so if you run out of Handles and won't be using+-- your WordNetEnv for a while, you can close it and always create a new+-- one later.+closeWordNet :: WordNetEnv -> IO ()+closeWordNet wne = do+  mapM_ (\ (h1,h2) -> hClose h1 >> hClose h2) (elems (dataHandles wne))+  mapM_ hClose (elems (excHandles wne))+  mapM_ (\x -> when (isJust x) $ hClose (fromJust x))+        [senseHandle wne, countListHandle wne, keyIndexHandle wne,+         revKeyIndexHandle wne, liftM fst (vSentHandle wne), liftM snd (vSentHandle wne)]++-- | getIndexString takes a string and a part of speech and tries to find+-- that string (or something like it) in the database.  It is essentially+-- a cannonicalization routine and should be used before querying the+-- database, to ensure that your string is in the right form.+getIndexString :: WordNetEnv -> String -> POS -> IO (Maybe String)+getIndexString wne str partOfSpeech = getIndexString' . cannonWNString $ str+  where+    getIndexString' [] = return Nothing+    getIndexString' (s:ss) = do+      i <- binarySearch (fst (dataHandles wne ! partOfSpeech)) s+      if isJust i+        then return (Just s)+        else getIndexString' ss++-- | getSynsetForSense takes a sensekey and finds the appropriate Synset.  SenseKeys can+-- be built using indexToSenseKey.+getSynsetForSense :: WordNetEnv -> SenseKey -> IO (Maybe Synset)+getSynsetForSense wne _ | isNothing (senseHandle wne) = ioError $ userError "no sense dictionary"+getSynsetForSense wne key = do+  l <- binarySearch+         (fromJust $ senseHandle wne)+         (senseKeyString key) -- ++ " " ++ charForPOS (senseKeyPOS key))+  case l of+    Nothing -> return Nothing+    Just l  -> do offset <- maybeRead $ takeWhile (not . isSpace) $+                               drop 1 $ dropWhile (not . isSpace) l+                  ss <- readSynset wne (senseKeyPOS key) offset (senseKeyWord key)+                  return (Just ss)++-- | readSynset takes a part of speech, and an offset (the offset can be found+-- in another Synset) and (perhaps) a word we're looking for (this is optional)+-- and will return its Synset.+readSynset :: WordNetEnv -> POS -> Offset -> String -> IO Synset+readSynset wne searchPos offset w = do+  let h = snd (dataHandles wne ! searchPos)+  hSeek h AbsoluteSeek offset+  toks <- liftM words $ hGetLine h+  --print toks+  (ptrTokS:fnumS:posS:numWordsS:rest1) <- matchN 4 toks+  hiam <- maybeRead ptrTokS+  fn   <- maybeRead fnumS+  let ss1 = synset0 { hereIAm = hiam,+                      pos = readEPOS posS,+                      fnum = fn,+                      ssType = if readEPOS posS == Satellite then IndirectAnt else UnknownEPos+                   }+  let numWords = case readHex numWordsS of+                   (n,_):_ -> n+                   _       -> 0+--read numWordsS+  let (wrds,ptrCountS:rest2) = splitAt (numWords*2) rest1  -- words and lexids+  let ptrCount = +        case readDec ptrCountS of+          (n,_):_ -> n+          _       -> 0+--  print (toks, ptrCountS, ptrCount)+  wrds' <- readWords ss1 wrds+  let ss2 = ss1 { ssWords = wrds',+                  whichWord = findIndex (==w) wrds }+  let (ptrs,rest3) = splitAt (ptrCount*4) rest2+  let (fp,ss3) = readPtrs (False,ss2) ptrs+  let ss4 = if fp && searchPos == Adj && ssType ss3 == UnknownEPos+              then ss3 { ssType = Pertainym }+              else ss3+  let (ss5,rest4) = +        if searchPos /= Verb +          then (ss4, rest3) +          else let (fcountS:rest4) = rest3+                   (synPtrs, rest5) = splitAt (read fcountS * 3) rest4+               in  (ss4, rest5)++  let ss6 = ss5 { defn = unwords $ drop 1 rest4 }++  return ss6+  where+    readWords ss (w:lid:xs) = do+      let s = map toLower $ replaceChar ' ' '_' w+      idx  <- indexLookup wne s (fromEPOS $ pos ss)+--      print (w,st,idx)+      let posn = case idx of+                   Nothing -> Nothing+                   Just ix -> findIndex (==hereIAm ss) (indexOffsets ix)+      rest <- readWords ss xs+      return ((w, fst $ head $ readHex lid, maybe AllSenses SenseNumber posn) : rest)+    readWords _ _ = return []+    readPtrs (fp,ss) (typ:off:ppos:lexp:xs) = +      let (fp',ss') = readPtrs (fp,ss) xs+          this = (getPointerType typ,+                  read off,+                  readEPOS ppos,+                  fst $ head $ readHex (take 2 lexp),+                  fst $ head $ readHex (drop 2 lexp))+      in  if searchPos == Adj && ssType ss' == UnknownEPos+            then if getPointerType typ == Antonym+                   then (fp' , ss' { forms = this : forms ss',+                                     ssType   = DirectAnt })+                   else (True, ss' { forms = this : forms ss' })+            else (fp', ss' { forms = this : forms ss' })+    readPtrs (fp,ss) _ = (fp,ss)++-- | indexToSenseKey takes an Index (as returned by, ex., indexLookup) and a sense+-- number and returns a SenseKey for that sense.+indexToSenseKey :: WordNetEnv -> Index -> Int -> IO (Maybe SenseKey)+indexToSenseKey wne idx sense = do+  let cpos = fromEPOS $ indexPOS idx+  ss1 <- readSynset wne cpos (indexOffsets idx !! (sense-1)) ""+  ss2 <- followSatellites ss1+  --print ss2+  case findIndex ((==indexWord idx) . map toLower) (map (\ (w,_,_) -> w) $ ssWords ss2) of+    Nothing -> return Nothing+    Just  j -> do+      let skey = if ssType ss2 == Satellite+                   then indexWord idx ++ "%" ++ show (fromEnum Satellite) ++ ":" +++                        padTo 2 (show $ fnum ss2) ++ ":" ++ headWord ss2 ++ ":" +++                        padTo 2 (show $ headSense ss2)+                   else indexWord idx ++ "%" ++ show (fromEnum $ pos ss2) ++ ":" +++                        padTo 2 (show $ fnum ss2) ++ ":" ++ +                        padTo 2 (show $ lexId ss2 j) ++ "::"+      return (Just $ SenseKey cpos skey (indexWord idx))+  where+    followSatellites ss +        | ssType ss == Satellite =+            case find (\ (f,_,_,_,_) -> f == Similar) (forms ss) of+              Nothing -> return ss+              Just (f,offset,p,j,k) -> do+                adjss <- readSynset wne (fromEPOS p) offset ""+                case ssWords adjss of+                  (hw,_,hs):_ -> return (ss { headWord  = map toLower hw,+                                              headSense = hs })+                  _ -> return ss+        | otherwise = return ss++-- indexLookup takes a word and part of speech and gives back its index.+indexLookup :: WordNetEnv -> String -> POS -> IO (Maybe Index)+indexLookup wne w pos = do+  ml <- binarySearch (fst (dataHandles wne ! pos)) w+  case ml of+    Nothing -> return Nothing+    Just  l -> do+      (wdS:posS:ccntS:pcntS:rest1) <- matchN 4 (words l)+      isc <- maybeRead ccntS+      pc  <- maybeRead pcntS+      let idx1 = index0 { indexWord = wdS,+                          indexPOS  = readEPOS posS,+                          indexSenseCount = isc+                        }+      let (ptrs,rest2) = splitAt pc rest1+      let idx2 = idx1 { indexForms = map getPointerType ptrs }+      (ocntS:tcntS:rest3) <- matchN 2 rest2+      itc <- maybeRead tcntS+      otc <- maybeRead ocntS+      let idx3 = idx2 { indexTaggedCount = itc }+      let (offsets,_) = splitAt otc rest3+      io <- mapM maybeRead offsets+      return (Just $ idx3 { indexOffsets = io })++-- do binary search on an index file+binarySearch :: Handle -> String -> IO (Maybe String)+binarySearch h s = do+  hSeek h SeekFromEnd 0+  bot <- hTell h+  binarySearch' 0 bot (bot `div` 2)+  where+    binarySearch' :: Integer -> Integer -> Integer -> IO (Maybe String)+    binarySearch' top bot mid = do+      hSeek h AbsoluteSeek (mid-1)+      when (mid /= 1) readUntilNL+      eof <- hIsEOF h+      if eof +        then if top >= bot-1 +               then return Nothing+               else binarySearch' top (bot-1) ((top+bot-1) `div` 2)+        else do+          l <- hGetLine h+          let key = takeWhile (/=' ') l+          if key == s +            then return (Just l)+            else case (bot - top) `div` 2 of+                   0 -> return Nothing+                   d -> case key `compare` s of+                          LT -> binarySearch' mid bot (mid + d)+                          GT -> binarySearch' top mid (top + d)+    readUntilNL = do+      eof <- hIsEOF h+      if eof +        then return ()+        else do hGetLine h; return ()
+ NLP/WordNet/Types.hs view
@@ -0,0 +1,191 @@+-----------------------------------------------------------------------------+-- |+-- Module      :  NLP.WordNet.Types+-- Copyright   :  (c) Hal Daume III 2003-2004+-- License     :  BSD-style+--+-- Maintainer  :  hdaume@isi.edu+-- Stability   :  experimental+-- Portability :  non-portable (H98 + implicit parameters)+--+-- This module is maintained at:+--    <http://www.isi.edu/~hdaume/HWordNet/>.+--+-- This module describes the types used in the purely functional interface.+--+-- More information about WordNet is available at:+--    <http://http://www.cogsci.princeton.edu/~wn/>.+-------------------------------------------------------------------------------+module NLP.WordNet.Types+    (+     -- * The wrapper type for wordnet functions.+     WN(),++     -- * The basic Word type (just a 'String').+     Word,++     -- * The part of speech type.+     POS(..),++     -- * The type, and functions dealing with overview searches.+     Overview(),+       numNounSenses,+       numVerbSenses,+       numAdjSenses,+       numAdvSenses,+       taggedCountNounSenses, +       taggedCountVerbSenses, +       taggedCountAdjSenses, +       taggedCountAdvSenses,++     -- * The type, and functions dealing with the word net environment.+     WordNetEnv(),+       getReleaseVersion,+       getDataDirectory,++     -- The type, and functions dealing with senses.+--     SenseKey(),+--       senseKeyPOS,+--       senseKeyWord,++     -- * The type to control which sense a search is looking at.+     SenseType(..),++     -- * The type, and functions dealing with search results.+     SearchResult(),+       srOverview,+       srSenseNum,+       srPOS,+       srDefinition,+       srSenses,+       srWords,+       srForms,+       srFormKeys,+       srToKey,++     -- The type, and functions dealing with indices.+--     Index(),+--       indexWord,+--       indexPOS,++     -- * A sum type of the different relations which can hold between words.+     Form(..),++     -- * A simple key into the database.+     Key(),+    )+    where++import System.IO.Unsafe+import Data.List+import Data.Maybe++import NLP.WordNet.PrimTypes+import NLP.WordNet.Util++-- | In actuality this type is:+--+-- > type WN a = (?wne :: WordNetEnv) => a+-- +-- but Haddock cannot parse this at this time.+-- type WN a = a+type WN a = (?wne :: WordNetEnv) => a++-- | A Word is just a String.+type Word = String++-- basically, all we do is wrap the SenseKey, Synset and WordNetEnv+-- datatypes in a bunch of useful functions++-- | This will give you the current release of the WordNet databases+-- we are using (if we know).+getReleaseVersion :: WN (Maybe String)+getReleaseVersion = wnReleaseVersion ?wne++-- | This will give you the directory from which the databases are being read.+getDataDirectory :: WN FilePath+getDataDirectory = dataDirectory ?wne+++-- | Given an 'Overview', this will tell you how many noun senses the searched-for word has.+numNounSenses :: Overview -> Int+numNounSenses = maybe 0 indexSenseCount . nounIndex++-- | Given an 'Overview', this will tell you how many verb senses the searched-for word has.+numVerbSenses :: Overview -> Int+numVerbSenses = maybe 0 indexSenseCount . verbIndex++-- | Given an 'Overview', this will tell you how many adjective senses the searched-for word has.+numAdjSenses :: Overview -> Int+numAdjSenses  = maybe 0 indexSenseCount . adjIndex++-- | Given an 'Overview', this will tell you how many adverb senses the searched-for word has.+numAdvSenses :: Overview -> Int+numAdvSenses  = maybe 0 indexSenseCount . advIndex+++-- | Given an 'Overview', this will tell you how many times this word was tagged as a noun.+taggedCountNounSenses :: Overview -> Int+taggedCountNounSenses = maybe 0 indexTaggedCount . nounIndex++-- | Given an 'Overview', this will tell you how many times this word was tagged as a verb.+taggedCountVerbSenses :: Overview -> Int+taggedCountVerbSenses = maybe 0 indexTaggedCount . verbIndex++-- | Given an 'Overview', this will tell you how many times this word was tagged as an adjective.+taggedCountAdjSenses :: Overview -> Int+taggedCountAdjSenses  = maybe 0 indexTaggedCount . adjIndex++-- | Given an 'Overview', this will tell you how many times this word was tagged as an adverb.+taggedCountAdvSenses :: Overview -> Int+taggedCountAdvSenses  = maybe 0 indexTaggedCount . advIndex++-- | This gives the part of speech of a 'SearchResult'+srPOS :: SearchResult -> POS+srPOS sr = case pos (srSynset sr) of { POS p -> p; _ -> Adj }++-- | This gives the definition of the sense of a word in a 'SearchResult'.+srDefinition :: SearchResult -> String+srDefinition = defn . srSynset++-- | This gives a list of senses the word has.+srSenses :: SearchResult -> [SenseType]+srSenses = nub . map thr3 . ssWords . srSynset++-- | This gives the actual words used to describe the Synset of a search result.+srWords :: SearchResult -> SenseType -> [Word]+srWords sr t = nub . map fst3 . filter ((isType t) . thr3) . ssWords . srSynset $ sr+  where isType AllSenses _ = True+        isType _ AllSenses = True+        isType (SenseNumber n) (SenseNumber m) = n == m++-- | This gives all the 'Form's a word has (i.e., what sort of relations hold between+-- it and other words.+srForms :: SearchResult -> [Form]+srForms = nub . map (\ (f,_,_,_,_) -> f) . forms . srSynset++-- | This provides a 'Key' (which can be searched for using 'lookupKey') for+-- a 'SearchResult' under a given form.  For instance, it can be used to+-- get all 'Hypernym's of a given word.+srFormKeys :: SearchResult -> Form -> [Key]+srFormKeys sr f = nub . mapMaybe mkKey . filter (\ (f',_,_,_,_) -> f == f') . forms . srSynset $ sr+  where mkKey (_,o,POS p,_,_) = Just $ Key (o,p)+        mkKey (_,o,_,_,_)     =+          case srSenseKey sr of+            Nothing -> Nothing+            Just sk -> Just $ Key (o, senseKeyPOS sk)++-- | This converts a 'SearchResult' into a 'Key'.+srToKey :: SearchResult -> Key  -- returns the key associated with this search result+srToKey sr = Key (hereIAm $ srSynset sr, p)+  where p = case srSenseKey sr of+              Just sk -> senseKeyPOS sk+              Nothing -> case pos (srSynset sr) of+                           POS pp -> pp+                           _ -> case ssType (srSynset sr) of+                                  POS pp -> pp+                                  _      -> Adj++instance Show SearchResult where+  showsPrec i (SearchResult { srSynset = ss }) =+    showChar '<' . showString (unwords $ map fst3 $ ssWords ss) . showChar '>'
+ NLP/WordNet/Util.hs view
@@ -0,0 +1,103 @@+module NLP.WordNet.Util where++import NLP.WordNet.PrimTypes++import Prelude hiding (catch)+import Control.Exception+import Data.Char (toLower)+import Data.List (nub)+import Data.Maybe (fromMaybe)+import GHC.Handle++data IOModeEx = BinaryMode IOMode | AsciiMode IOMode deriving (Eq, Ord, Show, Read)++openFileEx fp (BinaryMode md) = openBinaryFile fp md+openFileEx fp (AsciiMode  md) = openFile fp md+++fst3 (a,_,_) = a+snd3 (_,b,_) = b+thr3 (_,_,c) = c++maybeRead :: (Read a, Monad m) => String -> m a+maybeRead s = +  case readsPrec 0 s of+    (a,_):_ -> return a+    _       -> fail "error parsing string"++matchN :: Monad m => Int -> [a] -> m [a]+matchN n l | length l >= n = return l+           | otherwise     = fail "expecting more tokens"++lexId x n = (\ (_,i,_) -> i) $ (ssWords x !! n)+padTo n s = reverse $ take n $ (reverse s ++ repeat '0')+      +sensesOf :: Int {- num senses -} -> SenseType -> [Int]+sensesOf n AllSenses = [1..n]+sensesOf n (SenseNumber i)+    | i <= 0 = []+    | i >  n = []+    | otherwise = [i]++-- utility functions++charForPOS (Noun) = "n"+charForPOS (Verb) = "v"+charForPOS (Adj)  = "a"+charForPOS (Adv)  = "r"++tryMaybe :: IO a -> IO (Maybe a)+tryMaybe a = (a >>= return . Just) `catch` (const (return Nothing))++tryMaybeWarn :: (Exception -> IO ()) -> IO a -> IO (Maybe a)+tryMaybeWarn warn a = (a >>= return . Just) `catch` (\e -> warn e >> return Nothing)++partName :: POS -> String+partName = map toLower . show++cannonWNString :: String -> [String]+cannonWNString s'+    | not ('_' `elem` s) &&+      not ('-' `elem` s) &&+      not ('.' `elem` s) = [s]+    | otherwise = +        nub [s, +             replaceChar '_' '-' s,+             replaceChar '-' '_' s,+             filter (not . (`elem` "_-")) s,+             filter (/='.') s+            ]+  where s = map toLower s'++replaceChar from to [] = []+replaceChar from to (c:cs)+    | c == from = to : replaceChar from to cs+    | otherwise = c  : replaceChar from to cs++getPointerType s = fromMaybe Unknown $ lookup s l+  where+    l = +       [("!",   Antonym),+        ("@",   Hypernym),+        ("~",   Hyponym),+        ("*",   Entailment),+        ("&",   Similar),+        ("#m",  IsMember),+        ("#s",  IsStuff),+        ("#p",  IsPart),+        ("%m",  HasMember),+        ("%s",  HasStuff),+        ("%p",  HasPart),+        ("%",   Meronym),+        ("#",   Holonym),+        (">",   CauseTo),+        ("<",   PPL),+        ("^",   SeeAlso),+--        ("\\",  Pertainym),+        ("=",   Attribute),+        ("$",   VerbGroup),+        ("+",   Nominalization),+        (";",   Classification),+        ("-",   Class),+        -- additional searches, but not pointers.+        ("+",    Frames)]
+ Setup.lhs view
@@ -0,0 +1,4 @@+#! /usr/bin/env runhaskell++> import Distribution.Simple+> main = defaultMain
+ WordNet.cabal view
@@ -0,0 +1,14 @@+Name:               WordNet+Version:            0.1.1+Description:        A pure-Haskell interface to the WordNet lexical database of English. Depends on the WordNet database, but not on the WordNet source code.+Category:           natural-language processing, text+Synopsis:           Haskell interface to the WordNet database+License:            BSD3+License-file:       LICENSE+Author:             Hal Daume III <me@hal3.name>+Maintainer:         Max Rabkin <max.rabkin@gmail.com>+Build-Depends:      base+Exposed-Modules:    NLP.WordNet+Other-Modules:      NLP.WordNet.Common, NLP.WordNet.PrimTypes, NLP.WordNet.Util, NLP.WordNet.Consts, NLP.WordNet.Prims, NLP.WordNet.Types+Build-Type:         Simple+GHC-Options:        -cpp -fglasgow-exts