WordNet (empty) → 0.1.1
raw patch · 10 files changed
+1240/−0 lines, 10 filesdep +basesetup-changed
Dependencies added: base
Files
- LICENSE +30/−0
- NLP/WordNet.hs +338/−0
- NLP/WordNet/Common.hs +16/−0
- NLP/WordNet/Consts.hs +30/−0
- NLP/WordNet/PrimTypes.hs +205/−0
- NLP/WordNet/Prims.hs +309/−0
- NLP/WordNet/Types.hs +191/−0
- NLP/WordNet/Util.hs +103/−0
- Setup.lhs +4/−0
- WordNet.cabal +14/−0
+ 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