packages feed

partage (empty) → 0.1.0.0

raw patch · 20 files changed

+3983/−0 lines, 20 filesdep +HUnitdep +PSQueuedep +basesetup-changed

Dependencies added: HUnit, PSQueue, base, containers, data-lens-light, data-partition, dawg-ord, mmorph, mtl, partage, pipes, random, tasty, tasty-hunit, transformers, vector

Files

+ LICENSE view
@@ -0,0 +1,23 @@+Copyright (c) 2015-2016, Jakub Waszczuk+All rights reserved.++Redistribution and use in source and binary forms, with or without modification,+are permitted provided that the following conditions are met:++* Redistributions of source code must retain the above copyright notice, this+  list of conditions and the following disclaimer.++* 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.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 COPYRIGHT HOLDER 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.
+ Setup.lhs view
@@ -0,0 +1,4 @@+#! /usr/bin/env runhaskell++> import Distribution.Simple+> main = defaultMain
+ partage.cabal view
@@ -0,0 +1,80 @@+name:               partage+version:            0.1.0.0+synopsis:           Parsing factorized +description:+    Parsing factorized tree adjoining grammars.+license:            BSD3+license-file:       LICENSE+cabal-version:      >= 1.10+copyright:          Copyright (c) 2015-2016 Jakub Waszczuk+author:             Jakub Waszczuk+maintainer:         waszczuk.kuba@gmail.com+stability:          experimental+category:           Natural Language Processing+homepage:           https://github.com/kawu/partage+build-type:         Simple++library+    default-language:+        Haskell2010+    hs-source-dirs: src+    build-depends:+        base                >= 4        && < 5+      , containers          >= 0.5      && < 0.6+      , mtl                 >= 2.1      && < 2.3+      , transformers        >= 0.3      && < 0.5+      , pipes               >= 4.1      && < 4.2+      , PSQueue             >= 1.1      && < 1.2+      , data-partition      >= 0.3      && < 0.4+      , mmorph              >= 1.0      && < 1.1+      , dawg-ord            >= 0.5      && < 0.6+      , data-lens-light     >= 0.1      && < 0.2+      , random              >= 1.1      && < 1.2+      , vector              >= 0.10     && < 0.12++    exposed-modules:+        NLP.Partage.Tree+      , NLP.Partage.Tree.Other+      , NLP.Partage.FactGram+      , NLP.Partage.Gen+      , NLP.Partage.Auto+      , NLP.Partage.Auto.List+      , NLP.Partage.Auto.Trie+      , NLP.Partage.Auto.DAWG+      , NLP.Partage.Auto.Set+      , NLP.Partage.Earley++    other-modules:+        NLP.Partage.SOrd+      , NLP.Partage.FactGram.Internal+      , NLP.Partage.Earley.AutoAP+      , NLP.Partage.SubtreeSharing++    ghc-options: -Wall+    -- cpp-options: -DDebug+++source-repository head+    type: git+    location: https://github.com/kawu/partage.git+++test-suite test+    default-language:+        Haskell2010+    type:+        exitcode-stdio-1.0+    hs-source-dirs:+        tests+    main-is:+        test.hs+    other-modules:+        TestSet+      , Parser+    build-depends:+        partage+      , base                    >= 4        && < 5+      , containers              >= 0.5      && < 0.6+      , tasty                   >= 0.10+      , tasty-hunit             >= 0.9+      , HUnit                   >= 1.2
+ src/NLP/Partage/Auto.hs view
@@ -0,0 +1,90 @@+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE FlexibleContexts #-}+++-- | Abstract implementation of an automaton (or a set of automata,+-- in general).  `Auto` provides a minimal interface needed to+-- use automata in parsing and thus allows to use one of the+-- concrete implementations provided by the library:+--+--  * "NLP.Partage.Auto.DAWG": directed acyclic word graph+--  * "NLP.Partage.Auto.Trie": prefix tree+--  * "NLP.Partage.Auto.List": set of lists+--  * "NLP.Partage.Auto.Set": set of automata, one automaton per+--      `Head` non-terminal+++module NLP.Partage.Auto+(+-- * Automata+  Auto (..)+, Edge (..)+, GramAuto++-- * Utilities+, allIDs+, allEdges+) where+++import qualified Control.Monad.State.Strict as E++import qualified Data.Set                   as S++import           Data.DAWG.Ord (ID)+import           NLP.Partage.FactGram (Lab(..))+++-- | A datatype used to distinguish head non-terminals from body+-- non-terminals in automata-based grammar representation.+data Edge a+    = Head a+    | Body a+    deriving (Show, Eq, Ord)+++-- | Minimal automaton implementation.+-- Multiple roots are allowed in order to account for+-- list implementation of an automaton.+data Auto a = Auto+    { roots     :: S.Set ID+    -- ^ Set of automata roots+    , follow    :: ID -> a -> Maybe ID+    -- ^ Follow a transition with the given symbol from the given node+    , edges     :: ID -> [(a, ID)]+    -- ^ List of outgoing edges (transitions)+    }+++-- | Automaton type specialized to represent grammar rules.+type GramAuto n t = Auto (Edge (Lab n t))+++-- | Extract the set of underlying IDs.+allIDs :: Ord a => Auto a -> S.Set ID+allIDs d = S.fromList $ concat+    [[i, j] | (i, _, j) <- allEdges d]+++-- | Return the list of automaton transitions.+allEdges :: Ord a => Auto a -> [(ID, a, ID)]+allEdges = S.toList . walk+++-- | Traverse  the automaton and collect all the edges.+walk :: Ord a => Auto a -> S.Set (ID, a, ID)+walk Auto{..} =+    flip E.execState S.empty $+        flip E.evalStateT S.empty $+            mapM_ doit (S.toList roots)+  where+    -- The embedded state serves to store the resulting set of+    -- transitions; the surface state serves to keep track of+    -- already visited nodes.+    doit i = do+        b <- E.gets $ S.member i+        E.when (not b) $ do+            E.modify $ S.insert i+            E.forM_ (edges i) $ \(x, j) -> do+                E.lift . E.modify $ S.insert (i, x, j)+                doit j
+ src/NLP/Partage/Auto/DAWG.hs view
@@ -0,0 +1,106 @@+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE FlexibleContexts #-}+++-- | A version where the grammar is actually compressed to a form of+-- a single /directed acyclic word graph/, i.e. a+-- /minimal finite state automaton/.+++module NLP.Partage.Auto.DAWG+(+-- -- * DAWG+--   DAWG+-- , buildAuto+--+-- -- * Interface+-- , shell+  fromGram+) where+++-- import qualified Control.Monad.State.Strict as E+-- import           Control.Monad.Trans.Class (lift)++import qualified Data.Set                   as S++-- import           Data.DAWG.Ord (ID)+import qualified Data.DAWG.Ord              as D++import           NLP.Partage.FactGram+    ( FactGram, Lab(..), Rule(..) )+++import qualified NLP.Partage.Auto as A+++--------------------------------------------------+-- Interface+--------------------------------------------------+++-- -- | DAWG as automat with one parameter.+-- newtype Auto a = Auto { unAuto :: D.DAWG a () }+++-- | Abstract over the concrete representation of the grammar+-- automaton.+shell :: (Ord n, Ord t) => DAWG n t -> A.GramAuto n t+shell d = A.Auto+    { roots  = S.singleton (D.root d)+    , follow = \i x ->  D.follow i x d+    , edges  = flip D.edges d }+++-- | Build the DAWG-based representation of the given grammar.+fromGram :: (Ord n, Ord t) => FactGram n t -> A.GramAuto n t+fromGram = shell . buildAuto+++--------------------------------------------------+-- Implementation+--------------------------------------------------+++-- | The automaton-based representation of a factorized TAG+-- grammar.  Left transitions contain non-terminals belonging to+-- body non-terminals while Right transitions contain rule heads+-- non-terminals.+type DAWG n t = D.DAWG (A.Edge (Lab n t)) ()+++-- | Build automaton from the given grammar.+buildAuto :: (Ord n, Ord t) => FactGram n t -> DAWG n t+buildAuto gram = D.fromLang+    [ map A.Body bodyR ++ [A.Head headR]+    | Rule{..} <- S.toList gram ]+++-- -- | Return the list of automaton transitions.+-- edges :: (Ord n, Ord t) => DAWG n t -> [(ID, Edge (Lab n t), ID)]+-- edges = S.toList . walk+--+--+-- -- | Traverse  the automaton and collect all the edges.+-- --+-- -- TODO: it is provided in the general case in the `Mini` module.+-- -- Remove the version below.+-- walk+--     :: (Ord n, Ord t)+--     => DAWG n t+--     -> S.Set (ID, Edge (Lab n t), ID)+-- walk dawg =+--     flip E.execState S.empty $+--         flip E.evalStateT S.empty $+--             doit (D.root dawg)+--   where+--     -- The embedded state serves to store the resulting set of+--     -- transitions; the surface state serves to keep track of+--     -- already visited nodes.+--     doit i = do+--         b <- E.gets $ S.member i+--         E.when (not b) $ do+--             E.modify $ S.insert i+--             E.forM_ (D.edges i dawg) $ \(x, j) -> do+--                 E.lift . E.modify $ S.insert (i, x, j)+--                 doit j
+ src/NLP/Partage/Auto/List.hs view
@@ -0,0 +1,111 @@+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE FlexibleContexts #-}+++-- | List-based grammar representation: each rule is represented as a+-- separate, trivial automaton.+++module NLP.Partage.Auto.List+(+-- -- * ListSet+--   ListSet+-- , buildList+--+-- -- * Interface+-- , shell+  fromGram+) where+++import qualified Control.Arrow as Arr+import qualified Control.Monad.State.Strict as E++import           Data.Maybe (maybeToList)+import qualified Data.Set as S+import qualified Data.Map.Strict as M++import           Data.DAWG.Ord (ID)++import qualified NLP.Partage.Auto as A+import           NLP.Partage.FactGram (FactGram, Lab(..), Rule(..))+++-- | A single list.+type List a = Maybe (a, ID)+++-- | List-based grammar representation: each rule is represented as a+-- separate, trivial automaton.+data ListSet a = ListSet+    { rootSet   :: S.Set ID+    , listMap   :: M.Map ID (List a)+    }+++-- | Convert list to a `ListSet`.+convert :: Ord a => [[a]] -> ListSet a+convert xs0 = ListSet+    { rootSet = S.fromList rootList+    , listMap = listMap0 }+  where+    (rootList, (_, listMap0)) = E.runState+        (mapM mkList xs0)+        (0 :: Int, M.empty)+    mkList [] = do+        i <- newID+        yield i Nothing+        return i+    mkList (x:xs) = do+        i <- newID+        j <- mkList xs+        yield i $ Just (x, j)+        return i+    newID = E.state $ \(n, m) -> (n, (n + 1, m))+    yield i node = E.modify $ Arr.second (M.insert i node)+++-- | Follow symbol from the given node.+follow :: Ord a => ListSet a -> ID -> a -> Maybe ID+follow ListSet{..} i x = do+    (y, j) <- E.join $ M.lookup i listMap+    E.guard (x == y)+    return j+++-- | All edges outgoing from the given node ID.+edges :: ListSet a -> ID -> [(a, ID)]+edges ListSet{..} i+    = maybeToList . E.join+    $ M.lookup i listMap+++--------------------------------------------------+-- List from grammar+--------------------------------------------------+++-- | Build trie from the given grammar.+buildList :: (Ord n, Ord t) => FactGram n t -> [[A.Edge (Lab n t)]]+buildList gram =+    [ map A.Body bodyR ++ [A.Head headR]+    | Rule{..} <- S.toList gram ]+++--------------------------------------------------+-- Interface+--------------------------------------------------+++-- | Abstract over the concrete implementation.+shell :: (Ord n, Ord t) => [[A.Edge (Lab n t)]] -> A.GramAuto n t+shell d0 = A.Auto+    { roots  = rootSet d+    , follow = follow d+    , edges  = edges d }+    where d = convert d0+++-- | Build the list-based representation of the given grammar.+fromGram :: (Ord n, Ord t) => FactGram n t -> A.GramAuto n t+fromGram = shell . buildList
+ src/NLP/Partage/Auto/Set.hs view
@@ -0,0 +1,173 @@+{-# LANGUAGE RecordWildCards #-}+++-- | A version in which a separate automaton is built (according to+-- the underlying building function) for each distinct rule head+-- symbol, i.e. the set of rules is first partitioned w.r.t. the+-- heads of the individual rules, and then for each partition a+-- separate automaton is built.+++module NLP.Partage.Auto.Set+(+-- -- AutoSet+--   AutoSet+-- , buildAutoSet+--+-- -- * Interface+-- , shell+  fromGram+) where+++import           Control.Applicative ((<$>))+import           Control.Monad (forM)+import qualified Control.Monad.State.Strict as E+-- -- import           Control.Monad.Trans.Class (lift)++import           Data.List (foldl')+import           Data.Maybe (maybeToList)+import qualified Data.Set                   as S+import qualified Data.Map.Strict            as M++import           Data.DAWG.Ord (ID)++import           NLP.Partage.FactGram+    ( FactGram, Lab(..), Rule(..) )+++import qualified NLP.Partage.Auto as A+++--------------------------------------------------+-- Interface+--------------------------------------------------+++-- | Abstract over the concrete implementation of automaton.+shell :: (Ord a) => AutoSet a -> A.Auto a+shell AutoSet{..} = A.Auto+    { roots  = S.fromList+             . map unExID+             . S.toList $ rootSet+             -- we could note in the specification of the+             -- `Mini.Auto` that it doesn't have to be very+             -- efficient because it is run only once per+             -- parsing session+    , follow = \e x -> do+        (autoID, i) <- M.lookup (ExID e) fromExID+        auto        <- M.lookup autoID autoMap+        j           <- A.follow auto i x+        unExID     <$> M.lookup (autoID, j) toExID+    , edges  = \e -> do+        let mtl = maybeToList+        (autoID, i) <- mtl $ M.lookup (ExID e) fromExID+        auto        <- mtl $ M.lookup autoID autoMap+        (x, j)      <- A.edges auto i+        e'          <- mtl $ M.lookup (autoID, j) toExID+        return (x, unExID e')+    }+++-- | Build the set of automata from the given grammar.+fromGram+    :: (Ord n, Ord t)+    => (FactGram n t -> A.GramAuto n t)+        -- ^ The underlying automaton construction method+    -> FactGram n t+        -- ^ The grammar to compress+    -> A.GramAuto n t+fromGram mkOne = shell . buildAutoSet mkOne+++--------------------------------------------------+-- Implementation+--------------------------------------------------+++-- | An external identifier (in contrast to internal identifiers+-- which are local to individual component automata).+newtype ExID = ExID { unExID :: ID }+    deriving (Show, Eq, Ord)+++-- | An automaton identifier.+newtype AutoID = AutoID { _unAutoID :: ID }+    deriving (Show, Eq, Ord)+++-- | An ensemble of automata.+data AutoSet a = AutoSet+    { autoMap   :: M.Map AutoID (A.Auto a)+    -- ^ individual automata and their identifiers+    , rootSet   :: S.Set ExID+    -- ^ A set of roots of the ensemble+    , fromExID  :: M.Map ExID (AutoID, ID)+    -- ^ Map external IDs to internal ones+    , toExID    :: M.Map (AutoID, ID) ExID+    -- ^ Reverse of `fromEx`+    }+++-- | An empty `AutoSet`.+emptyAS :: AutoSet a+emptyAS = AutoSet M.empty S.empty M.empty M.empty+++-- | Assuming that two `AutoSet`s are disjoint (i.e. they have+-- disjoint sets of `AutoID`s and disjoin sets of `ExID`s), we can+-- union them easily.+unionAS :: AutoSet a -> AutoSet a -> AutoSet a+unionAS p q = AutoSet+    { autoMap   = M.union (autoMap p) (autoMap q)+    , rootSet   = S.union (rootSet p) (rootSet q)+    , fromExID  = M.union (fromExID p) (fromExID q)+    , toExID    = M.union (toExID p) (toExID q) }+++-- | Union a list of `AutoSet`s.+unionsAS :: [AutoSet a] -> AutoSet a+unionsAS = foldl' unionAS emptyAS+++-- | Build automata from the given grammar.+buildAutoSet+    :: (Ord n, Ord t)+    => (FactGram n t -> A.GramAuto n t)+        -- ^ The underlying automaton construction method+    -> FactGram n t+        -- ^ The grammar to compress+    -> AutoSet (A.Edge (Lab n t))+buildAutoSet mkOne gram = runM $+    unionsAS <$> sequence+        [ mkAutoSet+            (AutoID autoID)+            (mkOne ruleSet)+        | (autoID, ruleSet)+            <- zip [0..] (M.elems gramByHead) ]+  where+    -- grammar divided by rule heads+    gramByHead = M.fromListWith S.union+        [ (headR r, S.singleton r)+        | r <- S.toList gram ]+    -- build a single automatom+    mkAutoSet autoID auto = do+        rootMap <- mkNodeMap (A.roots auto)+        descMap <- mkNodeMap (A.allIDs auto S.\\ A.roots auto)+        let nodeMap = rootMap `M.union` descMap+        return AutoSet+            { autoMap   = M.singleton autoID auto+            , rootSet   = M.keysSet rootMap+            , fromExID  = nodeMap+            , toExID    = rev1to1 nodeMap }+      where+        mkNodeMap inNodeSet = fmap M.fromList $+            forM (S.toList inNodeSet) $ \i -> do+                e <- newExID+                return (e, (autoID, i))+    -- low-level monad-related functions+    runM = flip E.evalState (0 :: Int)+    newExID = E.state $ \k -> (ExID k, k + 1)+    -- reverse bijection+    rev1to1 = M.fromList . map swap . M.toList+    swap (x, y) = (y, x)
+ src/NLP/Partage/Auto/Trie.hs view
@@ -0,0 +1,140 @@+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE FlexibleContexts #-}+++-- | Prefix tree grammar representation: the set of rules is stored+-- in a form of a prefix tree.+++module NLP.Partage.Auto.Trie+(+-- -- * Trie+--   Trie+-- , empty+-- , insert+-- , fromLang+--+-- -- * From grammar+-- , buildTrie+--+-- -- * Interface+-- , shell+  fromGram+) where+++import qualified Control.Arrow as Arr+import           Control.Applicative ((<$>), (<*>), pure)+import qualified Control.Monad.State.Strict as E++import           Data.Maybe (fromMaybe)+import           Data.List (foldl')+import qualified Data.Set as S+import qualified Data.Map.Strict as M++import           Data.DAWG.Ord (ID)++import qualified NLP.Partage.Auto as A+import           NLP.Partage.FactGram (FactGram, Lab(..), Rule(..))+++--------------------------------------------------+-- Trie+--------------------------------------------------+++-- | Simple trie implementation.+newtype Trie a = Trie { _unTrie :: M.Map a (Trie a) }+++-- | Empty trie.+empty :: Trie a+empty = Trie M.empty+++-- | Insert new element into the trie.+insert :: Ord a => [a] -> Trie a -> Trie a+insert (x:xs) (Trie t) =+    let s = fromMaybe empty (M.lookup x t)+    in  Trie $ M.insert x (insert xs s) t+insert [] t = t+++-- | Build trie from language.+fromLang :: Ord a => [[a]] -> Trie a+fromLang = foldl' (flip insert) empty+++--------------------------------------------------+-- Trie from grammar+--------------------------------------------------+++-- | Build trie from the given grammar.+buildTrie :: (Ord n, Ord t) => FactGram n t -> Trie (A.Edge (Lab n t))+buildTrie gram = fromLang+    [ map A.Body bodyR ++ [A.Head headR]+    | Rule{..} <- S.toList gram ]+++--------------------------------------------------+-- Interface+--------------------------------------------------+++-- | Abstract over the concrete implementation.+shell :: (Ord n, Ord t) => Trie (A.Edge (Lab n t)) -> A.GramAuto n t+shell d0 = A.Auto+    { roots  = S.singleton (rootID d)+    , follow = follow d+    , edges  = edges d }+    where d = convert d0+++-- | Build the trie-based representation of the given grammar.+fromGram :: (Ord n, Ord t) => FactGram n t -> A.GramAuto n t+fromGram = shell . buildTrie+++-- | Node type.+type Node a = M.Map a ID+++-- | Alternative trie represetation with explicit node identifiers.+data ITrie a = ITrie+    { rootID    :: ID+    -- ^ Root of the trie+    , nodeMap   :: M.Map ID (Node a)+    }+++-- | Follow symbol from the given node.+follow :: Ord a => ITrie a -> ID -> a -> Maybe ID+follow ITrie{..} i x = do+    node <- M.lookup i nodeMap+    M.lookup x node+++-- | All edges outgoing from the given node ID.+edges :: ITrie a -> ID -> [(a, ID)]+edges ITrie{..} i = case M.lookup i nodeMap of+    Nothing -> []+    Just m  -> M.toList m+++-- | Convert `Trie` to `ITrie`.+convert :: Ord a => Trie a -> ITrie a+convert t0 = ITrie+    { rootID  = root+    , nodeMap = nodeMap' }+  where+    (root, (_, nodeMap')) = E.runState (doit t0) (0 :: Int, M.empty)+    doit (Trie t) = do+        i <- newID+        node <- M.fromList <$> sequence+            [ (,) <$> pure x <*> doit s+            | (x, s) <- M.toList t ]+        yield i node+        return i+    newID = E.state $ \(n, m) -> (n, (n + 1, m))+    yield i node = E.modify $ Arr.second (M.insert i node)
+ src/NLP/Partage/Earley.hs view
@@ -0,0 +1,41 @@+-- | Earley-style TAG parsing based on automata, with a distinction+-- between active and passive items.+++module NLP.Partage.Earley+(+-- * Earley-style parsing+-- $earley+  recognize+, recognizeFrom+, parse+, earley+-- ** With automata precompiled+, recognizeAuto+, recognizeFromAuto+, parseAuto+, earleyAuto++-- * Parsing trace (hypergraph)+, Hype+-- ** Extracting parsed trees+, parsedTrees+-- ** Stats+, hyperNodesNum+, hyperEdgesNum+-- ** Printing+, printHype++-- * Sentence position+, Pos+) where+++import           NLP.Partage.Earley.AutoAP++{- $earley+  All the parsing functions described below employ the+  "NLP.Partage.Auto.DAWG" grammar representation.+  You can also pre-compile your own automaton and use it with+  e.g. `parseAuto`.+-}
+ src/NLP/Partage/Earley/AutoAP.hs view
@@ -0,0 +1,1275 @@+{-# LANGUAGE RecordWildCards      #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE Rank2Types #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE CPP #-}+++-- | Earley-style TAG parsing based on automata, with a distinction+-- between active and passive items.+++module NLP.Partage.Earley.AutoAP+(+-- * Earley-style parsing+  recognize+, recognizeFrom+, parse+, earley+-- ** With automata precompiled+, recognizeAuto+, recognizeFromAuto+, parseAuto+, earleyAuto++-- * Parsing trace (hypergraph)+, Hype+-- ** Extracting parsed trees+, parsedTrees+-- ** Stats+, hyperNodesNum+, hyperEdgesNum+-- ** Printing+, printHype++-- * Sentence position+, Pos+) where+++import           Prelude hiding             (span, (.))+import           Control.Applicative        ((<$>))+import           Control.Monad      (guard, void, (>=>), when, forM_)+import           Control.Monad.Trans.Class  (lift)+-- import           Control.Monad.Trans.Maybe  (MaybeT (..))+import qualified Control.Monad.RWS.Strict   as RWS+import           Control.Category ((>>>), (.))++import           Data.Function              (on)+import           Data.Maybe     ( isJust, isNothing, mapMaybe+                                , maybeToList )+import qualified Data.Map.Strict            as M+import           Data.Ord       ( comparing )+import           Data.List      ( sortBy )+import qualified Data.Set                   as S+import qualified Data.PSQueue               as Q+import           Data.PSQueue (Binding(..))+import           Data.Lens.Light+import qualified Data.Vector                as V++import qualified Pipes                      as P+-- import qualified Pipes.Prelude              as P++import           Data.DAWG.Ord (ID)+-- import qualified Data.DAWG.Ord.Dynamic      as D++import           NLP.Partage.SOrd+import           NLP.Partage.FactGram (FactGram)+import           NLP.Partage.FactGram.Internal+                                ( Lab(..), Rule(..), viewLab )+import qualified NLP.Partage.Auto as A+import qualified NLP.Partage.Auto.DAWG  as D+import qualified NLP.Partage.Tree       as T+++--------------------------------------------------+-- BASE TYPES+--------------------------------------------------+++-- | A position in the input sentence.+type Pos = Int+++data Span = Span {+    -- | The starting position.+      _beg   :: Pos+    -- | The ending position (or rather the position of the dot).+    , _end   :: Pos+    -- | Coordinates of the gap (if applies)+    , _gap   :: Maybe (Pos, Pos)+    } deriving (Show, Eq, Ord)+$( makeLenses [''Span] )+++-- | Active chart item : state reference + span.+data Active = Active {+      _state :: ID+    , _spanA :: Span+    } deriving (Show, Eq, Ord)+$( makeLenses [''Active] )+++-- | Passive chart item : label + span.+data Passive n t = Passive {+      _label :: Lab n t+    , _spanP :: Span+    } deriving (Show, Eq, Ord)+$( makeLenses [''Passive] )+++-- | Does it represent regular rules?+regular :: Span -> Bool+regular = isNothing . getL gap+++-- | Does it represent auxiliary rules?+auxiliary :: Span -> Bool+auxiliary = isJust . getL gap+++-- | Print an active item.+printSpan :: Span -> IO ()+printSpan span = do+    putStr . show $ getL beg span+    putStr ", "+    case getL gap span of+        Nothing -> return ()+        Just (p, q) -> do+            putStr $ show p+            putStr ", "+            putStr $ show q+            putStr ", "+    putStr . show $ getL end span+++-- | Print an active item.+printActive :: Active -> IO ()+printActive p = do+    putStr "("+    putStr . show $ getL state p+    putStr ", "+    printSpan $ getL spanA p+    putStrLn ")"+++-- | Print a passive item.+printPassive :: (Show n, Show t) => Passive n t -> IO ()+printPassive p = do+    putStr "("+    putStr . viewLab $ getL label p+    putStr ", "+    printSpan $ getL spanP p+    putStrLn ")"+++--------------------------------------------------+-- Traversal+--------------------------------------------------+++-- | Traversal represents an action of inducing a new item on the+-- basis of one or two other chart items.  It can be seen as an+-- application of one of the inference rules specifying the parsing+-- algorithm.+--+-- TODO: Sometimes there is no need to store all the arguments of the+-- inference rules, it seems.  From one of the arguments the other+-- one could be derived.+data Trav n t+    = Scan+        { _scanFrom :: Active+        -- ^ The input active state+        , _scanTerm :: t+        -- ^ The scanned terminal+        }+    | Subst+        { _passArg  :: Passive n t+        -- ^ The passive argument of the action+        , _actArg   :: Active+        -- ^ The active argument of the action+        }+    -- ^ Pseudo substitution+    | Foot+        { _actArg   :: Active+        -- ^ The passive argument of the action+        -- , theFoot  :: n+        , _theFoot  :: Passive n t+        -- ^ The foot non-terminal+        }+    -- ^ Foot adjoin+    | Adjoin+        { _passAdj  :: Passive n t+        -- ^ The adjoined item+        , _passMod  :: Passive n t+        -- ^ The modified item+        }+    -- ^ Adjoin terminate with two passive arguments+    deriving (Show, Eq, Ord)+++-- | Print a traversal.+printTrav :: (Show n, Show t) => Item n t -> Trav n t -> IO ()+printTrav q' (Scan p x) = do+    putStr "# " >> printActive p+    putStr "+ " >> print x+    putStr "= " >> printItem q'+printTrav q' (Subst p q) = do+    putStr "# " >> printActive q+    putStr "+ " >> printPassive p+    putStr "= " >> printItem q'+printTrav q' (Foot q p) = do+    putStr "# " >> printActive q+    putStr "+ " >> printPassive p+    putStr "= " >> printItem q'+printTrav q' (Adjoin p s) = do+    putStr "# " >> printPassive p+    putStr "+ " >> printPassive s+    putStr "= " >> printItem q'+++--------------------------------------------------+-- Priority+--------------------------------------------------+++-- | Priority type.+--+-- NOTE: Priority has to be composed from two elements because+-- otherwise `tryAdjoinTerm` could work incorrectly.  That is,+-- the modified item could be popped from the queue after the+-- modifier (auxiliary) item and, as a result, adjunction would+-- not be considered.+type Prio = (Int, Int)+++-- | Priority of an active item.  Crucial for the algorithm --+-- states have to be removed from the queue in a specific order.+prioA :: Active -> Prio+prioA p =+    let i = getL (beg . spanA) p+        j = getL (end . spanA) p+    in  (j, j - i)+++-- | Priority of a passive item.  Crucial for the algorithm --+-- states have to be removed from the queue in a specific order.+prioP :: Passive n t -> Prio+prioP p =+    let i = getL (beg . spanP) p+        j = getL (end . spanP) p+    in  (j, j - i)+++-- | Extended priority which preservs information about the traversal+-- leading to the underlying chart item.+data ExtPrio n t = ExtPrio+    { prioVal   :: Prio+    -- ^ The actual priority+    , prioTrav  :: S.Set (Trav n t)+    -- ^ Traversal leading to the underlying chart item+    } deriving (Show)++instance (Eq n, Eq t) => Eq (ExtPrio n t) where+    (==) = (==) `on` prioVal+instance (Ord n, Ord t) => Ord (ExtPrio n t) where+    compare = compare `on` prioVal+++-- | Construct a new `ExtPrio`.+extPrio :: Prio -> ExtPrio n t+extPrio p = ExtPrio p S.empty+++-- | Join two priorities:+-- * The actual priority preserved is the lower of the two,+-- * The traversals are unioned.+--+-- NOTE: at the moment, priority is strictly specified by the+-- underlying chart item itself so we know that both priorities must+-- be equal.  Later when we start using probabilities this statement+-- will no longer hold.+joinPrio :: (Ord n, Ord t) => ExtPrio n t -> ExtPrio n t -> ExtPrio n t+joinPrio x y = ExtPrio+    (min (prioVal x) (prioVal y))+    (S.union (prioTrav x) (prioTrav y))+++--------------------------------------------------+-- Item Type+--------------------------------------------------+++-- | Passive or active item.+data Item n t+    = ItemP (Passive n t)+    | ItemA Active+    deriving (Show, Eq, Ord)+++-- | Print an active item.+printItem :: (Show n, Show t) => Item n t -> IO ()+printItem (ItemP p) = printPassive p+printItem (ItemA p) = printActive p+++-- | Priority of an active item.  Crucial for the algorithm --+-- states have to be removed from the queue in a specific order.+prio :: Item n t -> Prio+prio (ItemP p) = prioP p+prio (ItemA p) = prioA p+++--------------------------------------------------+-- Earley monad+--------------------------------------------------+++-- | The reader of the earley monad: vector of sets of terminals.+type EarRd t = V.Vector (S.Set t)+++-- | A hypergraph dynamically constructed during parsing.+data Hype n t = Hype+    { automat :: A.GramAuto n t+    -- ^ The underlying automaton (abstract implementation)++    , withBody :: M.Map (Lab n t) (S.Set ID)+    -- ^ A data structure which, for each label, determines the+    -- set of automaton states from which this label goes out+    -- as a body transition.++    -- , doneActive  :: M.Map (ID, Pos) (S.Set (Active n t))+    , doneActive  :: M.Map Pos (M.Map ID+        (M.Map Active (S.Set (Trav n t))))+    -- ^ Processed active items partitioned w.r.t ending+    -- positions and state IDs.++    -- , donePassive :: S.Set (Passive n t)+    , donePassive :: M.Map (Pos, n, Pos)+        (M.Map (Passive n t) (S.Set (Trav n t)))+    -- ^ Processed passive items.++    , waiting     :: Q.PSQ (Item n t) (ExtPrio n t)+    -- ^ The set of states waiting on the queue to be processed.+    -- Invariant: the intersection of `done' and `waiting' states+    -- is empty.+    --+    -- NOTE2: Don't understand the note below...+    -- NOTE: The only operation which requires active states to+    -- be put to the queue in the current algorithm is the scan+    -- operation.  So perhaps we could somehow bypass this+    -- problem and perform scan elsewhere.  Nevertheless, it is+    -- not certain that the same will apply to the probabilistic+    -- parser.+    }+++-- | Make an initial `Hype` from a set of states.+mkHype+    :: (Ord n, Ord t)+    => A.GramAuto n t+    -> S.Set Active+    -> Hype n t+mkHype dag s = Hype+    { automat = dag+    , withBody = mkWithBody dag+    , doneActive  = M.empty+    , donePassive = M.empty+    , waiting = Q.fromList+        [ ItemA p :-> extPrio (prioA p)+        | p <- S.toList s ] }+++-- | Create the `withBody` component based on the automaton.+mkWithBody+    :: (Ord n, Ord t)+    => A.GramAuto n t+    -> M.Map (Lab n t) (S.Set ID)+mkWithBody dag = M.fromListWith S.union+    [ (x, S.singleton i)+    | (i, A.Body x, _j) <- A.allEdges dag ]+++-- | Earley parser monad.  Contains the input sentence (reader)+-- and the state of the computation `Hype'.+type Earley n t = RWS.RWST (EarRd t) () (Hype n t) IO+++-- | Read word from the given position of the input.+readInput :: Pos -> P.ListT (Earley n t) t+readInput i = do+    -- ask for the input+    sent <- RWS.ask+    -- just a safe way to retrieve the i-th element+    -- each $ take 1 $ drop i xs+    xs <- some $ sent V.!? i+    each $ S.toList xs+++--------------------------------------------------+-- Hypergraph stats+--------------------------------------------------+++-- | Number of nodes in the parsing hypergraph.+hyperNodesNum :: Hype n t -> Int+hyperNodesNum e+    = length (listPassive e)+    + length (listActive e)+++-- | Number of edges in the parsing hypergraph.+hyperEdgesNum :: forall n t. Hype n t -> Int+hyperEdgesNum earSt+    = sumOver listPassive+    + sumOver listActive+  where+    sumOver :: (Hype n t -> [(a, S.Set (Trav n t))]) -> Int+    sumOver listIt = sum+        [ S.size travSet+        | (_, travSet) <- listIt earSt ]+++-- | Extract hypergraph (hyper)edges.+hyperEdges :: Hype n t -> [(Item n t, Trav n t)]+hyperEdges earSt =+    passiveEdges ++ activeEdges+  where+    passiveEdges =+        [ (ItemP p, trav)+        | (p, travSet) <- listPassive earSt+        , trav <- S.toList travSet ]+    activeEdges =+        [ (ItemA p, trav)+        | (p, travSet) <- listActive earSt+        , trav <- S.toList travSet ]+++-- | Print the hypergraph edges.+printHype :: (Show n, Show t) => Hype n t -> IO ()+printHype earSt =+    forM_ edges $ \(p, trav) ->+        printTrav p trav+  where+    edges  = sortIt (hyperEdges earSt)+    sortIt = sortBy (comparing $ prio.fst)++++--------------------+-- Active items+--------------------+++-- | List all active done items together with the corresponding+-- traversals.+listActive :: Hype n t -> [(Active, S.Set (Trav n t))]+listActive = (M.elems >=> M.elems >=> M.toList) . doneActive+++-- | Return the corresponding set of traversals for an active item.+activeTrav+    :: (Ord n, Ord t)+    => Active -> Hype n t+    -> Maybe (S.Set (Trav n t))+activeTrav p+    = (   M.lookup (p ^. spanA ^. end)+      >=> M.lookup (p ^. state)+      >=> M.lookup p )+    . doneActive+++-- | Check if the active item is not already processed.+_isProcessedA :: (Ord n, Ord t) => Active -> Hype n t -> Bool+_isProcessedA p =+    check . activeTrav p+  where+    check (Just _) = True+    check _        = False+++-- | Check if the active item is not already processed.+isProcessedA :: (Ord n, Ord t) => Active -> Earley n t Bool+isProcessedA p = _isProcessedA p <$> RWS.get+++-- | Mark the active item as processed (`done').+saveActive+    :: (Ord t, Ord n)+    => Active+    -> S.Set (Trav n t)+    -> Earley n t ()+saveActive p ts =+    RWS.state $ \s -> ((), s {doneActive = newDone s})+  where+    newDone st =+        M.insertWith+            ( M.unionWith+                ( M.unionWith S.union ) )+            ( p ^. spanA ^. end )+            ( M.singleton (p ^. state)+                ( M.singleton p ts ) )+            ( doneActive st )+++--------------------+-- Passive items+--------------------+++-- | List all passive done items together with the corresponding+-- traversals.+listPassive :: Hype n t -> [(Passive n t, S.Set (Trav n t))]+listPassive = (M.elems >=> M.toList) . donePassive+++-- | Return the corresponding set of traversals for a passive item.+passiveTrav+    :: (Ord n, Ord t)+    => Passive n t -> Hype n t+    -> Maybe (S.Set (Trav n t))+passiveTrav p+    = ( M.lookup+        ( p ^. spanP ^. beg+        , nonTerm $ p ^. label+        , p ^. spanP ^. end ) >=> M.lookup p )+    . donePassive+++-- | Check if the state is not already processed.+_isProcessedP :: (Ord n, Ord t) => Passive n t -> Hype n t -> Bool+_isProcessedP x =+    check . passiveTrav x+  where+    check (Just _) = True+    check _        = False+++-- | Check if the passive item is not already processed.+isProcessedP :: (Ord n, Ord t) => Passive n t -> Earley n t Bool+isProcessedP p = _isProcessedP p <$> RWS.get+++-- | Mark the passive item as processed (`done').+savePassive+    :: (Ord t, Ord n)+    => Passive n t+    -> S.Set (Trav n t)+    -> Earley n t ()+savePassive p ts =+    RWS.state $ \s -> ((), s {donePassive = newDone s})+  where+    newDone st =+        M.insertWith+            ( M.unionWith S.union )+            ( p ^. spanP ^. beg+            , nonTerm $ p ^. label+            , p ^. spanP ^. end )+            ( M.singleton p ts )+            ( donePassive st )+++--------------------+-- Waiting Queue+--------------------+++-- | Add the active item to the waiting queue.  Check first if it+-- is not already in the set of processed (`done') states.+pushActive :: (Ord t, Ord n) => Active -> Trav n t -> Earley n t ()+pushActive p t = isProcessedA p >>= \b -> if b+    then saveActive p $ S.singleton t+    else modify' $ \s -> s {waiting = newWait (waiting s)}+  where+    newWait = Q.insertWith joinPrio (ItemA p) newPrio+    newPrio = ExtPrio (prioA p) (S.singleton t)+-- pushActive p = RWS.state $ \s ->+--     let waiting' = if isProcessedA p s+--             then waiting s+--             else Q.insert (ItemA p) (prioA p) (waiting s)+--     in  ((), s {waiting = waiting'})+++-- | Add the passive item to the waiting queue.  Check first if it+-- is not already in the set of processed (`done') states.+pushPassive :: (Ord t, Ord n) => Passive n t -> Trav n t -> Earley n t ()+pushPassive p t = isProcessedP p >>= \b -> if b+    then savePassive p $ S.singleton t+    else modify' $ \s -> s {waiting = newWait (waiting s)}+  where+    newWait = Q.insertWith joinPrio (ItemP p) newPrio+    newPrio = ExtPrio (prioP p) (S.singleton t)+-- -- | Add the passive item to the waiting queue.  Check first if it+-- -- is not already in the set of processed (`done') states.+-- pushPassive :: (Ord t, Ord n) => Passive n t -> Earley n t ()+-- pushPassive p = RWS.state $ \s ->+--     let waiting' = if isProcessedP p s+--             then waiting s+--             else Q.insert (ItemP p) (prioP p) (waiting s)+--     in  ((), s {waiting = waiting'})+++-- | Add to the waiting queue all items induced from the given item.+pushInduced :: (Ord t, Ord n) => Active -> Trav n t -> Earley n t ()+pushInduced p t = do+    hasElems (getL state p) >>= \b -> when b+        (pushActive p t)+    P.runListT $ do+        x <- heads (getL state p)+        lift . flip pushPassive t $+            Passive x (getL spanA p)+++-- | Remove a state from the queue.+popItem+    :: (Ord t, Ord n)+    => Earley n t+        (Maybe (Binding (Item n t) (ExtPrio n t)))+popItem = RWS.state $ \st -> case Q.minView (waiting st) of+    Nothing -> (Nothing, st)+    Just (b, s) -> (Just b, st {waiting = s})+++---------------------------------+-- Extraction of Processed Items+---------------------------------+++-- | Return all active processed items which:+-- * expect a given label,+-- * end on the given position.+expectEnd+    :: (Ord n, Ord t) => Lab n t -> Pos+    -> P.ListT (Earley n t) Active+expectEnd sym i = do+    Hype{..} <- lift RWS.get+    -- determine items which end on the given position+    doneEnd <- some $ M.lookup i doneActive+    -- determine automaton states from which the given label+    -- leaves as a body transition+    stateSet <- some $ M.lookup sym withBody+    -- pick one of the states+    stateID <- each $ S.toList stateSet+    --+    -- ALTERNATIVE: state <- each . S.toList $+    --      stateSet `S.intersection` M.keySet doneEnd+    --+    -- determine items which refer to the chosen states+    doneEndLab <- some $ M.lookup stateID doneEnd+    -- return them all!+    each $ M.keys doneEndLab+++-- | Check if a passive item exists with:+-- * the given root non-terminal value (but not top-level+--   auxiliary)+-- * the given span+rootSpan+    :: Ord n => n -> (Pos, Pos)+    -> P.ListT (Earley n t) (Passive n t)+rootSpan x (i, j) = do+    Hype{..} <- lift RWS.get+    -- listValues (i, x, j) donePassive+    each $ case M.lookup (i, x, j) donePassive of+        Nothing -> []+        Just m -> M.keys m+++-- -- | List all processed passive items.+-- listDone :: Done n t -> [Item n t]+-- listDone done = ($ done) $+--     M.elems >=> M.elems >=>+--     M.elems >=> S.toList+++--------------------------------------------------+-- New Automaton-Based Primitives+--------------------------------------------------+++-- | Follow the given terminal in the underlying automaton.+followTerm :: (Ord n, Ord t) => ID -> t -> P.ListT (Earley n t) ID+followTerm i c = do+    -- get the underlying automaton+    auto <- RWS.gets automat+    -- follow the label+    some $ A.follow auto i (A.Body $ Term c)+++-- | Follow the given body transition in the underlying automaton.+-- It represents the transition function of the automaton.+--+-- TODO: merge with `followTerm`.+follow :: (Ord n, Ord t) => ID -> Lab n t -> P.ListT (Earley n t) ID+follow i x = do+    -- get the underlying automaton+    auto <- RWS.gets automat+    -- follow the label+    some $ A.follow auto i (A.Body x)+++-- | Rule heads outgoing from the given automaton state.+heads :: ID -> P.ListT (Earley n t) (Lab n t)+heads i = do+    auto <- RWS.gets automat+    let mayHead (x, _) = case x of+            A.Body _  -> Nothing+            A.Head y -> Just y+    each $ mapMaybe mayHead $ A.edges auto i+++-- -- | Rule body elements outgoing from the given automaton state.+-- elems :: ID -> P.ListT (Earley n t) (Lab n t)+-- elems i = do+--     auto <- RWS.gets automat+--     let mayBody (x, _) = case x of+--             A.Body y  -> Just y+--             A.Head _ -> Nothing+--     each $ mapMaybe mayBody $ A.edges auto i+++-- | Check if any element leaves the given state.+hasElems :: ID -> Earley n t Bool+hasElems i = do+    auto <- RWS.gets automat+    let mayBody (x, _) = case x of+            A.Body y  -> Just y+            A.Head _ -> Nothing+    return+        . not . null+        . mapMaybe mayBody+        $ A.edges auto i+++--------------------------------------------------+-- SCAN+--------------------------------------------------+++-- | Try to perform SCAN on the given active state.+tryScan :: (SOrd t, SOrd n) => Active -> Earley n t ()+tryScan p = void $ P.runListT $ do+    -- read the word immediately following the ending position of+    -- the state+    c <- readInput $ getL (spanA >>> end) p+    -- follow appropriate terminal transition outgoing from the+    -- given automaton state+    j <- followTerm (getL state p) c+    -- construct the resultant active item+    -- let q = p {state = j, end = end p + 1}+    let q = setL state j+          . modL' (spanA >>> end) (+1)+          $ p+#ifdef Debug+    -- print logging information+    lift . lift $ do+        putStr "[S]  " >> printActive p+        putStr "  :  " >> printActive q+#endif+    -- push the resulting state into the waiting queue+    lift $ pushInduced q $ Scan p c+++--------------------------------------------------+-- SUBST+--------------------------------------------------+++-- | Try to use the passive item `p` to complement+-- (=> substitution) other rules.+trySubst :: (SOrd t, SOrd n) => Passive n t -> Earley n t ()+trySubst p = void $ P.runListT $ do+    let pLab = getL label p+        pSpan = getL spanP p+    -- make sure that `p' represents regular rules+    guard . regular $ pSpan+    -- find active items which end where `p' begins and which+    -- expect the non-terminal provided by `p' (ID included)+    q <- expectEnd pLab (getL beg pSpan)+    -- follow the transition symbol+    j <- follow (getL state q) pLab+    -- construct the resultant state+    -- let q' = q {state = j, spanA = spanA p {end = end p}}+    let q' = setL state j+           . setL (end.spanA) (getL end pSpan)+           $ q+#ifdef Debug+    -- print logging information+    lift . lift $ do+        putStr "[U]  " >> printPassive p+        putStr "  +  " >> printActive q+        putStr "  :  " >> printActive q'+#endif+    -- push the resulting state into the waiting queue+    lift $ pushInduced q' $ Subst p q+++--------------------------------------------------+-- FOOT ADJOIN+--------------------------------------------------+++-- | `tryAdjoinInit p q':+-- * `p' is a completed state (regular or auxiliary)+-- * `q' not completed and expects a *real* foot+tryAdjoinInit :: (SOrd n, SOrd t) => Passive n t -> Earley n t ()+tryAdjoinInit p = void $ P.runListT $ do+    let pLab = p ^. label+        pSpan = p ^. spanP+    -- make sure that the corresponding rule is either regular or+    -- intermediate auxiliary ((<=) used as implication here)+    guard $ auxiliary pSpan <= not (topLevel pLab)+    -- find all active items which expect a foot with the given+    -- symbol and which end where `p` begins+    let foot = AuxFoot $ nonTerm pLab+    q <- expectEnd foot (getL beg pSpan)+    -- follow the foot+    j <- follow (getL state q) foot+    -- construct the resultant state+    let q' = setL state j+           . setL (spanA >>> end) (pSpan ^. end)+           . setL (spanA >>> gap) (Just+                ( pSpan ^. beg+                , pSpan ^. end ))+           $ q+#ifdef Debug+    -- print logging information+    lift . lift $ do+        putStr "[A]  " >> printPassive p+        putStr "  +  " >> printActive q+        putStr "  :  " >> printActive q'+#endif+    -- push the resulting state into the waiting queue+    lift $ pushInduced q' $ Foot q p -- -- $ nonTerm foot+++--------------------------------------------------+-- INTERNAL ADJOIN+--------------------------------------------------+++-- | `tryAdjoinCont p q':+-- * `p' is a completed, auxiliary state+-- * `q' not completed and expects a *dummy* foot+tryAdjoinCont :: (SOrd n, SOrd t) => Passive n t -> Earley n t ()+tryAdjoinCont p = void $ P.runListT $ do+    let pLab = p ^. label+        pSpan = p ^. spanP+    -- make sure the label is not top-level (internal spine+    -- non-terminal)+    guard . not $ topLevel pLab+    -- make sure that `p' is an auxiliary item+    guard . auxiliary $ pSpan+    -- find all rules which expect a spine non-terminal provided+    -- by `p' and which end where `p' begins+    q <- expectEnd pLab (pSpan ^. beg)+    -- follow the spine non-terminal+    j <- follow (q ^. state) pLab+    -- construct the resulting state; the span of the gap of the+    -- inner state `p' is copied to the outer state based on `q'+    let q' = setL state j+           . setL (spanA >>> end) (pSpan ^. end)+           . setL (spanA >>> gap) (pSpan ^. gap)+           $ q+#ifdef Debug+    -- logging info+    lift . lift $ do+        putStr "[B]  " >> printPassive p+        putStr "  +  " >> printActive q+        putStr "  :  " >> printActive q'+#endif+    -- push the resulting state into the waiting queue+    lift $ pushInduced q' $ Subst p q+++--------------------------------------------------+-- ROOT ADJOIN+--------------------------------------------------+++-- | Adjoin a fully-parsed auxiliary state `p` to a partially parsed+-- tree represented by a fully parsed rule/state `q`.+tryAdjoinTerm :: (SOrd t, SOrd n) => Passive n t -> Earley n t ()+tryAdjoinTerm q = void $ P.runListT $ do+    let qLab = q ^. label+        qSpan = q ^. spanP+    -- make sure the label is top-level+    guard $ topLevel qLab+    -- make sure that it is an auxiliary item (by definition only+    -- auxiliary states have gaps)+    (gapBeg, gapEnd) <- each $ maybeToList $ qSpan ^. gap+    -- take all passive items with a given span and a given+    -- root non-terminal (IDs irrelevant)+    p <- rootSpan (nonTerm qLab) (gapBeg, gapEnd)+    let p' = setL (spanP >>> beg) (qSpan ^. beg)+           . setL (spanP >>> end) (qSpan ^. end)+           $ p+#ifdef Debug+    lift . lift $ do+        putStr "[C]  " >> printPassive q+        putStr "  +  " >> printPassive p+        putStr "  :  " >> printPassive p'+#endif+    lift $ pushPassive p' $ Adjoin q p+++--------------------------------------------------+-- Earley step+--------------------------------------------------+++-- | Step of the algorithm loop.  `p' is the state popped up from+-- the queue.+step+    :: (SOrd t, SOrd n)+    => Binding (Item n t) (ExtPrio n t)+    -> Earley n t ()+step (ItemP p :-> e) = do+    mapM_ ($ p)+      [ trySubst+      , tryAdjoinInit+      , tryAdjoinCont+      , tryAdjoinTerm ]+    savePassive p $ prioTrav e+step (ItemA p :-> e) = do+    mapM_ ($ p)+      [ tryScan ]+    saveActive p $ prioTrav e+++---------------------------+-- Extracting Parsed Trees+---------------------------+++-- | Extract the set of parsed trees obtained on the given input+-- sentence.  Should be run on the result of the earley algorithm.+parsedTrees+    :: forall n t. (Ord n, Ord t)+    => Hype n t    -- ^ Final state of the earley parser+    -> n            -- ^ The start symbol+    -> Int          -- ^ Length of the input sentence+    -> S.Set (T.Tree n t)+parsedTrees earSt start n++    = S.fromList+    $ concatMap fromPassive+    $ finalFrom start n earSt++  where++    fromPassive :: Passive n t -> [T.Tree n t]+    fromPassive p = concat+        [ fromPassiveTrav p trav+        | travSet <- maybeToList $ passiveTrav p earSt+        , trav <- S.toList travSet ]++    fromPassiveTrav p (Scan q t) =+        [ T.Branch+            (nonTerm $ getL label p)+            (reverse $ T.Leaf t : ts)+        | ts <- fromActive q ]++--     fromPassiveTrav p (Foot q x) =+--         [ T.Branch+--             (nonTerm $ getL label p)+--             (reverse $ T.Branch x [] : ts)+--         | ts <- fromActive q ]++    fromPassiveTrav p (Foot q _p') =+        [ T.Branch+            (nonTerm $ getL label p)+            (reverse $ T.Branch (nonTerm $ p ^. label) [] : ts)+        | ts <- fromActive q ]++    fromPassiveTrav p (Subst qp qa) =+        [ T.Branch+            (nonTerm $ getL label p)+            (reverse $ t : ts)+        | ts <- fromActive qa+        , t  <- fromPassive qp ]++    fromPassiveTrav _p (Adjoin qa qm) =+        [ replaceFoot ini aux+        | aux <- fromPassive qa+        , ini <- fromPassive qm ]++    -- | Replace foot (the only non-terminal leaf) by the given+    -- initial tree.+    replaceFoot ini (T.Branch _ []) = ini+    replaceFoot ini (T.Branch x ts) = T.Branch x $ map (replaceFoot ini) ts+    replaceFoot _ t@(T.Leaf _)    = t+++    fromActive  :: Active -> [[T.Tree n t]]+    fromActive p = case activeTrav p earSt of+        Nothing -> error "fromActive: unknown active item"+        Just travSet -> if S.null travSet+            then [[]]+            else concatMap+                (fromActiveTrav p)+                (S.toList travSet)++    fromActiveTrav _p (Scan q t) =+        [ T.Leaf t : ts+        | ts <- fromActive q ]++    fromActiveTrav _p (Foot q p) =+        [ T.Branch (nonTerm $ p ^. label) [] : ts+        | ts <- fromActive q ]++--     fromActiveTrav _p (Foot q x) =+--         [ T.Branch x [] : ts+--         | ts <- fromActive q ]++    fromActiveTrav _p (Subst qp qa) =+        [ t : ts+        | ts <- fromActive qa+        , t  <- fromPassive qp ]++    fromActiveTrav _p (Adjoin _ _) =+        error "parsedTrees: fromActiveTrav called on a passive item"+++--------------------------------------------------+-- EARLEY+--------------------------------------------------+++-- | Does the given grammar generate the given sentence?+-- Uses the `earley` algorithm under the hood.+recognize+#ifdef Debug+    :: (SOrd t, SOrd n)+#else+    :: (Ord t, Ord n)+#endif+    => FactGram n t         -- ^ The grammar (set of rules)+    -> [S.Set t]            -- ^ Input sentence+    -> IO Bool+recognize gram =+    recognizeAuto (D.fromGram gram)+++-- | Does the given grammar generate the given sentence from the+-- given non-terminal symbol (i.e. from an initial tree with this+-- symbol in its root)?  Uses the `earley` algorithm under the+-- hood.+recognizeFrom+#ifdef Debug+    :: (SOrd t, SOrd n)+#else+    :: (Ord t, Ord n)+#endif+    => FactGram n t         -- ^ The grammar (set of rules)+    -> n                    -- ^ The start symbol+    -> [S.Set t]            -- ^ Input sentence+    -> IO Bool+recognizeFrom gram =+    recognizeFromAuto (D.fromGram gram)+++-- | Parse the given sentence and return the set of parsed trees.+parse+#ifdef Debug+    :: (SOrd t, SOrd n)+#else+    :: (Ord t, Ord n)+#endif+    => FactGram n t         -- ^ The grammar (set of rules)+    -> n                    -- ^ The start symbol+    -> [S.Set t]            -- ^ Input sentence+    -> IO (S.Set (T.Tree n t))+parse gram = parseAuto $ D.fromGram gram+++-- | Perform the earley-style computation given the grammar and+-- the input sentence.+earley+#ifdef Debug+    :: (SOrd t, SOrd n)+#else+    :: (Ord t, Ord n)+#endif+    => FactGram n t         -- ^ The grammar (set of rules)+    -> [S.Set t]            -- ^ Input sentence+    -> IO (Hype n t)+earley gram = earleyAuto $ D.fromGram gram+++--------------------------------------------------+-- Parsing with automaton+--------------------------------------------------+++-- | See `recognize`.+recognizeAuto+#ifdef Debug+    :: (SOrd t, SOrd n)+#else+    :: (Ord t, Ord n)+#endif+    => A.GramAuto n t           -- ^ Grammar automaton+    -> [S.Set t]            -- ^ Input sentence+    -> IO Bool+recognizeAuto auto xs =+    isRecognized xs <$> earleyAuto auto xs+++-- | See `recognizeFrom`.+recognizeFromAuto+#ifdef Debug+    :: (SOrd t, SOrd n)+#else+    :: (Ord t, Ord n)+#endif+    => A.GramAuto n t       -- ^ Grammar automaton+    -> n                    -- ^ The start symbol+    -> [S.Set t]            -- ^ Input sentence+    -> IO Bool+recognizeFromAuto auto start xs = do+    earSt <- earleyAuto auto xs+    return $ (not.null) (finalFrom start (length xs) earSt)+++-- | See `parse`.+parseAuto+#ifdef Debug+    :: (SOrd t, SOrd n)+#else+    :: (Ord t, Ord n)+#endif+    => A.GramAuto n t           -- ^ Grammar automaton+    -> n                    -- ^ The start symbol+    -> [S.Set t]            -- ^ Input sentence+    -> IO (S.Set (T.Tree n t))+parseAuto auto start xs = do+    earSt <- earleyAuto auto xs+    return $ parsedTrees earSt start (length xs)+++-- | See `earley`.+earleyAuto+#ifdef Debug+    :: (SOrd t, SOrd n)+#else+    :: (Ord t, Ord n)+#endif+    => A.GramAuto n t           -- ^ Grammar automaton+    -> [S.Set t]            -- ^ Input sentence+    -> IO (Hype n t)+earleyAuto dawg xs =+    fst <$> RWS.execRWST loop (V.fromList xs) st0+  where+    -- we put in the initial state all the states with the dot on+    -- the left of the body of the rule (-> left = []) on all+    -- positions of the input sentence.+    st0 = mkHype dawg $ S.fromList+        [ Active root Span+            { _beg   = i+            , _end   = i+            , _gap   = Nothing }+        | i <- [0 .. length xs - 1]+        , root <- S.toList (A.roots dawg) ]+    -- the computation is performed as long as the waiting queue+    -- is non-empty.+    loop = popItem >>= \mp -> case mp of+        Nothing -> return ()+        Just p  -> step p >> loop+++--------------------------------------------------+-- New utilities+--------------------------------------------------+++-- | Return the list of final passive chart items.+finalFrom+    :: (Ord n, Eq t)+    => n            -- ^ The start symbol+    -> Int          -- ^ The length of the input sentence+    -> Hype n t    -- ^ Result of the earley computation+    -> [Passive n t]+finalFrom start n Hype{..} =+    case M.lookup (0, start, n) donePassive of+        Nothing -> []+        Just m ->+            [ p+            | p <- M.keys m+            , p ^. label == NonT start Nothing ]+++-- -- | Return the list of final passive chart items.+-- final+--     :: (Ord n, Eq t)+--     -> Int          -- ^ The length of the input sentence+--     -> Hype n t    -- ^ Result of the earley computation+--     -> [Passive n t]+-- final start n Hype{..} =+--     case M.lookup (0, start, n) donePassive of+--         Nothing -> []+--         Just m ->+--             [ p+--             | p <- M.keys m+--             , p ^. label == NonT start Nothing ]+++-- | Check whether the given sentence is recognized+-- based on the resulting state of the earley parser.+--+-- TODO: The function returns `True` also when a subtree+-- of an elementary tree is recognized, it seems.+isRecognized+    :: (SOrd t, SOrd n)+    => [S.Set t]            -- ^ Input sentence+    -> Hype n t            -- ^ Earley parsing result+    -> Bool+isRecognized xs Hype{..} =+    (not . null)+    (complete+        (agregate donePassive))+  where+    n = length xs+    complete done =+        [ True | item <- S.toList done+        , item ^. spanP ^. beg == 0+        , item ^. spanP ^. end == n+        , isNothing (item ^. spanP ^. gap) ]+    agregate = S.unions . map M.keysSet . M.elems+++--------------------------------------------------+-- Utilities+--------------------------------------------------+++-- | Strict modify (in mtl starting from version 2.2).+modify' :: RWS.MonadState s m => (s -> s) -> m ()+modify' f = do+  x <- RWS.get+  RWS.put $! f x+++-- -- | MaybeT transformer.+-- maybeT :: Monad m => Maybe a -> MaybeT m a+-- maybeT = MaybeT . return+++-- | ListT from a list.+each :: Monad m => [a] -> P.ListT m a+each = P.Select . P.each+++-- | ListT from a maybe.+some :: Monad m => Maybe a -> P.ListT m a+some = each . maybeToList+++-- | Is the rule with the given head top-level?+topLevel :: Lab n t -> Bool+topLevel x = case x of+    NonT{..}  -> isNothing labID+    AuxRoot{} -> True+    _         -> False+++-- -- | Pipe all values from the set corresponding to the given key.+-- listValues+--     :: (Monad m, Ord a)+--     => a -> M.Map a (S.Set b)+--     -> P.ListT m b+-- listValues x m = each $ case M.lookup x m of+--     Nothing -> []+--     Just s -> S.toList s
+ src/NLP/Partage/FactGram.hs view
@@ -0,0 +1,22 @@+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE FlexibleContexts #-}+++-- | TAG conversion into flat production rules.+++module NLP.Partage.FactGram+(+-- * Factorized grammar+  FactGram+, Rule (..)+, Lab (..)++-- * Grammar flattening+, flattenNoSharing+, flattenWithSharing+) where+++import           NLP.Partage.FactGram.Internal+import           NLP.Partage.SubtreeSharing
+ src/NLP/Partage/FactGram/Internal.hs view
@@ -0,0 +1,357 @@+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE FlexibleContexts #-}+++-- | TAG conversion into flat production rules.+++module NLP.Partage.FactGram.Internal+(+-- * Grammar+  FactGram+, Rule (..)+, Lab (..)+, SymID++-- * Grammar flattening+, flattenNoSharing+-- , compileWeights++-- * Showing+, viewLab++-- * Internal+, runRM+, treeRules+, auxRules+) where+++import           Control.Monad.Trans.Class (lift)+import qualified Control.Monad.State.Strict   as E++import qualified Data.Set as S+-- import qualified Data.Map.Strict as M+import qualified Data.Tree as T++import qualified Pipes as P++import qualified NLP.Partage.Tree as G+++--------------------------------------------------+-- CORE TYPES+--------------------------------------------------+++-- | 'SymID' is used to mark internal (non-leaf, non-root)+-- non-terminals with unique (up to subtree sharing) identifiers so+-- that incompatible rule combinations are not possible.+type SymID = Int+++-- -- | Cost (weight, probability) of employing an elementary+-- -- unit (tree, rule) in a parse tree.+-- type Cost = Double+++----------------------+-- Factorized grammar+----------------------+++-- | Factorized grammar: a set of flat production rules.+type FactGram n t = S.Set (Rule n t)+++----------------------+-- Grammar compilation+----------------------+++-- | Compile the given grammar into the list of rules.+-- No structure sharing takes place here.+flattenNoSharing+    :: (Monad m, Ord n, Ord t)+    => [ Either+        (G.Tree n t)+        (G.AuxTree n t) ]+    -> m (FactGram n t)+flattenNoSharing ts =+    flip E.execStateT S.empty $ runRM $ P.runEffect $+        P.for rules $ \rule ->+            lift . lift $ E.modify $ S.insert rule+  where+    rules = mapM_ getRules ts+    getRules (Left t)  = treeRules t+    getRules (Right t) = auxRules  t+++-- -- | Compile the given probabilistic grammar into the list of rules.  No+-- -- structure sharing takes place.  Weights are evenly distributed over all+-- -- rules representing the corresponding elementary trees.+-- compileWeights+--     :: (Monad m, Ord n, Ord t)+--     => [ Either+--         (G.Tree n t, Cost)+--         (G.AuxTree n t, Cost) ]+--     -> m (M.Map (Rule n t) Cost)+-- compileWeights ts =+--     flip E.execStateT M.empty $ runRM $ P.runEffect $+--         P.for rules $ \(rule, cost) ->+--             lift . lift $ E.modify $ M.insert rule cost+--   where+--     rules = mapM_ getRules ts+--     getRules (Left (t, c0))  = do+--         labTree <- lift $ labelTree True t+--         keepRules labTree c0+--         return $ T.rootLabel labTree+--     getRules (Right (t, c0)) = do+--         labTree <- lift $ labelAux True t+--         keepRules labTree c0+--         return $ T.rootLabel labTree+--     keepRules labTree c0 = do+--         let rs = collect labTree+--             c = c0 / fromIntegral (length rs)+--         mapM_ keepRule [ (r, c) | r <- rs ]+++----------------------+-- Initial Trees+----------------------+++-- | A label is a data type over which flat production rules are+-- constructed.  In particular, it describes what information is+-- stored in the heads of rules, as well as in the elements of the+-- their bodies.+data Lab n t+    = NonT+        { nonTerm   :: n+        , labID     :: Maybe SymID }+    -- ^ A non-terminal symbol originating from a branching,+    -- non-spine node, optionally marked with a `SymID` if+    -- originating from an internal (non-root, non-leaf) node+    | Term t+    -- ^ A terminal symbol+    | AuxRoot+        { nonTerm   :: n }+    -- ^ A non-terminal originating from a /root/ of an auxiliary tree+    | AuxFoot+        { nonTerm   :: n }+    -- ^ A non-terminal originating from a /foot/ of an auxiliary tree+    | AuxVert+        { nonTerm   :: n+        , symID     :: SymID }+    -- ^ A non-terminal originating from a /spine/ of an auxiliary+    -- tree (unless root or foot)+    deriving (Show, Eq, Ord)+++-- | Show full info about the label.+viewLab :: (Show n, Show t) => Lab n t -> String+viewLab lab = case lab of+    NonT{..}    -> "N(" ++ show nonTerm+        ++ ( case labID of+                Nothing -> ""+                Just i  -> ", " ++ show i ) ++ ")"+    Term t      -> "T(" ++ show t ++ ")"+    AuxRoot{..} -> "A(" ++ show nonTerm ++ ")"+    AuxFoot x   -> "F(" ++ show x ++ ")"+    AuxVert{..} -> "V(" ++ show nonTerm ++ ", " ++ show symID ++ ")"+++-- -- | Show the label.+-- viewLab :: (View n, View t) => Lab n t -> String+-- viewLab (NonT s) = "N" ++ viewSym s+-- viewLab (Term t) = "T(" ++ view t ++ ")"+-- viewLab (Foot s) = "F" ++ viewSym s+++-- | A production rule, responsible for recognizing a specific+-- (unique) non-trivial (of height @> 0@) subtree of an elementary+-- grammar tree.  Due to potential subtree sharing, a single rule can+-- be responsible for recognizing a subtree common to many different+-- elementary trees.+--+-- Invariants:+--+--  * `headR` is neither `Term` nor `AuxFoot`+data Rule n t = Rule {+    -- | Head of the rule+      headR :: Lab n t+    -- | Body of the rule+    , bodyR :: [Lab n t]+    } deriving (Show, Eq, Ord)+++-- -- | Print the rule.+-- printRule+--     :: ( View n, View t )+--     => Rule n t -> IO ()+-- printRule Rule{..} = do+--     putStr $ viewLab headR+--     putStr " -> "+--     putStr . unwords $ map viewLab bodyR+++--------------------------+-- Rule generation monad+--------------------------+++-- | Identifier generation monad.+type ID m = E.StateT Int m+++-- | Generating rules in a pipe.+type RM r m = P.Producer r (ID m)+++-- | Pull the next identifier.+nextSymID :: E.MonadState SymID m => m SymID+nextSymID = E.state $ \i -> (i, i + 1)+++-- | Save the rule in the writer component of the monad.+keepRule :: Monad m => r -> RM r m ()+keepRule = P.yield+++-- | Evaluate the state part of the RM monad.+-- runRM :: Monad m => P.Effect (E.StateT Int m) a -> m a+-- runRM = flip E.evalStateT 0 . P.runEffect+runRM :: Monad m => E.StateT Int m a -> m a+runRM = flip E.evalStateT 0+++-----------------------------------------+-- Tree Factorization+-----------------------------------------+++-- instance (ToString a, ToString b) => ToString (Either a b) where+--     toString (Left x)  = "L " ++ toString x+--     toString (Right x) = "R " ++ toString x+++-- | Take an initial tree and factorize it into a list of rules.+treeRules+    :: (Monad m)+    => G.Tree n t   -- ^ The tree itself+    -> RM (Rule n t) m (Lab n t)+treeRules t = do+    labTree <- lift $ labelTree True t+    mapM_ keepRule $ collect labTree+    return $ T.rootLabel labTree+++-- | Take an initial tree and factorize it into a tree of labels.+labelTree+    :: (Monad m)+    => Bool         -- ^ Is it a top level tree?  `True' for+                    -- an entire initial tree, `False' otherwise.+    -> G.Tree n t   -- ^ The tree itself+    -> ID m (T.Tree (Lab n t))+labelTree isTop G.Branch{..} = case (subTrees, isTop) of+    -- Foot or substitution node:+    ([], _) -> return . flip T.Node [] $ NonT+        { nonTerm = labelI+        , labID   = Nothing }+    -- Root node:+    (_, True) -> do+        let x = NonT+              { nonTerm = labelI+              , labID   = Nothing }+        xs <- mapM (labelTree False) subTrees+        return $ T.Node x xs+    -- Internal node:+    (_, False) -> do+        i <- nextSymID+        let x = NonT+                { nonTerm = labelI+                , labID   = Just i }+        xs <- mapM (labelTree False) subTrees+        return $ T.Node x xs+labelTree _ G.Leaf{..} = return $ T.Node (Term labelF) []+++-----------------------------------------+-- Auxiliary Tree Factorization+-----------------------------------------+++-- | Take an auxiliary tree and factorize it into a tree of labels.+auxRules+    :: (Monad m)+    => G.AuxTree n t+    -> RM (Rule n t) m (Lab n t)+auxRules t = do+    labTree <- lift $ labelAux True t+    mapM_ keepRule $ collect labTree+    return $ T.rootLabel labTree+++-- | Take an auxiliary tree and factorize it into a tree of labels.+labelAux+    :: (Monad m)+    => Bool+    -> G.AuxTree n t+    -> ID m (T.Tree (Lab n t))+labelAux b G.AuxTree{..} =+    doit b auxTree auxFoot+  where+    doit _ G.Branch{..} [] = return . flip T.Node [] $+        AuxFoot {nonTerm = labelI}+    doit isTop G.Branch{..} (k:ks) = do+        let (ls, bt, rs) = split k subTrees+        ls' <- mapM (labelTree False) ls+        bt' <- doit False bt ks+        rs' <- mapM (labelTree False) rs+        -- In case of an internal node `xt` and `xb` are slighly+        -- different; for a root, they are the same:+        x0 <- if isTop+            then return AuxRoot+                        { nonTerm = labelI }+            else nextSymID >>= \i ->+                 return AuxVert+                        { nonTerm = labelI+                        , symID   = i }+        -- keepRule $ Rule x0 $ ls' ++ (bt' : rs')+        -- return x0+        return $ T.Node x0 $ ls' ++ (bt' : rs')+    doit _ _ _ = error "auxRules: incorrect path"+++-----------------------------------------+-- Utils+-----------------------------------------+++-- | Split the given list on the given position.+split :: Int -> [a] -> ([a], a, [a])+split =+    doit []+  where+    doit acc 0 (x:xs) = (reverse acc, x, xs)+    doit acc k (x:xs) = doit (x:acc) (k-1) xs+    doit _ _ [] = error "auxRules.split: index to high"+++-- | Collect rules present in the tree produced by `labelTree`.+collect :: T.Tree (Lab n t) -> [Rule n t]+collect T.Node{..} = case subForest of+    [] -> []+    -- WARNING! It is crucial for substructure-sharing (at least in+    -- the current implementation, that indexes (SymIDs) are+    -- generated in the ascending order.  This stems from the fact+    -- that `Data.Partition.rep` returns the minimum element of the+    -- given partition, thus making it impossible to choose a custom+    -- representant of the given partition.+    --+    -- Note that this solution should be changed and that+    -- substructure sharing should be implemented differently.+    -- The current solution seems too error prone.+    _  ->  concatMap collect subForest+        ++ [ Rule rootLabel+            (map T.rootLabel subForest) ]
+ src/NLP/Partage/Gen.hs view
@@ -0,0 +1,437 @@+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TypeSynonymInstances #-}+{-# LANGUAGE FlexibleInstances #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+++-- | A simple, experimental tree generation module, with the aim+-- to generate /all/, or /randomly/, language trees satisfying+-- certain simple constraints.+--+-- One of the possible usecases where such a functionality can be+-- useful is to automatically generate test sets over which+-- efficiency of a parser can be measured.+++module NLP.Partage.Gen+( Gram++-- * Generation+, generateAll+-- ** Randomized generation+, generateRand+, GenConf (..)+) where++++import           Control.Applicative ((<$>), (<*>))+import qualified Control.Monad.State.Strict   as E+import           Control.Monad.Trans.Maybe (MaybeT (..))++import           Pipes+import qualified Pipes.Prelude as Pipes+import           System.Random (randomRIO)++import qualified Data.Foldable as F+import           Data.Maybe (maybeToList)+import qualified Data.Set as S+import qualified Data.Map.Strict as M+import qualified Data.PSQueue as Q+import           Data.PSQueue (Binding(..))+import qualified Data.Tree as R++import           NLP.Partage.Tree.Other+++--------------------------+-- Basic types+--------------------------+++deriving instance (Ord n, Ord t) => (Ord (Tree n t))+++-- | A TAG grammar: a set of (elementary) initial and auxiliary+-- trees.+type Gram n t = S.Set (Tree n t)+++--------------------------+-- Tree size+--------------------------+++-- | Size of a tree, i.e. number of nodes.+treeSize :: Tree n t -> Int+treeSize = length . R.flatten+++--------------------------+-- Generation state+--------------------------+++-- | Map of visited trees.+type DoneMap n t = M.Map Int (S.Set (Tree n t))+++-- | Underlying state of the generation pipe.+data GenST n t = GenST {+      waiting :: Q.PSQ (Tree n t) Int+    -- ^ Queue of the derived trees yet to be visited.+    , doneFinal :: DoneMap n t+    -- ^ Set of visited, final trees divided by size+    , doneActive :: DoneMap n t+    -- ^ Set of visited, active (not final) trees divided by size+    }+++-- | Construct new generation state with all trees in the priority queue.+newGenST :: (Ord n, Ord t) => Gram n t -> GenST n t+newGenST gramSet = GenST {+      waiting = Q.fromList+        [ t :-> treeSize t+        | t <- S.toList gramSet ]+    , doneFinal  = M.empty+    , doneActive = M.empty }+++-- | Pop the tree with the lowest score from the queue.+pop+    :: (E.MonadState (GenST n t) m, Ord n, Ord t)+    -- => m (Maybe (Tree n t))+    => ListT m (Tree n t)+pop = do+    mayTree <- E.state $ \s@GenST{..} -> case Q.minView waiting of+        Nothing -> (Nothing, s)+        Just (t :-> _, q) -> (Just t, s {waiting=q})+    -- return mayTree+    some $ maybeToList mayTree+++-- | Push tree into the waiting queue.+push :: (E.MonadState (GenST n t) m, Ord n, Ord t) => Tree n t -> m ()+push t = E.modify $ \s -> s+    {waiting = Q.insert t (treeSize t) (waiting s)}+++-- | Save tree as visited.+save :: (E.MonadState (GenST n t) m, Ord n, Ord t) => Tree n t -> m ()+save t = if isFinal t+    then E.modify $ \s -> s+            { doneFinal = M.insertWith S.union+                 (treeSize t) (S.singleton t) (doneFinal s) }+    else E.modify $ \s -> s+            { doneActive = M.insertWith S.union+                 (treeSize t) (S.singleton t) (doneActive s) }+++-- | Check if tree already visited.+visited+    :: (E.MonadState (GenST n t) m, Ord n, Ord t)+    => Tree n t -> m Bool+visited t = if isFinal t+    then isVisited doneFinal+    else isVisited doneActive+  where+    isVisited doneMap = do+        done <- E.gets doneMap+        return $ case M.lookup (treeSize t) done of+             Just ts -> S.member t ts+             Nothing -> False+++-- | Retrieve all trees from the given map with the size satsifying+-- the given condition.+visitedWith+    :: (E.MonadState (GenST n t) m, Ord n, Ord t)+    => (GenST n t -> DoneMap n t)+    -> (Int -> Bool)+    -> ListT m (Tree n t)+visitedWith doneMap cond = do+    done <- E.gets doneMap+    some [ t+      | (k, treeSet) <- M.toList done+      , cond k, t <- S.toList treeSet ]+++-- -- | Retrieve all visited final trees with a size satsifying the+-- -- given condition.+-- finalWith+--     :: (E.MonadState (GenST n t) m, Ord n, Ord t)+--     => (Int -> Bool) -> ListT m (Tree n t)+-- finalWith = visitedWith doneFinal+--+--+-- -- | Retrieve all visited trees with a size satsifying+-- -- the given condition.+-- activeWith+--     :: (E.MonadState (GenST n t) m, Ord n, Ord t)+--     => (Int -> Bool) -> ListT m (Tree n t)+-- activeWith cond = visitedWith doneActive+++--------------------------+-- Higher-level generation+--------------------------+++-- | Randomized generation configuration.+-- First all derivable trees up to the size `genAllSize` are+-- generated, and on this basis other derived trees (with adjunction+-- probability controlled by `adjProb`) are constructed.+data GenConf = GenConf {+      genAllSize    :: Int+    -- ^ Generate all derivable trees up to the given size+    , adjProb       :: Double+    -- ^ Adjunction probability+    } deriving (Show, Eq, Ord)+++-- | Randomly generate derived trees from the grammar, according to+-- the given configuration.+generateRand+    :: (MonadIO m, Ord n, Ord t)+    => Gram n t+    -> GenConf+    -> Producer (Tree n t) m ()+generateRand gramSet cfg = E.forever $ do+    finalSet <- collect basePipe+    mayTree  <- drawTree gramSet finalSet cfg+    F.forM_ mayTree yield+--     case mayTree of+--         Nothing -> return ()+--         Just t  -> yield t+  where+    -- first compute the base set of final trees+    basePipe = generateAll gramSet (genAllSize cfg)+           >-> Pipes.filter isFinal+++-- | Try to construct randomly a tree based on the TAG grammar and+-- on the pre-built set of derived final trees.+drawTree+    :: (MonadIO m, Ord n, Ord t)+    => Gram n t     -- ^ The grammar+    -> Gram n t     -- ^ Final trees+    -> GenConf      -- ^ Global config+    -> m (Maybe (Tree n t))+drawTree gramSet finalSet GenConf{..} = runMaybeT $ do+    -- randomly draw an elementary tree+    t0 <- drawFrom $ limitTo isInitial gramSet+    -- recursivey modify the tree+    modify t0+  where+    modify t@(R.Node (Term _) []) =+        return t+    modify (R.Node (NonTerm x) []) =+        let cond = (&&) <$> hasRoot x <*> isInitial+        in  drawFrom (limitTo cond finalSet)+    modify (R.Node (NonTerm x) xs0) = do+        -- modify subtrees+        xs <- mapM modify xs0+        -- construct the new tree+        let t = R.Node (NonTerm x) xs+        -- adjoin some tree if lucky+        lottery adjProb (return t) $ do+            let cond = (&&) <$> hasRoot x <*> isAuxiliary+            auxTree <- drawFrom $ limitTo cond finalSet+            return $ replaceFoot t auxTree+    modify _ = error "drawTree.modify: unhandled node type"+    drawFrom s = do+        E.guard $ S.size s > 0+        i <- liftIO $ randomRIO (0, S.size s - 1)+        -- return $ S.elemAt i s <- works starting from containers 0.5.2+        return $ S.toList s !! i+    limitTo f = S.fromList . filter f . S.toList++++--------------------------+-- Generation+--------------------------+++-- | Type of the generator.+type Gen m n t = E.StateT (GenST n t) (Producer (Tree n t) m) ()+++-- | Generate all trees derivable from the given grammar up to the+-- given size.+generateAll+    :: (MonadIO m, Ord n, Ord t)+    => Gram n t -> Int -> Producer (Tree n t) m ()+generateAll gram0 sizeMax =+    -- gram <- subGram gram0+    E.evalStateT+        (genPipe sizeMax)+        (newGenST gram0)+++-- -- | Select sub-grammar rules.+-- subGram+--     :: (MonadIO m, Ord n, Ord t) => Double -> Gram n t -> m (Gram n t)+-- subGram probMax gram = do+--     stdGen <- liftIO getStdGen+--     let ps = randomRs (0, 1) stdGen+--     return $ S.fromList+--         [t | (t, p) <- zip (S.toList gram) ps, p <= probMax]+++-- | A function which generates trees derived from the grammar.  The+-- second argument allows to specify a probability of ignoring a tree+-- popped up from the waiting queue.  When set to `1`, all derived+-- trees up to the given size should be generated.+genPipe :: (MonadIO m, Ord n, Ord t) => Int -> Gen m n t+genPipe sizeMax = runListT $ do+    -- pop best-score tree from the queue+    t <- pop+    lift $ do+        genStep sizeMax t+        genPipe sizeMax+++-- | Generation step.+genStep+    :: (MonadIO m, Ord n, Ord t)+    => Int              -- ^ Tree size limit+    -> Tree n t         -- ^ Tree from the queue+    -> Gen m n t+genStep sizeMax t = runListT $ do+    -- check if it's not in the set of visited trees yet+    -- TODO: is it even necessary?+    E.guard . not =<< visited t++    -- save tree `t` and yield it+    save t+    lift . lift $ yield t++    -- choices based on whether 't' is final+    let doneMap = if isFinal t+            then doneActive+            else doneFinal++    -- find all possible combinations of 't' and some visited 'u',+    -- and add them to the waiting queue;+    -- note that `t` is now in the set of visited trees --+    -- this allows the process to generate `combinations t t`;+    u <- visitedWith doneMap $+        let n = treeSize t+        in \k -> k + n <= sizeMax + 1++    -- NOTE: at this point we know that `v` cannot yet be visited;+    -- it must be larger than any tree in the set of visited trees.+    let  combine x y = some $+            inject x y +++            inject y x+    v <- combine t u++    -- we only put to the queue trees which do not exceed+    -- the specified size+    E.guard $ treeSize v <= sizeMax+    push v+++---------------------------------------------------------------------+-- Composition+---------------------------------------------------------------------+++-- | Identify all possible ways to inject (i.e. substitute+-- or adjoin) the first tree to the second one.+inject :: (Eq n, Eq t) => Tree n t -> Tree n t -> [Tree n t]+inject s t = if isAuxiliary s+    then adjoin s t+    else subst s t+++-- | Compute all possible ways of adjoining the first tree into the+-- second one.+adjoin :: (Eq n, Eq t) => Tree n t -> Tree n t -> [Tree n t]+adjoin _ (R.Node (NonTerm _) []) = []+adjoin s (R.Node n ts) =+    here ++ below+  where+    -- perform adjunction here+    here = [replaceFoot (R.Node n ts) s | R.rootLabel s == n]+    -- consider to perform adjunction lower in the tree+    below = map (R.Node n) (doit ts)+    doit [] = []+    doit (x:xs) =+        [u : xs | u <- adjoin s x] +++        [x : us | us <- doit xs]+++-- | Replace foot of the second tree with the first tree.+-- If there is no foot in the second tree, it will be returned+-- unchanged.+replaceFoot :: Tree n t -> Tree n t -> Tree n t+replaceFoot t (R.Node (Foot _) []) = t+replaceFoot t (R.Node x xs) = R.Node x $ map (replaceFoot t) xs+++-- | Compute all possible ways of substituting the first tree into+-- the second one.+subst :: (Eq n, Eq t) => Tree n t -> Tree n t -> [Tree n t]+subst s = take 1 . _subst s+++-- | Compute all possible ways of substituting the first tree into+-- the second one.+_subst :: (Eq n, Eq t) => Tree n t -> Tree n t -> [Tree n t]+_subst s (R.Node n []) =+    [s | R.rootLabel s == n]+_subst s (R.Node n ts) =+    map (R.Node n) (doit ts)+  where+    doit [] = []+    doit (x:xs) =+        [u : xs | u <- subst s x] +++        [x : us | us <- doit xs]+++--------------------------+-- Utils+--------------------------+++-- -- | MaybeT constructor.+-- maybeT :: Monad m => Maybe a -> MaybeT m a+-- maybeT = MaybeT . return+++-- | ListT from a list.+some :: Monad m => [a] -> ListT m a+some = Select . each+++-- -- | Draw a number between 0 and 1, and check if it is <= the given+-- -- maximal probability.+-- lottery :: (MonadPlus m, MonadIO m) => Double -> m ()+-- lottery probMax = do+--     p <- liftIO $ randomRIO (0, 1)+--     E.guard $ p <= probMax+++-- | Collect elements from the pipe into a set.+collect :: (Monad m, Ord a) => Producer a m () -> m (S.Set a)+collect inputPipe =+    flip E.execStateT S.empty+        $ runEffect+            $ hoist lift inputPipe >-> collectPipe+  where+    collectPipe = E.forever $ do+        x <- await+        lift . E.modify $ S.insert x+++-- | Run `my` if lucky, `mx` otherwise.+lottery :: (MonadIO m, MonadPlus m) => Double -> m a -> m a -> m a+lottery probMax mx my = do+    p <- liftIO $ randomRIO (0, 1)+    if p > probMax+        then mx+        else my
+ src/NLP/Partage/SOrd.hs view
@@ -0,0 +1,21 @@+{-# LANGUAGE FlexibleInstances    #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE CPP #-}+++-- | Internal typeclass representing `Show` + `Ord`.+++module NLP.Partage.SOrd+( SOrd+) where+++-- | 'Show' + 'Ord'+#ifdef Debug+class (Show a, Ord a) => SOrd a where+instance (Show a, Ord a) => SOrd a where+#else+class Ord a => SOrd a where+instance Ord a => SOrd a where+#endif
+ src/NLP/Partage/SubtreeSharing.hs view
@@ -0,0 +1,308 @@+{-# LANGUAGE RecordWildCards      #-}+++-- | TAG conversion into flat production rules.+-- Due to subtree sharing provided by `flattenWithSharing`, a single+-- rule can be responsible for recognizing a subtree common to many+-- different elementary trees.+--+++module NLP.Partage.SubtreeSharing+(+-- * Grammar flattening+  flattenWithSharing+) where+++import           Control.Applicative        ((<$>))+import           Control.Arrow              (second)+import           Control.Monad              (guard, forever)+import qualified Control.Monad.State.Strict as E+import           Control.Monad.Trans.Class  (lift)+import           Control.Monad.Identity     (Identity(..))+import           Control.Monad.Morph        (generalize)++import           Data.Function              (on)+import           Data.Monoid                (mappend, mconcat)+import           Data.Maybe                 (isJust)+import qualified Data.Set                   as S+import qualified Data.Partition             as Part+import qualified Pipes                      as P+import           Pipes                      (hoist, (>->))++import           NLP.Partage.FactGram.Internal+    ( FactGram, Lab(..), Rule(..), SymID )+import qualified NLP.Partage.FactGram.Internal as Rule+import qualified NLP.Partage.Tree as G+++--------------------------------------------------+-- Compilation+--------------------------------------------------+++-- | Compile the given grammar into the list of rules.+-- Common subtrees are shared.+flattenWithSharing+    :: (Functor m, Monad m, Ord n, Ord t)+    => [ Either+        (G.Tree n t)+        (G.AuxTree n t) ]+    -> m (FactGram n t)+flattenWithSharing ts =+    fmap snd $ runDupT $ Rule.runRM $ P.runEffect $+        P.for shared (const $ return ())+  where+    shared = hoist (hoist (hoist generalize))+        (   hoist (hoist lift) rules+        >-> hoist lift rmDups )+    rules = mapM_ getRules ts+    getRules (Left t)  = Rule.treeRules t+    getRules (Right t) = Rule.auxRules  t+++--------------------------------------------------+-- Eq/Ord Instances for RuleP+--------------------------------------------------+++-- | We define a newtype in order to define a custom Eq/Ord instances+-- take the symbol of the head into account in a different manner.+newtype RuleP n t = RuleP+    { unRuleP :: Rule n t+    } deriving (Show)+++-- | Ordinary label equality.+labEq+    :: (Eq n, Eq t)+    => Lab n t -> Lab n t -> Bool+labEq p q = p == q+++-- | Label equality.  Concerning the `SymID` values, it is only+-- checkes if either both are `Nothing` or both are `Just`.+labEq' :: (Eq n, Eq t) => Lab n t -> Lab n t -> Bool+labEq' p q =+    eq p q+  where+    eq x@NonT{} y@NonT{}+        =  eqOn nonTerm x y+        && eqOn (isJust . labID) x y+    eq x@AuxVert{} y@AuxVert{}+        =  eqOn nonTerm x y+    eq _ _ = p == q+    eqOn f x y = f x == f y+++-- | Ordinary label comparison.+labCmp :: (Ord n, Ord t) => Lab n t -> Lab n t -> Ordering+labCmp p q = compare p q+++-- | Label comparison.  Concerning the `SymID` values, it is only+-- checked if either both are `Nothing` or both are `Just`.+labCmp' :: (Ord n, Ord t) => Lab n t -> Lab n t -> Ordering+labCmp' p q =+    cmp p q+  where+    cmp x@NonT{} y@NonT{} =+        cmpOn nonTerm x y       `mappend`+        cmpOn (isJust . labID) x y+    cmp x@AuxVert{} y@AuxVert{} =+        cmpOn nonTerm x y+    cmp _ _ = compare p q+    cmpOn f x y = compare (f x) (f y)+++instance (Eq n, Eq t) => Eq (RuleP n t) where+    r == s = (hdEq `on` headP) r s+        && ((==) `on` length.bodyP) r s+        && and [eq x y | (x, y) <- zip (bodyP r) (bodyP s)]+      where+        eq x y   = labEq  x y+        hdEq x y = labEq' x y+        headP    = headR . unRuleP+        bodyP    = bodyR . unRuleP+++instance (Ord n, Ord t) => Ord (RuleP n t) where+    r `compare` s = (hdCmp `on` headP) r s    `mappend`+        (compare `on` length.bodyP) r s       `mappend`+        mconcat [cmp x y | (x, y) <- zip (bodyP r) (bodyP s)]+      where+        cmp x y   = labCmp  x y+        hdCmp x y = labCmp' x y+        headP     = headR . unRuleP+        bodyP     = bodyR . unRuleP+++--------------------------------------------------+-- Substructure Sharing+--------------------------------------------------+++-- | Duplication-removal state serves to share common+-- substructures.+--+-- The idea is to remove redundant rules equivalent to other+-- rules already present in the set of processed rules+-- `rulDepo`(sit).+--+-- Note that rules have to be processed in an appropriate order+-- so that lower-level rules are processed before the+-- higher-level rules from which they are referenced.+data DupS n t = DupS {+    -- | A disjoint set for `SymID`s+      symDisj   :: Part.Partition SymID+    -- | Rules already saved+    , rulDepo   :: S.Set (RuleP n t)+    }+++-- Let us take a rule and let us assume that all identifiers it+-- contains references to rules which have already been processed+-- (for this assumption to be valid we just need to order the+-- input set of rules properly).  So we have a rule `r`, a set of+-- processed rules `rs` and a clustering (disjoint-set) over+-- `SymID`s present in `rs`.+--+-- Now we want to process `r` and, in particular, check if it is+-- not already in `rs` and update its `SymID`s.+--+-- First we translate the body w.r.t. the existing clustering of+-- `SymID`s (thanks to our assumption, these `SymID`s are already+-- known and processed).  The `SymID` in the root of the rule (if+-- present) is the new one and it should not yet have been mentioned+-- in `rs`.  Even when `SymID` is not present in the root, we can+-- still try to check if `r` is not present in `rs` -- after all,+-- there may be some duplicates in the input grammar.+--+-- Case 1: we have a rule with a `SymID` in the root.  We want to+-- check if there is already a rule in `rs` which:+-- * Has identical body (remember that we have already+--   transformed `SymID`s of the body of the rule in question)+-- * Has the same non-terminal in the root and some `SymID`+--+-- Case 2: the same as case 1 with the difference that we look+-- for the rules which have an empty `SymID` in the root.+--+-- For this to work we just need a specific comparison function+-- which works as specified in the two cases desribed above+-- (i.e. either there are some `SymID`s in the rule heads, or+-- there are no `SymID`s in both heads.)+--+-- Once we have this comparison (which is provided by the+-- function `labCmp'` above), we simply process the set of rules+-- incrementally.+++-- | Duplication-removal transformer.+type DupT n t m = E.StateT (DupS n t) m+++-- | Duplication-removal monad.+type DupM n t = DupT n t Identity+++-- | Run the transformer.+runDupT+    :: (Functor m, Monad m, Ord t, Ord n)+    => DupT n t m b+    -> m (b, S.Set (Rule n t))+runDupT = fmap (second getRules) . flip E.runStateT+    (DupS Part.empty S.empty)+  where+    getRules+        = S.fromList . map unRuleP+        . S.toList. rulDepo+++-- | Update the body of the rule by replacing old `SymID`s with+-- their representatives.+updateBody+    :: RuleP n t+    -> DupM n t (RuleP n t)+updateBody (RuleP r) = do+    d <- E.gets symDisj+    let body' = map (updLab d) (bodyR r)+    return . RuleP $ r { bodyR = body' }+  where+    updLab d x@NonT{..}     = x { labID = updSym d <$> labID }+    updLab d x@AuxVert{..}  = x { symID = updSym d symID }+    updLab _ x              = x+    updSym                  = Part.rep+++-- | Find a rule if already present.+findRule+    :: (Ord n, Ord t)+    => RuleP n t+    -> DupM n t (Maybe (RuleP n t))+findRule x = do+    s <- E.gets rulDepo+    return $ lookupSet x s+++-- | Join two `SymID`s.+joinSym :: SymID -> SymID -> DupM n t ()+joinSym x y = E.modify $ \s@DupS{..} -> s+    { symDisj = Part.joinElems x y symDisj }++++-- | Save the rule in the underlying deposit.+keepRule+    :: (Ord n, Ord t)+    => RuleP n t+    -> DupM n t ()+keepRule r = E.modify $ \s@DupS{..} -> s+    { rulDepo = S.insert r rulDepo }+++-- | Retrieve the symbol of the head of the rule.+headSym :: RuleP n t -> Maybe SymID+headSym r = case headR (unRuleP r) of+    NonT{..}    -> labID+    AuxVert{..} -> Just symID+    _           -> Nothing+++-- | Removing duplicates updating `SymID`s at the same time.+-- WARNING: The pipe assumes that `SymID`s to which the present+-- rule refers have already been processed -- in other words,+-- that rule on which the present rule depends have been+-- processed earlier.+--+-- This function is responsible for basic sharing of common+-- subtrees.+rmDups+    :: (Ord n, Ord t)+    => P.Pipe+        (Rule n t)    -- Input+        (Rule n t)    -- Output+        (DupM n t)    -- Underlying state+        ()            -- No result+rmDups = forever $ do+    r <- P.await >>= lift . updateBody . RuleP+    lift (findRule r) >>= \mr -> case mr of+        Nothing -> do+            lift $ keepRule r+            P.yield $ unRuleP r+        Just r' -> case (headSym r, headSym r') of+            (Just x, Just y)    -> lift $ joinSym x y+            _                   -> return ()+++--------------------------------------------------+-- Utilities+--------------------------------------------------+++-- | Lookup an element in a set.+lookupSet :: Ord a => a -> S.Set a -> Maybe a+lookupSet x s = do+    y <- S.lookupLE x s+    guard $ x == y+    return y
+ src/NLP/Partage/Tree.hs view
@@ -0,0 +1,233 @@+{-# LANGUAGE RecordWildCards #-}+++-- | This module provides datatypes representing TAG trees.+-- `Tree` is an initial tree, while `AuxTree` represents an auxiliary+-- tree.+++module NLP.Partage.Tree+(+-- * Initial tree+  Tree (..)+-- , showTree+-- , showTree'+, project++-- * Auxiliary tree+, AuxTree (..)+-- ** Path+, Path+, follow++-- -- * Combining operations+-- -- ** Substitution+-- , subst+-- -- ** Adjoining+-- , adjoin++-- -- * Derivation+-- , Deriv+-- , Trans+-- , derive+-- -- * Traversal+-- , walk+) where+++-- import           Control.Applicative ((<$>))+-- import           Control.Arrow (first)+import           Control.Monad (foldM)+++-- | A tree with values of type @a@ (/non-termianls/) kept in+-- branching nodes, and values of type @b@ (/terminals/) kept in leaf+-- nodes+data Tree a b+    -- | Branching node with a non-terminal symbol+    = Branch+        { labelI    :: a+        -- ^ The non-terminal kept in the branching node+        , subTrees  :: [Tree a b]+        -- ^ The list of subtrees+        }+    -- | Leaf node with a terminal symbol+    | Leaf+        { labelF    :: b+        -- ^ The terminal symbol+        }+    deriving (Show, Eq, Ord)+++-- | List of frontier values.+toWord :: Tree a b -> [b]+toWord t = case t of+    Branch{..} -> concatMap toWord subTrees+    Leaf{..}   -> [labelF]+++-- | Projection of a tree: the list of terminal symbols in its+-- leaves+project :: Tree a b -> [b]+project = toWord+++-- -- | Replace the tree on the given position.+-- replaceChild :: Tree a b -> Int -> Tree a b -> Tree a b+-- replaceChild t@INode{..} k t' = t { subTrees = replace subTrees k t' }+-- replaceChild _ _ _ = error "replaceChild: frontier node"+++-- -- | Show a tree given the showing functions for label values.+-- showTree :: (a -> String) -> (b -> String) -> Tree a b -> String+-- showTree f g = unlines . go+--   where+--     go t = case t of+--         INode{..}   -> ("INode " ++ f labelI)+--             : map ("  " ++) (concatMap go subTrees)+--         FNode{..}   -> ["FNode " ++ g labelF]+--+--+-- -- | Like `showTree`, but using the default `Show` instances+-- -- to present label values.+-- showTree' :: (Show a, Show b) => Tree a b -> String+-- showTree' = showTree show show+++---------------------------------------------------------------------+-- Path+---------------------------------------------------------------------+++-- | A path indicates a particular node in a tree and can be used to+-- extract a particular subtree of the tree (see `follow`).+-- For instance, @[]@ designates the entire tree, @[0]@ the first+-- child, and @[1,3]@ the fourth child of the second child of the+-- underlying tree.+type Path = [Int]+++-- | Follow the path to a particular subtree.+follow :: Path -> Tree a b -> Maybe (Tree a b)+follow = flip $ foldM step+++-- | Follow one step of the `Path`.+step :: Tree a b -> Int -> Maybe (Tree a b)+step (Leaf _) _      = Nothing+step (Branch _ xs) k = xs !? k+++---------------------------------------------------------------------+-- Substitution+---------------------------------------------------------------------+++-- -- | Perform substitution on a tree.  It is neither whether+-- -- the path indicates a leaf, nor if its symbol is identical to the+-- -- symbol of the root of the substituted tree.+-- subst+--     :: Path             -- ^ Place of the substitution+--     -> Tree a b         -- ^ Tree to be substituted+--     -> Tree a b         -- ^ Original tree+--     -> Maybe (Tree a b) -- ^ Resulting tree (or `Nothing`+--                         --   if substitution not possible)+-- subst (k:ks) st t = do+--     replaceChild t k <$> (step t k >>= subst ks st)+-- subst [] st _ = Just st+++---------------------------------------------------------------------+-- Adjoining+---------------------------------------------------------------------+++-- | An auxiliary tree+data AuxTree a b = AuxTree+    { auxTree   :: Tree a b+    -- ^ The underlying initial tree+    , auxFoot   :: Path+    -- ^ The path to the foot node.  Beware that currently it is+    -- possible to use the `AuxTree` constructor to build an invalid+    -- auxiliary tree, i.e. with an incorrect `auxFoot` value.+    } deriving (Show, Eq, Ord)+++-- -- | Perform adjoining operation on a tree.+-- adjoin+--     :: Path             -- ^ Where to adjoin+--     -> AuxTree a b      -- ^ Tree to be adjoined+--     -> Tree a b         -- ^ Tree with the node to be modified+--     -> Maybe (Tree a b) -- ^ Resulting tree+-- adjoin (k:ks) aux t = do+--     replaceChild t k <$> (step t k >>= adjoin ks aux)+-- adjoin [] AuxTree{..} t = do+--     subst auxFoot t auxTree+++-- ---------------------------------------------------------------------+-- -- Derivation+-- ---------------------------------------------------------------------+--+--+-- -- | A derived tree is constructed by applying a sequence of+-- -- transforming (substitution or adjoining) rules on particular+-- -- positions of a tree.  The `Deriv` sequence represents a+-- -- derivation process.  One could also construct a derivation+-- -- tree, which to some extent abstracts over the particular order+-- -- of derivations (when it doesn't matter).+-- type Deriv a b = [(Path, Trans a b)]+--+--+-- -- | Transformation of a tree.+-- type Trans a b = Either (Tree a b) (AuxTree a b)+--+--+-- -- | Derive a tree.+-- derive :: Deriv a b -> Tree a b -> Maybe (Tree a b)+-- derive =+--     flip $ foldM m+--   where+--     m t (pos, op) = case op of+--         Left x  -> subst  pos x t+--         Right x -> adjoin pos x t+++---------------------------------------------------------------------+-- Traversal+---------------------------------------------------------------------+++-- -- | Return all tree paths with corresponding subtrees.+-- walk :: Tree a b -> [(Path, Tree a b)]+-- walk =+--     map (first reverse) . go []+--   where+--     go acc n@INode{..} = (acc, n) : concat+--         [ go (k:acc) t+--         | (k, t) <- zip [0..] subTrees ]+--     go acc n@FNode{..} = [(acc, n)]+++---------------------------------------------------------------------+-- Misc+---------------------------------------------------------------------+++-- | Maybe a k-th element of a list.+(!?) :: [a] -> Int -> Maybe a+(x:xs) !? k+    | k > 0     = xs !? (k-1)+    | otherwise = Just x+[] !? _ = Nothing+++-- -- | Replace the k-th element of a list.  If the given position is+-- -- outside of the list domain, the returned list will be unchanged.+-- -- It the given index is negative, the first element will be+-- -- replaced.+-- replace :: [a] -> Int -> a -> [a]+-- replace (x:xs) k y+--     | k > 0     = x : replace xs (k - 1) y+--     | otherwise = y : xs+-- replace [] _ _  = []
+ src/NLP/Partage/Tree/Other.hs view
@@ -0,0 +1,172 @@+{-# LANGUAGE RecordWildCards #-}+++-- | Alternative (to "NLP.Partage.Tree") representation of TAG+-- trees, in which information about the foot is present in the tree+-- itself.+++module NLP.Partage.Tree.Other+(+-- * TAG Tree+  Tree+, Node (..)+-- ** Base representation+, SomeTree++-- * Conversion+, encode+, decode++-- * Utils+, isTerm+, isFinal+, isInitial+, isAuxiliary+, hasRoot+, project+) where+++-- import           Control.Applicative ((<$>))+import           Control.Monad (msum)+-- import           Data.Maybe (isJust)+import qualified Data.Foldable as F++import qualified Data.Tree as R++import qualified NLP.Partage.Tree as T+++---------------------------------------------------------------------+-- Types+---------------------------------------------------------------------+++-- | Node of a TAG tree.+data Node n t+    = NonTerm n     -- ^ Standard non-terminal+    | Foot n        -- ^ Foot non-terminal+    | Term t        -- ^ Terminal+    deriving (Show, Eq, Ord)+++-- | Is it a teminal?+isTerm :: Node n t -> Bool+isTerm (Term _) = True+isTerm _        = False+++-- | An initial or auxiliary TAG tree.  Note that the type doesn't+-- ensure that the foot is placed in a leaf, nor that there is at+-- most one foot node.  On the other hand, and in contrast to+-- "NLP.Partage.Tree", information about the foot is available at+-- the level of the corresponding foot node.+type Tree n t = R.Tree (Node n t)+++-- | An original tree representation (see "NLP.Partage.Tree").+type SomeTree n t = Either (T.Tree n t) (T.AuxTree n t)+++---------------------------------------------------------------------+-- Encoding+---------------------------------------------------------------------+++-- | Encode the tree using the alternative representation.+encode :: SomeTree n t -> Tree n t+encode (Left t) = unTree t+encode (Right T.AuxTree{..}) = markFoot auxFoot (unTree auxTree)+++-- | Encode the initial tree using the alternative representation.+unTree :: T.Tree n t -> Tree n t+unTree (T.Branch x xs) = R.Node (NonTerm x) (map unTree xs)+unTree (T.Leaf x)    = R.Node (Term x) []+++-- | Mark non-terminal under the path as a foot.+markFoot :: T.Path -> Tree n t -> Tree n t+markFoot [] (R.Node (NonTerm x) []) = R.Node (Foot x) []+markFoot (i:is) (R.Node y ys) =+    R.Node y $ doit i ys+  where+    doit 0 (x:xs) = markFoot is x : xs+    doit k (x:xs) = x : doit (k-1) xs+    doit _      _      = error "markFoot.doit: unhandled case"+markFoot _ _ = error "markFoot: unhandled case"+++---------------------------------------------------------------------+-- Decoding+---------------------------------------------------------------------+++-- | Decode the tree represented with the alternative representation.+decode :: Tree n t -> SomeTree n t+decode t = case findFoot t of+    Just is -> Right $ T.AuxTree (mkTree t) is+    Nothing -> Left $ mkTree t+++-- | Convert the parsed tree into an LTAG tree.+mkTree :: Tree n t -> T.Tree n t+mkTree (R.Node n xs) = case n of+    Term x  -> T.Leaf x+    Foot x  -> T.Branch+        { T.labelI = x+        , T.subTrees = [] }+    NonTerm x   -> T.Branch+        { T.labelI = x+        , T.subTrees = map mkTree xs }+++-- | Find the path of the foot (if present) in the tree.+findFoot :: Tree n t -> Maybe T.Path+findFoot (R.Node n xs) = case n of+    Foot _  -> Just []+    _       -> msum+        $ zipWith addID [0..]+        $ map findFoot xs+  where+    addID i (Just is) = Just (i:is)+    addID _ Nothing   = Nothing+++---------------------------------------------------------------------+-- Utils+---------------------------------------------------------------------+++-- | Is it an initial (i.e. non-auxiliary) tree?+isInitial :: Tree n t -> Bool+isInitial = not . isAuxiliary+++-- | Is it an auxiliary (i.e. with a foot) tree?+isAuxiliary :: Tree n t -> Bool+isAuxiliary (R.Node (Foot _) _) = True+isAuxiliary (R.Node _ xs) = any isAuxiliary xs+++-- | Is it a final tree (i.e. does it contain only terminals+-- in its leaves)?+isFinal :: Tree n t -> Bool+isFinal (R.Node n []) = isTerm n+isFinal (R.Node _ xs) = all isFinal xs+++-- | Projection of a tree, i.e. a list of its terminals.+project :: Tree n t -> [t]+project =+    F.foldMap term+  where+    term (Term x) = [x]+    term _        = []+++-- | Is it a root label of the given tree?+hasRoot :: Eq n => n -> Tree n t -> Bool+hasRoot x (R.Node (NonTerm y) _) = x == y+hasRoot _ _ = False
+ tests/Parser.hs view
@@ -0,0 +1,31 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+++-- | Testing the automata-based Earley-style TAG parser.+++module Parser where+++import           Control.Applicative ((<$>))+import           Control.Monad (forM_)+import           Test.Tasty (TestTree)+import qualified Data.Set as S++import qualified NLP.Partage.Earley as E++import qualified TestSet as T+++-- | All the tests of the parsing algorithm.+tests :: TestTree+tests = T.testTree "Parser"+    recFrom (Just parseFrom)+  where+    recFrom gram start+        = E.recognizeFrom gram start+        . map S.singleton+    parseFrom gram start+        = E.parse gram start+        . map S.singleton
+ tests/TestSet.hs view
@@ -0,0 +1,349 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TupleSections #-}+++-- | The module collects the sample grammars used for testing and+-- unit test examples themselves.+++module TestSet+( Test (..)+, TestRes (..)+, mkGram1+, gram1Tests+, mkGram2+, gram2Tests+, mkGram3+, gram3Tests++, Gram+, testTree+)  where+++import           Control.Applicative ((<$>), (<*>))++import qualified Data.Set as S+import qualified Data.Map.Strict as M++import           Test.Tasty (TestTree, testGroup, withResource)+import           Test.HUnit (Assertion, (@?=))+import           Test.Tasty.HUnit (testCase)++import           NLP.Partage.Tree (Tree (..), AuxTree (..))+import           NLP.Partage.FactGram (Rule, flattenWithSharing)+++---------------------------------------------------------------------+-- Prerequisites+---------------------------------------------------------------------+++type Tr    = Tree String String+type AuxTr = AuxTree String String+type Rl    = Rule String String+-- type WRl   = W.Rule String String+++-- | A compiled grammar.+type Gram  = S.Set Rl++-- -- | A compiled grammar with weights.+-- type WeightedTree  = (Tr, Cost)+-- type WeightedAux   = (AuxTr, Cost)+-- type WeightedGram  = S.Set WRl+++---------------------------------------------------------------------+-- Tests+---------------------------------------------------------------------+++-- | A single test case.+data Test = Test {+    -- | Starting symbol+      startSym  :: String+    -- | The sentence to parse (list of words)+    , testSent  :: [String]+    -- | The expected recognition result+    , testRes   :: TestRes+    } deriving (Show, Eq, Ord)+++-- | The expected test result.  The set of parsed trees can be optionally+-- specified.+data TestRes+    = No+    -- ^ No parse+    | Yes+    -- ^ Parse+    | Trees (S.Set Tr)+--     -- ^ Parsing results+--     | WeightedTrees (M.Map Tr Cost)+--     -- ^ Parsing results with weights+    deriving (Show, Eq, Ord)+++---------------------------------------------------------------------+-- Grammar1+---------------------------------------------------------------------+++tom :: Tr+tom = Branch "NP"+    [ Branch "N"+        [Leaf "Tom"]+    ]+++sleeps :: Tr+sleeps = Branch "S"+    [ Branch "NP" []+    , Branch "VP"+        [Branch "V" [Leaf "sleeps"]]+    ]+++caught :: Tr+caught = Branch "S"+    [ Branch "NP" []+    , Branch "VP"+        [ Branch "V" [Leaf "caught"]+        , Branch "NP" [] ]+    ]+++almost :: AuxTr+almost = AuxTree (Branch "V"+    [ Branch "Ad" [Leaf "almost"]+    , Branch "V" []+    ]) [1]+++quickly :: AuxTr+quickly = AuxTree (Branch "V"+    [ Branch "Ad" [Leaf "quickly"]+    , Branch "V" []+    ]) [1]+++a :: Tr+a = Branch "D" [Leaf "a"]+++mouse :: Tr+mouse = Branch "NP"+    [ Branch "D" []+    , Branch "N"+        [Leaf "mouse"]+    ]+++-- | Compile the first grammar.+mkGram1 :: IO Gram+mkGram1 = flattenWithSharing $+    map Left [tom, sleeps, caught, a, mouse] +++    map Right [almost, quickly]+++---------------------------------------------------------------------+-- Grammar1 Tests+---------------------------------------------------------------------+++gram1Tests :: [Test]+gram1Tests =+    -- group 1+    [ Test "S" ["Tom", "sleeps"] . Trees . S.singleton $+        Branch "S"+            [ Branch "NP"+                [ Branch "N"+                    [Leaf "Tom"]+                ]+            , Branch "VP"+                [Branch "V" [Leaf "sleeps"]]+            ]+    , Test "S" ["Tom"] No+    , Test "NP" ["Tom"] Yes+    -- group 2+    , Test "S" ["Tom", "almost", "caught", "a", "mouse"] . Trees . S.singleton $+        Branch "S"+            [ Branch "NP"+                [ Branch "N"+                    [ Leaf "Tom" ] ]+            , Branch "VP"+                [ Branch "V"+                    [ Branch "Ad"+                        [Leaf "almost"]+                    , Branch "V"+                        [Leaf "caught"]+                    ]+                , Branch "NP"+                    [ Branch "D"+                        [Leaf "a"]+                    , Branch "N"+                        [Leaf "mouse"]+                    ]+                ]+            ]+    , Test "S" ["Tom", "caught", "almost", "a", "mouse"] No+    , Test "S" ["Tom", "quickly", "almost", "caught", "Tom"] Yes+    , Test "S" ["Tom", "caught", "a", "mouse"] Yes+    , Test "S" ["Tom", "caught", "Tom"] Yes+    , Test "S" ["Tom", "caught", "a", "Tom"] No+    , Test "S" ["Tom", "caught"] No+    , Test "S" ["caught", "a", "mouse"] No ]+++---------------------------------------------------------------------+-- Grammar2+---------------------------------------------------------------------+++alpha :: Tr+alpha = Branch "S"+    [ Branch "X"+        [Leaf "e"] ]+++beta1 :: AuxTr+beta1 = AuxTree (Branch "X"+    [ Leaf "a"+    , Branch "X"+        [ Branch "X" []+        , Leaf "a" ] ]+    ) [1,0]+++beta2 :: AuxTr+beta2 = AuxTree (Branch "X"+    [ Leaf "b"+    , Branch "X"+        [ Branch "X" []+        , Leaf "b" ] ]+    ) [1,0]+++mkGram2 :: IO Gram+mkGram2 = flattenWithSharing $+    map Left [alpha] +++    map Right [beta1, beta2]+++---------------------------------------------------------------------+-- Grammar2 Tests+---------------------------------------------------------------------+++-- | What we test is not really a copy language but rather a+-- language in which there is always the same number of `a`s and+-- `b`s on the left and on the right of the empty `e` symbol.+-- To model the real copy language with a TAG we would need to+-- use either adjunction constraints or feature structures.+gram2Tests :: [Test]+gram2Tests =+    [ Test "S" (words "a b e a b") Yes+    , Test "S" (words "a b e a a") No+    , Test "S" (words "a b a b a b a b e a b a b a b a b") Yes+    , Test "S" (words "a b a b a b a b e a b a b a b a  ") No+    , Test "S" (words "a b e b a") Yes+    , Test "S" (words "b e a") No+    , Test "S" (words "a b a b") No ]+++---------------------------------------------------------------------+-- Grammar 3+---------------------------------------------------------------------+++mkGram3 :: IO Gram+mkGram3 = flattenWithSharing $+    map Left [sent] +++    map Right [xtree]+  where+    sent = Branch "S"+        [ Leaf "p"+        , Branch "X"+            [Leaf "e"]+        , Leaf "b" ]+    xtree = AuxTree (Branch "X"+        [ Leaf "a"+        , Branch "X" []+        , Leaf "b" ]+        ) [1]+++-- | Here we check that the auxiliary tree must be fully+-- recognized before it can be adjoined.+gram3Tests :: [Test]+gram3Tests =+    [ Test "S" (words "p a e b b") Yes+    , Test "S" (words "p a e b") No ]+++---------------------------------------------------------------------+-- Resources+---------------------------------------------------------------------+++-- | Compiled grammars.+data Res = Res+    { gram1 :: Gram+    , gram2 :: Gram+    , gram3 :: Gram }+++-- | Construct the shared resource (i.e. the grammars) used in+-- tests.+mkGrams :: IO Res+mkGrams = Res <$> mkGram1 <*> mkGram2 <*> mkGram3+++---------------------------------------------------------------------+-- Test Tree+---------------------------------------------------------------------+++-- | All the tests of the parsing algorithm.+testTree+    :: String+        -- ^ Name of the tested module+    -> (Gram -> String -> [String] -> IO Bool)+        -- ^ Recognition function+    -> Maybe (Gram -> String -> [String] -> IO (S.Set Tr))+        -- ^ Parsing function (optional)+    -> TestTree+testTree modName reco parse = withResource mkGrams (const $ return ()) $+    \resIO -> testGroup modName $+        map (testIt resIO gram1) gram1Tests +++        map (testIt resIO gram2) gram2Tests +++        map (testIt resIO gram3) gram3Tests+  where+    testIt resIO getGram test = testCase (show test) $ do+        gram <- getGram <$> resIO+        doTest gram test++    doTest gram test@Test{..} = case (parse, testRes) of+        (Nothing, _) ->+            reco gram startSym testSent @@?= simplify testRes+        (Just pa, Trees ts) ->+            pa gram startSym testSent @@?= ts+        _ ->+            reco gram startSym testSent @@?= simplify testRes++    simplify No         = False+    simplify Yes        = True+    simplify (Trees _)  = True+--     simplify (WeightedTrees _)+--                         = True++---------------------------------------------------------------------+-- Utils+---------------------------------------------------------------------+++(@@?=) :: (Show a, Eq a) => IO a -> a -> Assertion+mx @@?= y = do+    x <- mx+    x @?= y
+ tests/test.hs view
@@ -0,0 +1,10 @@+import           Test.Tasty (defaultMain, testGroup, localOption)++import           TestSet+import qualified Parser+++main :: IO ()+main = defaultMain $ testGroup "Tests"+    [ Parser.tests+    ]