GenI 0.16.1 → 0.17.3
raw patch · 87 files changed
+16408/−3753 lines, 87 filesdep +filepathdep −HaXmldep −libGenIdep −wxcoredep ~HUnitdep ~QuickCheckdep ~basesetup-changedbinary-added
Dependencies added: filepath
Dependencies removed: HaXml, libGenI, wxcore
Dependency ranges changed: HUnit, QuickCheck, base, binary, containers, mtl, parsec, process, wx
Files
- AUTHORS +2/−0
- EnableGUI.hs +0/−38
- GenI.cabal +102/−19
- INSTALL +17/−0
- LICENSE +2/−2
- MainGeni.lhs +0/−87
- NEWS +13/−0
- NLP/GenI/BuilderGui.lhs +0/−34
- NLP/GenI/CkyEarley/CkyGui.lhs +0/−457
- NLP/GenI/Console.hs +0/−191
- NLP/GenI/Graphviz.hs +0/−212
- NLP/GenI/GraphvizShow.lhs +0/−222
- NLP/GenI/GraphvizShowPolarity.lhs +0/−130
- NLP/GenI/Gui.lhs +0/−762
- NLP/GenI/GuiHelper.lhs +0/−858
- NLP/GenI/Simple/SimpleGui.lhs +0/−201
- NLP/GenI/SysGeni.lhs +0/−110
- NLP/GenI/unused/Predictors.lhs +0/−315
- README +24/−0
- Setup.lhs +1/−1
- etc/macstuff/Info.plist +36/−0
- etc/macstuff/macosx-app +114/−0
- etc/macstuff/wxmac.icns binary
- examples/artificial/lexicon +56/−0
- examples/artificial/macros +126/−0
- examples/artificial/suite +29/−0
- examples/artificial/suite-bad +11/−0
- examples/chatnoir/lexicon +65/−0
- examples/chatnoir/macros +132/−0
- examples/chatnoir/suite +36/−0
- examples/demo/README +48/−0
- examples/demo/lexicon +96/−0
- examples/demo/macros +439/−0
- examples/demo/suite +19/−0
- examples/ej/lexicon +100/−0
- examples/ej/macros +92/−0
- examples/ej/suite +88/−0
- examples/nosemantics/README.txt +19/−0
- examples/nosemantics/lexicon +20/−0
- examples/nosemantics/macros +363/−0
- examples/promettre/lexicon +78/−0
- examples/promettre/macros +450/−0
- examples/promettre/morphinfo +33/−0
- examples/promettre/suite +113/−0
- examples/xmg-example/Makefile +138/−0
- examples/xmg-example/README +8/−0
- examples/xmg-example/grammar/Arguments.mg +146/−0
- examples/xmg-example/grammar/Entete.mg +65/−0
- examples/xmg-example/grammar/Evaluations.mg +21/−0
- examples/xmg-example/grammar/Misc.mg +32/−0
- examples/xmg-example/grammar/Sem.mg +80/−0
- examples/xmg-example/grammar/VerbMorph.mg +78/−0
- examples/xmg-example/grammar/demo-corpus-latin1.txt +6/−0
- examples/xmg-example/grammar/parse-corpus.sh +16/−0
- examples/xmg-example/lexicon/demo-lemma-latin1.lex +113/−0
- examples/xmg-example/lexicon/demo-morph-latin1.mph +24/−0
- examples/xmg-example/suite +39/−0
- macstuff/macosx-app +0/−114
- src/EnableGUI.hs +38/−0
- src/MainGeni.lhs +88/−0
- src/NLP/GenI/Automaton.lhs +140/−0
- src/NLP/GenI/Btypes.lhs +987/−0
- src/NLP/GenI/BtypesBinary.hs +54/−0
- src/NLP/GenI/Builder.lhs +480/−0
- src/NLP/GenI/BuilderGui.lhs +34/−0
- src/NLP/GenI/CkyEarley/CkyBuilder.lhs +1243/−0
- src/NLP/GenI/CkyEarley/CkyGui.lhs +456/−0
- src/NLP/GenI/Configuration.lhs +873/−0
- src/NLP/GenI/Console.hs +196/−0
- src/NLP/GenI/General.hs +435/−0
- src/NLP/GenI/Geni.lhs +1029/−0
- src/NLP/GenI/GeniParsers.lhs +763/−0
- src/NLP/GenI/GeniShow.lhs +185/−0
- src/NLP/GenI/Graphviz.hs +212/−0
- src/NLP/GenI/GraphvizShow.lhs +222/−0
- src/NLP/GenI/GraphvizShowPolarity.lhs +130/−0
- src/NLP/GenI/Gui.lhs +756/−0
- src/NLP/GenI/GuiHelper.lhs +860/−0
- src/NLP/GenI/Morphology.lhs +218/−0
- src/NLP/GenI/Polarity.lhs +1167/−0
- src/NLP/GenI/Simple/SimpleBuilder.lhs +1205/−0
- src/NLP/GenI/Simple/SimpleGui.lhs +200/−0
- src/NLP/GenI/Statistics.hs +171/−0
- src/NLP/GenI/SysGeni.lhs +93/−0
- src/NLP/GenI/Tags.lhs +332/−0
- src/NLP/GenI/Test.hs +36/−0
- src/NLP/GenI/unused/Predictors.lhs +315/−0
+ AUTHORS view
@@ -0,0 +1,2 @@+Carlos Areces+Eric Kow
− EnableGUI.hs
@@ -1,38 +0,0 @@--- this is just to get the GUI running on my mac, no big deal--- note: for Observe.lhs: -fglasgow-exts -cpp -package concurrent--module EnableGUI(enableGUI) where--import Data.Int-import Foreign-import qualified Main as Main2--{--import Posix-import Concurrent-import Control.Exception-catchCtrlC = do- main_thread <- myThreadId- installHandler sigINT (Catch (hupHandler main_thread)) Nothing- where- hupHandler :: ThreadId -> IO ()- hupHandler main_thread- = throwTo main_thread (ErrorCall "Control-C")--}--main = do (enableGUI >> Main2.main)--type ProcessSerialNumber = Int64--foreign import ccall "GetCurrentProcess" getCurrentProcess :: Ptr ProcessSerialNumber -> IO Int16-foreign import ccall "_CGSDefaultConnection" cgsDefaultConnection :: IO ()-foreign import ccall "CPSEnableForegroundOperation" cpsEnableForegroundOperation :: Ptr ProcessSerialNumber -> IO ()-foreign import ccall "CPSSignalAppReady" cpsSignalAppReady :: Ptr ProcessSerialNumber -> IO ()-foreign import ccall "CPSSetFrontProcess" cpsSetFrontProcess :: Ptr ProcessSerialNumber -> IO ()--enableGUI = alloca $ \psn -> do- getCurrentProcess psn- cgsDefaultConnection- cpsEnableForegroundOperation psn- cpsSignalAppReady psn- cpsSetFrontProcess psn
GenI.cabal view
@@ -1,38 +1,121 @@ Name: GenI-Version: 0.16.1+Version: 0.17.3 License: GPL License-file: LICENSE Author: Carlos Areces and Eric Kow-Cabal-version: >=1.2-build-type: Custom Category: Natural Language Processing-Homepage: http://trac.loria.fr/~geni Synopsis: A natural language generator (specifically, an FB-LTAG surface realiser) Description: A natural language generator (specifically, an FB-LTAG surface realiser)-Maintainer: eric.kow@loria.fr+Homepage: http://projects.haskell.org/GenI+Maintainer: geni-users@loria.fr+Build-Type: Custom+Cabal-Version: >=1.2.3+data-files: AUTHORS, INSTALL, README, NEWS, GenI.cabal,+ examples/artificial/lexicon,+ examples/artificial/macros,+ examples/artificial/suite,+ examples/artificial/suite-bad,+ examples/chatnoir/lexicon,+ examples/chatnoir/macros,+ examples/chatnoir/suite,+ examples/demo/lexicon,+ examples/demo/macros,+ examples/demo/README,+ examples/demo/suite,+ examples/ej/lexicon,+ examples/ej/macros,+ examples/ej/suite,+ examples/nosemantics/lexicon,+ examples/nosemantics/macros,+ examples/nosemantics/README.txt,+ examples/promettre/lexicon,+ examples/promettre/macros,+ examples/promettre/morphinfo,+ examples/promettre/suite,+ examples/xmg-example/grammar/Arguments.mg,+ examples/xmg-example/grammar/demo-corpus-latin1.txt,+ examples/xmg-example/grammar/Entete.mg,+ examples/xmg-example/grammar/Evaluations.mg,+ examples/xmg-example/grammar/Misc.mg,+ examples/xmg-example/grammar/parse-corpus.sh,+ examples/xmg-example/grammar/Sem.mg,+ examples/xmg-example/grammar/VerbMorph.mg,+ examples/xmg-example/lexicon/demo-lemma-latin1.lex,+ examples/xmg-example/lexicon/demo-morph-latin1.mph,+ examples/xmg-example/Makefile,+ examples/xmg-example/README,+ examples/xmg-example/suite,+ etc/macstuff/macosx-app, etc/macstuff/Info.plist, etc/macstuff/wxmac.icns -extra-source-files: EnableGUI.hs, NLP/GenI/SysGeni.lhs- NLP/GenI/CkyEarley/CkyGui.lhs- NLP/GenI/Simple/SimpleGui.lhs, NLP/GenI/Gui.lhs- NLP/GenI/GraphvizShow.lhs, NLP/GenI/GuiHelper.lhs- NLP/GenI/Console.hs, NLP/GenI/Graphviz.hs- NLP/GenI/BuilderGui.lhs, NLP/GenI/unused/Predictors.lhs- NLP/GenI/GraphvizShowPolarity.lhs+extra-source-files: src/EnableGUI.hs+ src/NLP/GenI/SysGeni.lhs+ src/NLP/GenI/Test.hs+ src/NLP/GenI/CkyEarley/CkyGui.lhs+ src/NLP/GenI/Simple/SimpleGui.lhs, src/NLP/GenI/Gui.lhs+ src/NLP/GenI/GraphvizShow.lhs, src/NLP/GenI/GuiHelper.lhs+ src/NLP/GenI/Console.hs, src/NLP/GenI/Graphviz.hs+ src/NLP/GenI/BuilderGui.lhs, src/NLP/GenI/unused/Predictors.lhs+ src/NLP/GenI/GraphvizShowPolarity.lhs -data-files: GenI.cabal macstuff/macosx-app +Flag gui+ description: Build with a graphical user interface+ Flag splitBase description: Choose the new smaller, split-up base package. +Library+ Build-depends: parsec >= 2 && < 3,+ QuickCheck >= 1.2 && < 2,+ HUnit > 1 && < 1.3,+ mtl > 1.0 && < 1.2,+ binary > 0.2 && < 0.5+ if flag(splitBase)+ Build-depends: base >= 3 && < 4, containers, process+ else+ Build-Depends: base < 3++ if !flag(gui)+ cpp-options: -DDISABLE_GUI++ Exposed-Modules:+ NLP.GenI.Btypes,+ NLP.GenI.BtypesBinary,+ NLP.GenI.General,+ NLP.GenI.GeniParsers,+ NLP.GenI.GeniShow,+ NLP.GenI.Tags,+ NLP.GenI.Morphology,+ NLP.GenI.Polarity, NLP.GenI.Automaton,+ NLP.GenI.Statistics,+ NLP.GenI.Builder,+ NLP.GenI.Simple.SimpleBuilder, NLP.GenI.CkyEarley.CkyBuilder,+ NLP.GenI.Geni, NLP.GenI.Configuration++ Hs-Source-Dirs: src+ Extensions: CPP, Rank2Types, OverlappingInstances, MultiParamTypeClasses, FlexibleContexts, TypeSynonymInstances, FlexibleInstances, DeriveDataTypeable, ExistentialQuantification, LiberalTypeSynonyms+ Ghc-options: -Wall -O2+ Executable geni Main-Is: MainGeni.lhs- Hs-Source-Dirs: .- Extensions: CPP, Rank2Types, OverlappingInstances, MultiParamTypeClasses+ Hs-Source-Dirs: src+ Extensions: CPP, Rank2Types, OverlappingInstances, MultiParamTypeClasses, FlexibleContexts, TypeSynonymInstances, FlexibleInstances, DeriveDataTypeable, ExistentialQuantification, LiberalTypeSynonyms+ Ghc-options: -Wall+ Build-Depends: filepath > 1.0 && < 1.2,+ parsec >= 2.1 && < 3,+ QuickCheck >= 1.2 && < 2,+ HUnit >= 1 && < 1.3,+ mtl >= 1.0 && < 1.2,+ binary >= 0.2 && < 0.5 if flag(splitBase)- Build-Depends: base >= 3, binary, wx, wxcore, mtl, parsec,- QuickCheck, HUnit, HaXml >=1.16, libGenI,+ Build-Depends: base >= 3, process > 1, directory > 1, containers >= 0.1 else- Build-Depends: base < 3, binary, wx, wxcore, mtl, parsec,- QuickCheck, HUnit, HaXml >=1.16, libGenI+ Build-Depends: base < 3++ if flag(gui)+ Build-Depends: wx >= 0.10.3 && < 0.12+ else+ cpp-options: -DDISABLE_GUI+
+ INSTALL view
@@ -0,0 +1,17 @@+Requirements+------------+ * ghc 6.8 or 6.10+ * libgmp (for ghc)+ * wxhaskell 0.10 (darcs version for now)+ * wxWidgets 2.8 (for wxhaskell 0.11)+ * graphviz (for GUI) ++Building GenI+--------------+1. obtain cabal-install++2. cabal configure+ cabal build+ cabal install++For more details, see http://trac.haskell.org/GenI
LICENSE view
@@ -2,7 +2,7 @@ Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc.- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. @@ -313,7 +313,7 @@ If the program is interactive, make it output a short notice like this when it starts in an interactive mode: - Gnomovision version 69, Copyright (C) year name of author+ Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details.
− MainGeni.lhs
@@ -1,87 +0,0 @@-% GenI surface realiser-% Copyright (C) 2005 Carlos Areces and Eric Kow-%-% This program is free software; you can redistribute it and/or-% modify it under the terms of the GNU General Public License-% as published by the Free Software Foundation; either version 2-% of the License, or (at your option) any later version.-%-% This program is distributed in the hope that it will be useful,-% but WITHOUT ANY WARRANTY; without even the implied warranty of-% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the-% GNU General Public License for more details.-%-% You should have received a copy of the GNU General Public License-% along with this program; if not, write to the Free Software-% Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.--\chapter{Main}--Welcome to the GenI source code. The main module is where everything-starts from. If you're trying to figure out how GenI works, the main-action is in Geni and Tags -(chapters \ref{cha:Geni} and \ref{cha:Tags}). --\begin{code}-module Main (main) where-\end{code}--\ignore{-\begin{code}-import Data.IORef(newIORef)-import System.Environment(getArgs)--import NLP.GenI.Geni(emptyProgState)-import NLP.GenI.Console(consoleGeni)-import NLP.GenI.Configuration (treatStandardArgs, processInstructions,- hasFlagP, BatchDirFlg(..), DisableGuiFlg(..), FromStdinFlg(..),- RegressionTestModeFlg(..),- )--#ifndef DISABLE_GUI-import NLP.GenI.Gui(guiGeni)-#else-guiGeni = consoleGeni-#endif-\end{code}-}--In figure \ref{fig:code-outline-main} we show what happens from main: First, we-hand control off to either the console or the graphical user interface. These-functions then do all the business stuff like loading files and figuring out-what to generate. From there, they invoke the the generation step-\fnref{runGeni} which does surface realisation from A-Z. Alternately, the-graphical interface could invoke a graphical debugger which also does surface-realisation from A-Z but allows you to intervene, inspect and stop at each-step.--\begin{figure}-\begin{center}-\includegraphics[scale=0.25]{images/code-outline-main}-\label{fig:code-outline-main}-\caption{How the GenI entry point is used}-\end{center}-\end{figure}--\begin{code}-main :: IO ()-main = do - args <- getArgs- confArgs <- treatStandardArgs args >>= processInstructions- let pst = emptyProgState confArgs- pstRef <- newIORef pst- let batch = hasFlagP BatchDirFlg confArgs- console = hasFlagP DisableGuiFlg confArgs- fromstdin = hasFlagP FromStdinFlg confArgs- regression = hasFlagP RegressionTestModeFlg confArgs- if (fromstdin || console || batch || regression)- then consoleGeni pstRef- else guiGeni pstRef-\end{code}--% TODO-% Define what is and what is not exported from the modules. -% In particular in BTypes take care to export the inspection function -% but not the types.-% Re-write functions in Main as needed.-% Change input in Lexicon and Grammar to allow more than one anchor.
+ NEWS view
@@ -0,0 +1,13 @@+GenI 0.17.3, 3 Apr 2009+-----------------------+* Simplified build method+ * one single cabal package instead of two+ * cabal configure -f-gui to disable GUI+ * makefile stripped out++* Lexical selection on empty semantics now allowed+ * This is so that the zero-literal semantics mechanism can work again++* Better help text++* Baked-in unit testing (geni --unit-test)
− NLP/GenI/BuilderGui.lhs
@@ -1,34 +0,0 @@-% GenI surface realiser-% Copyright (C) 2005 Carlos Areces and Eric Kow-%-% This program is free software; you can redistribute it and/or-% modify it under the terms of the GNU General Public License-% as published by the Free Software Foundation; either version 2-% of the License, or (at your option) any later version.-%-% This program is distributed in the hope that it will be useful,-% but WITHOUT ANY WARRANTY; without even the implied warranty of-% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the-% GNU General Public License for more details.-%-% You should have received a copy of the GNU General Public License-% along with this program; if not, write to the Free Software-% Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.--\begin{code}-module NLP.GenI.BuilderGui-where--import Graphics.UI.WXCore--import qualified NLP.GenI.Builder as B-import NLP.GenI.Geni (ProgStateRef, GeniResult)-import NLP.GenI.Configuration (Params)-import NLP.GenI.Statistics (Statistics)-\end{code}--\begin{code}-data BuilderGui = BuilderGui- { resultsPnl :: forall a . ProgStateRef -> (Window a) -> IO ([GeniResult],Statistics,Layout)- , debuggerPnl :: forall a . (Window a) -> Params -> B.Input -> String -> IO Layout }-\end{code}
− NLP/GenI/CkyEarley/CkyGui.lhs
@@ -1,457 +0,0 @@-% GenI surface realiser-% Copyright (C) 2005 Carlos Areces and Eric Kow-%-% This program is free software; you can redistribute it and/or-% modify it under the terms of the GNU General Public License-% as published by the Free Software Foundation; either version 2-% of the License, or (at your option) any later version.-%-% This program is distributed in the hope that it will be useful,-% but WITHOUT ANY WARRANTY; without even the implied warranty of-% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the-% GNU General Public License for more details.-%-% You should have received a copy of the GNU General Public License-% along with this program; if not, write to the Free Software-% Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.--\chapter{CKY Gui}--\begin{code}-{-# LANGUAGE FlexibleInstances, TypeSynonymInstances #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}-module NLP.GenI.CkyEarley.CkyGui where-\end{code}--\ignore{-\begin{code}-import Graphics.UI.WX hiding (when)-import Graphics.UI.WXCore hiding (when)--import qualified Control.Monad as Monad -import Control.Monad (liftM)--import Data.IORef-import Data.List (intersperse, findIndex, sort)-import qualified Data.Map as Map -import Data.Maybe (listToMaybe, catMaybes)-import Data.Tree --import NLP.GenI.Statistics (Statistics)--import NLP.GenI.Automaton- ( NFA(states, transitions, startSt, finalStList)- , addTrans )-import qualified NLP.GenI.Builder as B-import qualified NLP.GenI.BuilderGui as BG-import NLP.GenI.Btypes ( GNode, gnname )--import NLP.GenI.CkyEarley.CkyBuilder- ( ckyBuilder, earleyBuilder, CkyStatus, CkyItem(..), ChartId- , ciRoot, ciAdjDone- , bitVectorToSem, findId- , extractDerivations- , theResults, theAgenda, theChart, theTrash- , emptySentenceAut, mJoinAutomata, mAutomatonPaths- , unpackItemToAuts,- )-import NLP.GenI.Configuration ( Params(..) )--import NLP.GenI.Geni- ( ProgStateRef, runGeni, GeniResult )-import NLP.GenI.General ( boundsCheck, geniBug )-import NLP.GenI.GuiHelper- ( messageGui, toSentence- , debuggerPanel, DebuggerItemBar- , addGvHandler, modifyGvParams- , GraphvizGuiSt(gvitems, gvsel, gvparams), GvIO, setGvSel- , graphvizGui, newGvRef, setGvDrawables,- )--import NLP.GenI.Tags ( idname, tsemantics, ttree, TagElem )--import NLP.GenI.Graphviz- ( GraphvizShow(..), gvNode, gvEdge, gvSubgraph, gvUnlines, gvShowTree- , gvNewline- , GraphvizShowNode(..) )-\end{code}-}--% ---------------------------------------------------------------------\section{Interface}-% ----------------------------------------------------------------------\begin{code}-ckyGui, earleyGui :: BG.BuilderGui-ckyGui = ckyOrEarleyGui False-earleyGui = ckyOrEarleyGui True--ckyOrEarleyGui :: Bool -> BG.BuilderGui-ckyOrEarleyGui isEarley = BG.BuilderGui {- BG.resultsPnl = resultsPnl builder- , BG.debuggerPnl = ckyDebuggerTab builder }- where builder = if isEarley then earleyBuilder else ckyBuilder--resultsPnl :: B.Builder CkyStatus CkyItem Params -> ProgStateRef -> Window a -> IO ([GeniResult], Statistics, Layout)-resultsPnl builder pstRef f =- do (sentences, stats, st) <- runGeni pstRef builder- (lay, _, _) <- realisationsGui pstRef f (theResults st)- return (sentences, stats, lay)-\end{code}--% ---------------------------------------------------------------------\section{Results}-\label{sec:cky_results_gui}-% ----------------------------------------------------------------------\begin{code}--- | Browser for the results (if there are any)-realisationsGui :: ProgStateRef -> (Window a) -> [CkyItem]- -> GvIO CkyDebugParams (Maybe CkyItem)-realisationsGui _ f [] =- do m <- messageGui f "No results found"- gvRef <- newGvRef initCkyDebugParams [] ""- return (m, gvRef, return ())-realisationsGui _ f resultsRaw =- do let tip = "result"- results = map Just resultsRaw- labels = map (toSentence.ciSourceTree) resultsRaw- gvRef <- newGvRef initCkyDebugParams labels tip- setGvDrawables gvRef results- graphvizGui f "cky-results" gvRef-\end{code}--\begin{code}-data CkyDebugParams = - CkyDebugParams { debugShowFeats :: Bool - , debugShowFullDerv :: Bool- , debugShowSourceTree :: Bool- , debugWhichDerivation :: Int- , debugNodeChoice :: [ChartId] }--initCkyDebugParams :: CkyDebugParams-initCkyDebugParams = - CkyDebugParams { debugShowFeats = False- , debugShowFullDerv = False- , debugShowSourceTree = False- , debugWhichDerivation = 0- , debugNodeChoice = [] }---- would be nice if Haskell sugared this kind of stuff for us-setDebugShowFeats, setDebugShowFullDerv, setDebugShowSourceTree :: Bool -> CkyDebugParams -> CkyDebugParams-setDebugShowFeats b x = x { debugShowFeats = b }-setDebugShowFullDerv b x = x { debugShowFullDerv = b }-setDebugShowSourceTree b x = x { debugShowSourceTree = b }--setDebugWhichDerivation :: Int -> CkyDebugParams -> CkyDebugParams-setDebugWhichDerivation w x = x { debugWhichDerivation = w }--clearDebugNodeChoice :: CkyDebugParams -> CkyDebugParams-clearDebugNodeChoice x = x { debugNodeChoice = [] }--pushDebugNodeChoice :: ChartId -> CkyDebugParams -> CkyDebugParams-pushDebugNodeChoice w x = x { debugNodeChoice = w:(debugNodeChoice x) }--popDebugNodeChoice :: CkyDebugParams -> Maybe (ChartId, CkyDebugParams)-popDebugNodeChoice x =- case debugNodeChoice x of- [] -> Nothing- (h:t) -> Just (h, x { debugNodeChoice = t })--ckyDebuggerTab :: B.Builder CkyStatus CkyItem Params- -> (Window a) -> Params -> B.Input -> String -> IO Layout-ckyDebuggerTab builder = debuggerPanel builder initCkyDebugParams stateToGv ckyItemBar- where - stateToGv :: CkyStatus -> ([(Maybe (CkyStatus,CkyItem))], [String])- stateToGv st = - let agenda = section "AGENDA" $ theAgenda st- trash = section "TRASH" $ theTrash st- chart = section "CHART" $ theChart st- results = section "RESULTS" $ theResults st- --- section n i = hd : (map tlFn i)- where hd = (Nothing, "___" ++ n ++ "___")- tlFn x = (Just (st,x), labelFn x)- showPaths = const ""- {- if (polarised $ genconfig st)- then (\t -> " (" ++ showPolPaths t ++ ")")- else const "" -}- gorn i = case gornAddressStr (ttree $ ciSourceTree i) (ciNode i) of- Nothing -> geniBug "A chart item claims to have a node which is not in its tree"- Just x -> x- isComplete i = ciRoot i && ciAdjDone i- -- try displaying as an automaton, or if all else fails, the tree sentence- fancyToSentence ci =- let mergedAut = uncurry mJoinAutomataUsingHole $ unpackItemToAuts st ci- boringSentence = toSentence $ ciSourceTree ci- in case mAutomatonPaths mergedAut of- [] -> boringSentence- (h:_) -> unwords $ map fst $ h- labelFn i = unwords [ completeStr ++ idStr ++ gornStr- , fancyToSentence i- , "/" ++ (idname $ ciSourceTree i)- , showPaths i- ]- where idStr = show $ ciId i- completeStr = if isComplete i then ">" else ""- gornStr = if isComplete i then "" else " g" ++ (gorn i)- in unzip $ agenda ++ chart ++ results ++ trash--ckyItemBar :: DebuggerItemBar CkyDebugParams (CkyStatus, CkyItem)-ckyItemBar f gvRef updaterFn =- do ib <- panel f []- -- select derivation- derTxt <- staticText ib []- derChoice <- choice ib [ tooltip := "Select a derivation" ]- jumpBtn <- button ib [ text := "Go to node" ]- unjumpBtn <- button ib [ text := "Pop back" ]- jumpChoice <- choice ib [ tooltip := "Jump to item." ]- let onDerChoice =- do sel <- get derChoice selection- modifyGvParams gvRef (setDebugWhichDerivation sel)- gvSt <- readIORef gvRef- -- update the list of jump choices- case Map.lookup (gvsel gvSt) (gvitems gvSt) of- Just (Just (s,c)) -> do- let t = selectedDerivation (gvparams gvSt) s c- nodes = map show $ sort $ derivationNodes t- set jumpChoice [ items := nodes, selection := 0 ]- updaterFn- _ -> return ()- set derChoice [ on select := onDerChoice ]- -- show features- detailsChk <- checkBox ib [ text := "features"- , enabled := False, checked := False ]- fullDervChk <- checkBox ib [ text := "full derivation"- , checked := False ]- srcTreeChk <- checkBox ib [ text := "src tree"- , checked := False ]- let setChkBoxUpdater box setter =- set box [ on command := do isChecked <- get box checked- modifyGvParams gvRef $ setter isChecked- updaterFn ]- setChkBoxUpdater detailsChk setDebugShowFeats- setChkBoxUpdater fullDervChk setDebugShowFullDerv- setChkBoxUpdater srcTreeChk setDebugShowSourceTree- -- make detailsChk conditioned on srcTreeChk- set srcTreeChk [ on command :~ \x -> x >> do- isChecked <- get srcTreeChk checked- set detailsChk [ enabled := isChecked ]- ]- -- add a handler for when an item is selected: - -- update the list of derivations to choose from- let updateDerTxt t = set derTxt [ text := "Deriviations (" ++ t ++ ")" ]- handler gvSt = - do case Map.lookup (gvsel gvSt) (gvitems gvSt) of- Just (Just (s,c)) ->- do let derivations = extractDerivations s c - dervLabels = zipWith (\n _ -> show n) ([1..]::[Int]) derivations- set derChoice [ enabled := True, items := dervLabels, selection := 0 ]- onDerChoice- updateDerTxt $ show $ length derivations- _ ->- do set derChoice [ enabled := False, items := [] ]- updateDerTxt "n/a"- addGvHandler gvRef handler- -- call the handler to react to the first selection- handler `liftM` readIORef gvRef- -- pushing and popping between nodes- let jumpToNode jmpTo =- do gvSt <- readIORef gvRef- let chartItems = Map.elems $ gvitems gvSt- case findIndex isJmpTo chartItems of- Nothing -> geniBug $ "Was asked to see node " ++ (show jmpTo) ++ ", which is not in the list"- Just x ->- do setGvSel gvRef x- modifyGvParams gvRef (setDebugWhichDerivation 0)- readIORef gvRef >>= handler- updaterFn- where isJmpTo Nothing = False- isJmpTo (Just (_,x)) = ciId x == jmpTo- set jumpBtn [ on command := do- gvSt <- readIORef gvRef- case Map.lookup (gvsel gvSt) (gvitems gvSt) of- Just (Just x) -> modifyGvParams gvRef (pushDebugNodeChoice $ (ciId.snd) x)- _ -> return ()- jmpSel <- get jumpChoice selection- jmpItms <- get jumpChoice items- let jmpTo = (read $ jmpItms !! jmpSel)- jumpToNode jmpTo ]-- set unjumpBtn [ on command := do- gvSt <- readIORef gvRef- case popDebugNodeChoice (gvparams gvSt) of- Nothing -> return ()- Just (x,gvParam) -> do modifyGvParams gvRef (const gvParam)- jumpToNode x ]- --- return $ hfloatCentre $ container ib $ column 0 $- [ row 5- [ label "Show...", widget fullDervChk, widget srcTreeChk, widget detailsChk ]- , row 5- [ widget derTxt, widget derChoice- , hspace 5, label "Node", widget jumpChoice, widget jumpBtn, widget unjumpBtn ] ]-\end{code}--\section{Helper code}--\begin{code}--gornAddressStr :: Tree GNode -> GNode -> Maybe String-gornAddressStr t target =- (concat . (intersperse ".") . (map show)) `liftM` gornAddress t target--gornAddress :: Tree GNode -> GNode -> Maybe [Int]-gornAddress tr target = reverse `liftM` helper [] tr- where- helper current (Node x _) | (gnname x == gnname target) = Just current- helper current (Node _ l) = listToMaybe $ catMaybes $- zipWith (\c t -> helper (c:current) t) [1..] l---selectedDerivation :: CkyDebugParams -> CkyStatus -> CkyItem -> Tree (ChartId, String)-selectedDerivation f s c =- let derivations = extractDerivations s c- whichDer = debugWhichDerivation f- in if boundsCheck whichDer derivations- then derivations !! whichDer- else geniBug $ "Bounds check failed on derivations selector:\n"- ++ "Selected derivation: " ++ (show whichDer) ++ "\n"- ++ "Bounds: 0 to " ++ (show $ length derivations - 1)--derivationNodes :: Tree (ChartId, String) -> [ChartId]-derivationNodes = (map fst).flatten---- | Remove na and subst or adj completion links-thinDerivationTree :: Tree (ChartId, String) -> Tree (ChartId, String)-thinDerivationTree =- let thinlst = ["no-adj", "subst", "adj" ]- helper n@(Node _ []) = n- -- this is made complicated for fancy highlighting to work- helper (Node (i,op) [k]) | op `elem` thinlst = (Node (i,op2) k2)- where (Node (_,op2) k2) = helper k- helper (Node x kids) = (Node x $ map helper kids)- in helper--instance GraphvizShow CkyDebugParams (CkyStatus, CkyItem) where- graphvizLabel f (_,c) = graphvizLabel f c- graphvizParams f (_,c) = graphvizParams f c- graphvizShowAsSubgraph f p (s,c) = - let color_ x = ("color", x)- label_ x = ("label", x)- style_ x = ("style", x)- arrowtail_ x = ("arrowtail", x)- --- substColor = color_ "blue"- adjColor = color_ "red"- --- edgeParams (_ ,"no-adj") = [ label_ "na" ]- edgeParams (_, "kids" ) = []- edgeParams (_, "init" ) = [ label_ "i" ]- edgeParams (_, "subst" ) = [ substColor ]- edgeParams (_, "adj" ) = [ adjColor ]- edgeParams (_, "subst-finish") = [ substColor, style_ "bold" , arrowtail_ "normal" ]- edgeParams (_, "adj-finish") = [ adjColor , style_ "dashed, bold", arrowtail_ "normal" ]- edgeParams (_, k) = [ ("label", "UNKNOWN: " ++ k) ]- --- whichDer = debugWhichDerivation f- showFullDer = debugShowFullDerv f- showSrcTree = debugShowSourceTree f- showTree i t = gvSubgraph $ gvShowTree edgeParams (s,showFullDer, [ciId c]) prfx t- where prfx = p ++ "t" ++ (show i)- gvDerv = showTree whichDer $ if showFullDer then t else thinDerivationTree t- where t = selectedDerivation f s c- --- joinedAut = uncurry mJoinAutomataUsingHole $ unpackItemToAuts s c- gvAut = graphvizShowAsSubgraph () (p ++ "aut") joinedAut- --- showFeats = debugShowFeats f- treeParams = unlines $ graphvizParams showFeats $ ciSourceTree c- -- FIXME: will have to make this configurable, maybe, show aut, show tree? radio button?- in "\n// ------------------- derivations --------------------------\n"- ++ treeParams ++ "node [ shape = plaintext, peripheries = 0 ]\n"- ++ gvDerv- ++ "\n// ------------------- automata (joined) ------------------------\n"- ++ gvSubgraph gvAut- ++ if showSrcTree- then ("\n// ------------------- elementary tree --------------------------\n"- ++ treeParams ++ graphvizShowAsSubgraph f p c)- else ""--instance GraphvizShowNode (CkyStatus,Bool,[ChartId]) (ChartId, String) where- graphvizShowNode (st,showFullDerv,highlight) prefix (theId,_) =- let idStr = show theId- treename i = " (" ++ ((idname.ciSourceTree) i) ++ ")"- txt = case findId st theId of- Nothing -> ("???" ++ idStr)- Just i -> idStr ++ " " ++ (show.ciNode) i- ++ (if showFullDerv then treename i else "")- custom = if theId `elem` highlight then [ ("fontcolor","red") ] else []- in gvNode prefix txt custom--instance GraphvizShow CkyDebugParams CkyItem where- graphvizLabel f ci =- graphvizLabel (debugShowFeats f, nullHlter) (toTagElem ci) ++- gvNewline ++ (gvUnlines $ ciDiagnostic ci)-- graphvizShowAsSubgraph f prefix ci = - let showFeats = debugShowFeats f- hlter n = (n, if (gnname n) == (gnname $ ciNode ci)- then Just "red" else Nothing)- in graphvizShowAsSubgraph (showFeats,hlter) (prefix ++ "tree") $ toTagElem ci--nullHlter :: GNode -> (GNode, Maybe String)-nullHlter a = (a,Nothing)--toTagElem :: CkyItem -> TagElem-toTagElem ci =- te { ttree = ttree te- , tsemantics = bitVectorToSem (ciSemBitMap ci) (ciSemantics ci) }- where te = ciSourceTree ci---- FIXME: this is largely copy-and-pasted from Polarity.lhs --- it should be refactored later-instance GraphvizShow () B.SentenceAut where- graphvizShowAsSubgraph _ prefix aut =- let st = (concat.states) aut- ids = map (\x -> prefix ++ show x) ([0..]::[Int])- -- map which permits us to assign an id to a state- stmap = Map.fromList $ zip st ids- lookupFinal x = Map.findWithDefault "error_final" x stmap- in -- final states should be a double-edged ellispse- "node [ shape = ellipse, peripheries = 2 ]; "- ++ (unlines $ map lookupFinal $ finalStList aut)- -- any other state should be an ellipse- ++ "node [ shape = ellipse, peripheries = 1 ]\n"- -- draw the states and transitions - ++ (concat $ zipWith gvShowState ids st) - ++ (concat $ zipWith (gvShowTrans aut stmap) ids st )--type SentenceAutState = Int --gvShowState :: String -> SentenceAutState -> String-gvShowState stId st = gvNode stId (show st) []--gvShowTrans :: B.SentenceAut -> Map.Map SentenceAutState String- -> String -> SentenceAutState -> String -gvShowTrans aut stmap idFrom st = - let -- outgoing transition labels from st- trans = Map.findWithDefault Map.empty st $ transitions aut- -- returns the graphviz dot command to draw a labeled transition- drawTrans (stTo,x) = case Map.lookup stTo stmap of- Nothing -> drawTrans' ("id_error_" ++ (show stTo)) x - Just idTo -> drawTrans' idTo x- drawTrans' idTo x = gvEdge idFrom idTo (drawLabel x) []- drawLabel labels = gvUnlines $ map fst $ catMaybes labels - in unlines $ map drawTrans $ Map.toList trans-\end{code}--\begin{code}--- | join two automata, inserting a ".." transition between them-mJoinAutomataUsingHole :: Maybe B.SentenceAut -> Maybe B.SentenceAut -> Maybe B.SentenceAut-mJoinAutomataUsingHole aut1 Nothing = aut1-mJoinAutomataUsingHole aut1 aut2 =- mJoinAutomata aut1 $ mJoinAutomata (Just holeAut) aut2- where holeAut = addTrans emptyA 0 (Just ("..",[])) 1- emptyA = emptySentenceAut { startSt = 0, finalStList = [1], states = [[0,1]] }-\end{code}
− NLP/GenI/Console.hs
@@ -1,191 +0,0 @@--- GenI surface realiser--- Copyright (C) 2005 Carlos Areces and Eric Kow------ This program is free software; you can redistribute it and/or--- modify it under the terms of the GNU General Public License--- as published by the Free Software Foundation; either version 2--- of the License, or (at your option) any later version.------ This program is distributed in the hope that it will be useful,--- but WITHOUT ANY WARRANTY; without even the implied warranty of--- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the--- GNU General Public License for more details.------ You should have received a copy of the GNU General Public License--- along with this program; if not, write to the Free Software--- Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.---- | The console user interface including batch processing on entire--- test suites.--module NLP.GenI.Console(consoleGeni, runTestCaseOnly) where--import Control.Monad-import Data.IORef(readIORef, modifyIORef)-import Data.List(find, sort)-import Data.Maybe ( isJust, fromMaybe )-import System.Directory(createDirectoryIfMissing)-import System.Exit ( exitFailure )-import Test.HUnit.Text (runTestTT)-import qualified Test.HUnit.Base as H-import Test.HUnit.Base ((@?))--import NLP.GenI.Btypes- ( SemInput, showSem- , TestCase(tcSem, tcName, tcExpected)- )-import qualified NLP.GenI.Btypes as G-import NLP.GenI.General- ( ePutStrLn, withTimeout, exitTimeout, (///)- , fst3,- )-import NLP.GenI.Geni-import NLP.GenI.Configuration- ( Params- , BatchDirFlg(..), EarlyDeathFlg(..), FromStdinFlg(..), OutputFileFlg(..)- , MetricsFlg(..), RegressionTestModeFlg(..), StatsFileFlg(..)- , TestCaseFlg(..), TimeoutFlg(..), VerboseModeFlg(..)- , hasFlagP, getFlagP- , builderType , BuilderType(..)- )-import qualified NLP.GenI.Builder as B-import NLP.GenI.CkyEarley.CkyBuilder-import NLP.GenI.Simple.SimpleBuilder-import NLP.GenI.Statistics ( showFinalStats, Statistics )--consoleGeni :: ProgStateRef -> IO()-consoleGeni pstRef = do- pst <- readIORef pstRef- loadEverything pstRef- case getFlagP TimeoutFlg (pa pst) of- Nothing -> runSuite pstRef- Just t -> withTimeout t (timeoutErr t) $ runSuite pstRef- where- timeoutErr t = do ePutStrLn $ "GenI timed out after " ++ (show t) ++ "s"- exitTimeout---- | Runs a test suite.--- We assume that the grammar and target semantics are already--- loaded into the monadic state.--- If batch processing is enabled, save the results to the batch output--- directory with one subdirectory per case.-runSuite :: ProgStateRef -> IO ()-runSuite pstRef =- do pst <- readIORef pstRef- let suite = tsuite pst- config = pa pst- verbose = hasFlagP VerboseModeFlg config- earlyDeath = hasFlagP EarlyDeathFlg config- if hasFlagP RegressionTestModeFlg config- then runRegressionSuite pstRef >> return ()- else case getFlagP BatchDirFlg config of- Nothing -> runTestCaseOnly pstRef >> return ()- Just bdir -> runBatch earlyDeath verbose bdir suite- where- runBatch earlyDeath verbose bdir suite =- if any null $ map tcName suite- then ePutStrLn "Can't do batch processing. The test suite has cases with no name."- else do ePutStrLn "Batch processing mode"- mapM_ (runCase earlyDeath verbose bdir) suite- runCase earlyDeath verbose bdir (G.TestCase { tcName = n, tcSem = s }) =- do when verbose $- ePutStrLn "======================================================"- (res , _) <- runOnSemInput pstRef (PartOfSuite n bdir) s- ePutStrLn $ " " ++ n ++ " - " ++ (show $ length res) ++ " results"- when (null res && earlyDeath) $ do- ePutStrLn $ "Exiting early because test case " ++ n ++ " failed."- exitFailure---- | Run a test suite, but in HUnit regression testing mode,--- treating each GenI test case as an HUnit test. Obviously--- we need a test suite, grammar, etc as input-runRegressionSuite :: ProgStateRef -> IO (H.Counts)-runRegressionSuite pstRef =- do pst <- readIORef pstRef- tests <- (mapM toTest) . tsuite $ pst- runTestTT . (H.TestList) . concat $ tests- where- toTest :: G.TestCase -> IO [H.Test] -- ^ GenI test case to HUnit Tests- toTest tc = -- run the case, and return a test case for each expected result- do (res , _) <- runOnSemInput pstRef InRegressionTest (tcSem tc)- let sentences = fst (unzip res)- name = tcName tc- semStr = showSem . fst3 . tcSem $ tc- mainMsg = "for " ++ semStr ++ ", got no results"- mainCase = H.TestLabel name- $ H.TestCase $ (not.null $ sentences) @? mainMsg- subMsg e = "for " ++ semStr ++ ", failed to get (" ++ e ++ ")"- subCase e = H.TestLabel name- $ H.TestCase $ (e `elem` sentences) @? subMsg e- return $ (mainCase :) $ map subCase (tcExpected tc)---- | Run the specified test case, or failing that, the first test--- case in the suite-runTestCaseOnly :: ProgStateRef -> IO ([GeniResult], Statistics)-runTestCaseOnly pstRef =- do pst <- readIORef pstRef- let config = pa pst- pstOutfile = fromMaybe "" $ getFlagP OutputFileFlg config- sFile = fromMaybe "" $ getFlagP StatsFileFlg config- semInput <- case getFlagP TestCaseFlg config of- Nothing -> if hasFlagP FromStdinFlg config- then do getContents >>= loadTargetSemStr pstRef- ts `fmap` readIORef pstRef- else getFirstCase pst- Just c -> findCase pst c- runOnSemInput pstRef (Standalone pstOutfile sFile) semInput- where- getFirstCase pst =- case tsuite pst of- [] -> fail "Test suite is empty."- (c:_) -> return $ tcSem c- findCase pst theCase =- case find (\x -> tcName x == theCase) (tsuite pst) of- Nothing -> fail ("No such test case: " ++ theCase)- Just s -> return $ tcSem s--data RunAs = Standalone FilePath FilePath- | PartOfSuite String FilePath- | InRegressionTest---- | Runs a case in the test suite. If the user does not specify any test--- cases, we run the first one. If the user specifies a non-existing--- test case we raise an error.-runOnSemInput :: ProgStateRef- -> RunAs- -> SemInput- -> IO ([GeniResult], Statistics)-runOnSemInput pstRef args semInput =- do modifyIORef pstRef (\x -> x{ts = semInput})- pst <- readIORef pstRef- let config = pa pst- (results', stats) <- case builderType config of- NullBuilder -> helper B.nullBuilder- SimpleBuilder -> helper simpleBuilder_2p- SimpleOnePhaseBuilder -> helper simpleBuilder_1p- CkyBuilder -> helper ckyBuilder- EarleyBuilder -> helper earleyBuilder- let results = sort results'- -- create directory if need be- case args of- PartOfSuite n f -> createDirectoryIfMissing False (f///n)- _ -> return ()- let oWrite = case args of- Standalone "" _ -> putStrLn- Standalone f _ -> writeFile f- PartOfSuite n f -> writeFile $ f /// n /// "responses"- InRegressionTest -> const $ return ()- soWrite = case args of- Standalone _ "" -> putStrLn- Standalone _ f -> writeFile f- PartOfSuite n f -> writeFile $ f /// n /// "stats"- InRegressionTest -> const $ return ()- oWrite . unlines . map fst $ results- -- print out statistical data (if available)- when (isJust $ getFlagP MetricsFlg config) $- do soWrite $ "begin stats\n" ++ showFinalStats stats ++ "end"- return (results, stats)- where- helper builder =- do (results, stats, _) <- runGeni pstRef builder- return (results, stats)
− NLP/GenI/Graphviz.hs
@@ -1,212 +0,0 @@-{-# OPTIONS -fglasgow-exts #-}--{-- GenI surface realiser- Copyright (C) 2005 Carlos Areces and Eric Kow- - This program is free software; you can redistribute it and/or- modify it under the terms of the GNU General Public License- as published by the Free Software Foundation; either version 2- of the License, or (at your option) any later version.- - This program is distributed in the hope that it will be useful,- but WITHOUT ANY WARRANTY; without even the implied warranty of- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the- GNU General Public License for more details.- - You should have received a copy of the GNU General Public License- along with this program; if not, write to the Free Software- Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.--}--{- | Graphviz is an open source tool which converts an abstract- representation of a graph (node foo is connected to node bar, etc.)- into a nicely laid out graphic. This module contains methods - to invoke graphviz and to convert graphs and trees to its input format.-- You can download this (open source) tool at- <http://www.research.att.com/sw/tools/graphviz>--}--module NLP.GenI.Graphviz-where--import Control.Monad(when)-import Data.List(intersperse)-import Data.Tree-import System.IO(hPutStrLn, hClose)-import System.Exit(ExitCode)--import NLP.GenI.SysGeni(waitForProcess, runInteractiveProcess)--{- |- Data structures which can be visualised with GraphViz should- implement this class. Note the first argument to graphvizShowGraph is- so that you can parameterise your show function (i.e. pass in- flags to change the way you show particular object). Note- that by default, all graphs are treated as directed graphs. You- can hide this by turning off edge arrows.--}-class GraphvizShow flag b where- graphvizShowGraph :: flag -> b -> String- graphvizShowAsSubgraph :: flag -- ^ flag- -> String -- ^ prefix- -> b -- ^ item- -> String -- ^ gv output - graphvizLabel :: flag -- ^ flag- -> b -- ^ item- -> String -- ^ gv output- graphvizParams :: flag -> b -> [String] -- graphvizShowGraph f b = - let l = graphvizLabel f b- in "digraph {\n" - ++ (unlines $ graphvizParams f b)- ++ graphvizShowAsSubgraph f "_" b ++ "\n"- ++ (if null l then "" else " label = \"" ++ l ++ "\";\n")- ++ "}"- graphvizLabel _ _ = ""- graphvizParams _ _ = []--class GraphvizShowNode flag b where- graphvizShowNode :: flag -- ^ flag - -> String -- ^ prefix - -> b -- ^ item - -> String -- ^ gv output---- | Things which are meant to be displayed within some other graph--- as (part) of a node label-class GraphvizShowString flag b where- graphvizShow :: flag -- ^ flag- -> b -- ^ item- -> String -- ^ gv output---- | Note: the 'dotFile' argument allows you to save the intermediary--- dot output to a file. You can pass in the empty string if you don't-toGraphviz :: (GraphvizShow f a) => f - -> a - -> String -- ^ the 'dotFile'- -> String -> IO ExitCode -toGraphviz p x dotFile outputFile = do- graphviz (graphvizShowGraph p x) dotFile outputFile---- ------------------------------------------------------------------------ useful utility functions--- -----------------------------------------------------------------------gvNewline :: String-gvNewline = "\\n"--gvUnlines :: [String] -> String-gvUnlines = concat . (intersperse gvNewline)--gvSubgraph :: String -> String-gvSubgraph g = "subgraph {\n" ++ g ++ "}\n"---- | The Graphviz string for a node. Note that we make absolutely no--- effort to escape any characters for you; so if you need to protect--- anything from graphviz, you're on your own-gvNode :: String -- ^ the node name- -> String -- ^ the label (may be empty)- -> [(String,String)] -- ^ any other parameters- -> String-gvNode name label params = - " " ++ name ++ " " ++ (gvLabelAndParams label params) ++ "\n"---- | The Graphviz string for a connection between two nodes. --- Same disclaimer as 'gvNode' applies.-gvEdge :: String -- ^ the 'from' node- -> String -- ^ the 'to' node- -> String -- ^ the label (may be empty)- -> [(String,String)] -- ^ any other parameters - -> String-gvEdge from to label params = - " " ++ from ++ " -> " ++ to ++ (gvLabelAndParams label params) ++ "\n"--gvLabelAndParams :: String -> [(String,String)] -> String-gvLabelAndParams l p = - gvParams $ if null l then p else ("label", l) : p--gvParams :: [(String,String)] -> String-gvParams [] = ""-gvParams p = "[ " ++ (concat $ intersperse ", " $ map showPair p) ++ " ]"- where showPair (a,v) = a ++ "=\"" ++ v ++ "\""---- ------------------------------------------------------------------------ some instances --- -----------------------------------------------------------------------instance (GraphvizShow f b) => GraphvizShow f (Maybe b) where- graphvizShowAsSubgraph _ _ Nothing = ""- graphvizShowAsSubgraph f p (Just b) = graphvizShowAsSubgraph f p b -- graphvizLabel _ Nothing = ""- graphvizLabel f (Just b) = graphvizLabel f b-- graphvizParams _ Nothing = [] - graphvizParams f (Just b) = graphvizParams f b---- | Displays a tree in graphviz format. -{- Note that we could make this an- instance of GraphvizShow, but I'm not too sure about the wisdom of- such a move. -- Maybe if we had some really super-sophisticated types in Haskell, where- we can define this as the default instance which could be overrided by- something more specific, that would be cool.-- The prefix argument is interpreted as the name of the top node. Node- names below are basically Gorn addresses (e.g. n0x2x3 means 3rd child of- the 2nd child of the root) to keep them distinct. Note : We use the- letter `x' as seperator because graphviz will choke on `.' or `-', even- underscore. -}-gvShowTree :: (GraphvizShowNode f n) => - (n->[(String,String)]) -- ^ function to convert a node to a list of graphviz parameters for the edge - -> f -- ^ GraphvizShow flag- -> String -- ^ node prefix- -> (Tree n) -- ^ the tree- -> String-gvShowTree edgeFn f prefix t = - "edge [ arrowhead = none ]\n" ++ gvShowTreeHelper edgeFn f prefix t --gvShowTreeHelper :: forall n . forall f . (GraphvizShowNode f n) => (n->[(String,String)]) -> f -> String -> (Tree n) -> String-gvShowTreeHelper edgeFn f prefix (Node node l) = - let showNode = graphvizShowNode f prefix - showKid :: Integer -> Tree n -> String- showKid index kid = - gvShowTreeHelper edgeFn f kidname kid ++ " " - ++ (gvEdge prefix kidname "" (edgeFn node))- where kidname = prefix ++ "x" ++ (show index)- in showNode node ++ "\n" ++ (concat $ zipWith showKid [0..] l)---- ------------------------------------------------------------------------ invocation --- ------------------------------------------------------------------------- | Calls graphviz. If the second argument is the empty string, then we--- just send stuff directly to dot's stdin--graphviz :: String -- ^ graphviz's dot format.- -> String -- ^ the name of the file graphviz should write the dot - -> String -- ^ the name of the file graphviz should write its output - -> IO ExitCode---- We write the dot String to a temporary file which we then feed to graphviz.--- This is avoid complications with fork and pipes. We use png output even--- though it's uglier, because we don't have a wxhaskell widget that can --- display postscript... do we?--graphviz dot dotFile outputFile = do- let dotArgs' = ["-Gfontname=courier", - "-Nfontname=courier", - "-Efontname=courier", - "-Gcharset=latin1", -- FIXME: should really output UTF-8 instead- "-Tpng", "-o" ++ outputFile ]- dotArgs = dotArgs' ++ (if (null dotFile) then [] else [dotFile])- -- putStrLn ("sending to graphviz:\n" ++ dot) - when (not $ null dotFile) $ writeFile dotFile dot- (_, toGV, _, pid) <- runInteractiveProcess "dot" dotArgs Nothing Nothing- when (null dotFile) $ do - hPutStrLn toGV dot - hClose toGV- waitForProcess pid
− NLP/GenI/GraphvizShow.lhs
@@ -1,222 +0,0 @@-% GenI surface realiser-% Copyright (C) 2005 Carlos Areces and Eric Kow-%-% This program is free software; you can redistribute it and/or-% modify it under the terms of the GNU General Public License-% as published by the Free Software Foundation; either version 2-% of the License, or (at your option) any later version.-%-% This program is distributed in the hope that it will be useful,-% but WITHOUT ANY WARRANTY; without even the implied warranty of-% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the-% GNU General Public License for more details.-%-% You should have received a copy of the GNU General Public License-% along with this program; if not, write to the Free Software-% Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.--\section{GraphvizShow}--Outputting core GenI data to graphviz.--\begin{code}-{-# LANGUAGE FlexibleInstances, TypeSynonymInstances, FlexibleContexts #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}-module NLP.GenI.GraphvizShow-where-\end{code}--\ignore{-\begin{code}-import Data.List(intersperse,nub)--import NLP.GenI.Tags- ( TagElem, TagDerivation, idname,- tsemantics, ttree,- )-import NLP.GenI.Btypes (GeniVal(GConst), AvPair,- GNode(..), GType(..), Flist,- isConst,- showSem,- )-import NLP.GenI.General (wordsBy)-import NLP.GenI.Graphviz- ( gvUnlines, gvNewline- , GraphvizShow(graphvizShowAsSubgraph, graphvizLabel, graphvizParams)- , GraphvizShowNode(graphvizShowNode)- , GraphvizShowString(graphvizShow)- , gvNode, gvEdge, gvShowTree- )--\end{code}-}--% -----------------------------------------------------------------------\section{For GraphViz}-% ------------------------------------------------------------------------\begin{code}-type GvHighlighter a = a -> (a, Maybe String)--nullHighlighter :: GvHighlighter GNode-nullHighlighter a = (a,Nothing)--instance GraphvizShow Bool TagElem where- graphvizShowAsSubgraph sf = graphvizShowAsSubgraph (sf, nullHighlighter)- graphvizLabel sf = graphvizLabel (sf, nullHighlighter )- graphvizParams sf = graphvizParams (sf, nullHighlighter)---instance GraphvizShow (Bool, GvHighlighter GNode) TagElem where- graphvizShowAsSubgraph (sf,hfn) prefix te =- (gvShowTree (\_->[]) sf (prefix ++ "DerivedTree0") $- fmap hfn $ ttree te)-- graphvizLabel _ te =- -- we display the tree semantics as the graph label- let treename = "name: " ++ (idname te)- semlist = "semantics: " ++ (showSem $ tsemantics te)- in gvUnlines [ treename, semlist ]-- graphvizParams _ _ =- [ "fontsize = 10", "ranksep = 0.3"- , "node [fontsize=10]"- , "edge [fontsize=10 arrowhead=none]" ]-\end{code}--Helper functions for the TagElem GraphvizShow instance--\section{GNode - GraphvizShow}--\begin{code}-instance GraphvizShowNode (Bool) (GNode, Maybe String) where- -- compact -> (node, mcolour) -> String- graphvizShowNode detailed prefix (gn, mcolour) =- let -- attributes- filledParam = ("style", "filled")- fillcolorParam = ("fillcolor", "lemonchiffon")- shapeRecordParam = ("shape", "record")- shapePlaintextParam = ("shape", "plaintext")- --- colorParams = case mcolour of- Nothing -> []- Just c -> [ ("fontcolor", c) ]- shapeParams = if detailed- then [ shapeRecordParam, filledParam, fillcolorParam ]- else [ shapePlaintextParam ]- -- content- stub = showGnStub gn- extra = showGnDecorations gn- summary = if null extra then stub- else "{" ++ stub ++ "|" ++ extra ++ "}"- --- body = if not detailed then graphvizShow_ gn- else "{" ++ summary- ++ (barAnd.showFs $ gup gn)- ++ (maybeShow (barAnd.showFs) $ gdown gn)- ++ "}"- where barAnd x = "|" ++ x- showFs = gvUnlines . (map graphvizShow_)- in gvNode prefix body (shapeParams ++ colorParams)-\end{code}--\begin{code}-instance GraphvizShowString () GNode where- graphvizShow () gn =- let stub = showGnStub gn- extra = showGnDecorations gn- in stub ++ maybeShow_ " " extra--instance GraphvizShowString () AvPair where- graphvizShow () (a,v) = a ++ ":" ++ (graphvizShow_ v)--instance GraphvizShowString () GeniVal where- graphvizShow () (GConst x) = concat $ intersperse " ! " x- graphvizShow () x = show x--showGnDecorations :: GNode -> String-showGnDecorations gn =- case gtype gn of- Subs -> "!"- Foot -> "*"- _ -> if (gaconstr gn) then "#" else ""--showGnStub :: GNode -> String-showGnStub gn =- let cat = case getGnVal gup "cat" gn of- Nothing -> ""- Just v -> graphvizShow_ v- --- getIdx f =- case getGnVal f "idx" gn of- Nothing -> ""- Just v -> if isConst v then graphvizShow_ v else ""- idxT = getIdx gup- idxB = getIdx gdown- idx = idxT ++ (maybeShow_ "." idxB)- --- lexeme = concat $ intersperse "!" $ glexeme gn- in concat $ intersperse ":" $ filter (not.null) [ cat, idx, lexeme ]--getGnVal :: (GNode -> Flist) -> String -> GNode -> Maybe GeniVal-getGnVal getFeat attr gn =- case [ av | av <- getFeat gn, fst av == attr ] of- [] -> Nothing- (av:_) -> Just (snd av)---- | Apply fn to s if s is not null-maybeShow :: ([a] -> String) -> [a] -> String-maybeShow fn s = if null s then "" else fn s--- | Prefix a string if it is not null-maybeShow_ :: String -> String -> String-maybeShow_ prefix s = maybeShow (prefix++) s--graphvizShow_ :: (GraphvizShowString () a) => a -> String-graphvizShow_ = graphvizShow ()-\end{code}--% -----------------------------------------------------------------------\section{Derivation tree}-% ------------------------------------------------------------------------\begin{code}-graphvizShowDerivation :: TagDerivation -> String-graphvizShowDerivation deriv =- if (null histNodes)- then ""- else " node [ shape = plaintext ];\n"- ++ (concatMap showHistNode histNodes)- ++ (concatMap graphvizShowDerivation' deriv)- where showHistNode n = gvNode (gvDerivationLab n) (label n) []- label n = case wordsBy ':' n of- name:fam:tree:_ -> name ++ ":" ++ fam ++ gvNewline ++ tree- _ -> n ++ " (geni/gv ERROR)"- histNodes = reverse $ nub $ concatMap (\ (_,c,(p,_)) -> [c,p]) deriv-\end{code}--\begin{code}-graphvizShowDerivation' :: (Char, String, (String, String)) -> String-graphvizShowDerivation' (substadj, child, (parent,_)) =- gvEdge (gvDerivationLab parent) (gvDerivationLab child) "" p- where p = if substadj == 'a' then [("style","dashed")] else []-\end{code}--We have a couple of functions to help massage our data into Graphviz input-format: node names can't have hyphens in them and newlines within the node-labels should be represented literally as \verb$\n$.--\begin{code}-gvDerivationLab :: String -> String-gvDerivationLab xs = "Derivation" ++ gvMunge xs--newlineToSlashN :: Char -> String-newlineToSlashN '\n' = gvNewline-newlineToSlashN x = [x]--gvMunge :: String -> String-gvMunge = map dot2x . filter (/= ':') . filter (/= '-')--dot2x :: Char -> Char-dot2x '.' = 'x'-dot2x c = c-\end{code}
− NLP/GenI/GraphvizShowPolarity.lhs
@@ -1,130 +0,0 @@-% GenI surface realiser-% Copyright (C) 2005 Carlos Areces and Eric Kow-%-% This program is free software; you can redistribute it and/or-% modify it under the terms of the GNU General Public License-% as published by the Free Software Foundation; either version 2-% of the License, or (at your option) any later version.-%-% This program is distributed in the hope that it will be useful,-% but WITHOUT ANY WARRANTY; without even the implied warranty of-% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the-% GNU General Public License for more details.-%-% You should have received a copy of the GNU General Public License-% along with this program; if not, write to the Free Software-% Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.--\begin{code}-{-# LANGUAGE TypeSynonymInstances #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}-module NLP.GenI.GraphvizShowPolarity-where--import Data.List (intersperse)-import qualified Data.Map as Map--import NLP.GenI.Btypes(showSem)-import NLP.GenI.General(showInterval, isEmptyIntersect)-import NLP.GenI.Polarity(PolAut, PolState(PolSt), NFA(states, transitions), finalSt)-import NLP.GenI.Graphviz(GraphvizShow(..), gvUnlines, gvNewline, gvNode, gvEdge)-import NLP.GenI.Tags(idname)-\end{code}--\begin{code}-instance GraphvizShow () PolAut where- -- we want a directed graph (arrows)- graphvizShowGraph f aut =- "digraph aut {\n"- ++ "rankdir=LR\n"- ++ "ranksep = 0.02\n"- ++ "pack=1\n"- ++ "edge [ fontsize=10 ]\n"- ++ "node [ fontsize=10 ]\n"- ++ graphvizShowAsSubgraph f "aut" aut- ++ "}"-- --- graphvizShowAsSubgraph _ prefix aut =- let st = (concat.states) aut- ids = map (\x -> prefix ++ show x) ([0..] :: [Int])- -- map which permits us to assign an id to a state- stmap = Map.fromList $ zip st ids- in --- gvShowFinal aut stmap- -- any other state should be an ellipse- ++ "node [ shape = ellipse, peripheries = 1 ]\n"- -- draw the states and transitions- ++ (concat $ zipWith gvShowState ids st)- ++ (concat $ zipWith (gvShowTrans aut stmap) ids st )-\end{code}--\begin{code}-gvShowState :: String -> PolState -> String-gvShowState stId st =- -- note that we pass the label param explicitly to allow for null label- gvNode stId "" [ ("label", showSt st) ]- where showSt (PolSt pr ex po) = showPr pr ++ showEx ex ++ showPo po- showPr _ = "" -- (_,pr,_) = pr ++ gvNewline- showPo po = concat $ intersperse "," $ map showInterval po- showEx ex = if null ex then "" else showSem ex ++ gvNewline-\end{code}--Specify that the final states are drawn with a double circle--\begin{code}-gvShowFinal :: PolAut -> Map.Map PolState String -> String-gvShowFinal aut stmap =- if isEmptyIntersect (concat $ states aut) fin- then ""- else "node [ peripheries = 2 ]; "- ++ concatMap (\x -> " " ++ lookupId x) fin- ++ "\n"- where fin = finalSt aut- lookupId x = Map.findWithDefault "error_final" x stmap-\end{code}--Each transition is displayed with the name of the tree. If there is more-than one transition to the same state, they are displayed on a single-label.--\begin{code}-gvShowTrans :: PolAut -> Map.Map PolState String- -> String -> PolState -> String-gvShowTrans aut stmap idFrom st =- let -- outgoing transition labels from st- trans = Map.findWithDefault Map.empty st $ transitions aut- -- returns the graphviz dot command to draw a labeled transition- drawTrans (stTo,x) = case Map.lookup stTo stmap of- Nothing -> drawTrans' ("id_error_" ++ (sem_ stTo)) x- Just idTo -> drawTrans' idTo x- where sem_ (PolSt i _ _) = show i- --showSem (PolSt (_,pred,_) _ _) = pred- drawTrans' idTo x = gvEdge idFrom idTo (drawLabel x) []- drawLabel labels = gvUnlines labs- where- lablen = length labels- maxlabs = 6- excess = "...and " ++ (show $ lablen - maxlabs) ++ " more"- --- labstrs = map fn labels- fn Nothing = "EMPTY"- fn (Just x) = idname x- --- labs = if lablen > maxlabs- then take maxlabs labstrs ++ [ excess ]- else labstrs- in unlines $ map drawTrans $ Map.toList trans-\end{code}--%gvShowTransPred te =-% let p = tpredictors te-% charge fv = case () of _ | c == -1 -> "-"-% | c == 1 -> "+"-% | c > 0 -> "+" ++ (show c)-% | otherwise -> (show c)-% where c = lookupWithDefaultFM p 0 fv-% showfv (f,v) = charge (f,v) ++ f-% ++ (if (null v) then "" else ":" ++ v)-% in map showfv $ Map.keys p-
− NLP/GenI/Gui.lhs
@@ -1,762 +0,0 @@-% GenI surface realiser-% Copyright (C) 2005 Carlos Areces and Eric Kow-%-% This program is free software; you can redistribute it and/or-% modify it under the terms of the GNU General Public License-% as published by the Free Software Foundation; either version 2-% of the License, or (at your option) any later version.-%-% This program is distributed in the hope that it will be useful,-% but WITHOUT ANY WARRANTY; without even the implied warranty of-% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the-% GNU General Public License for more details.-%-% You should have received a copy of the GNU General Public License-% along with this program; if not, write to the Free Software-% Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.--\chapter{Graphical User Interface} --\begin{code}-{-# LANGUAGE FlexibleContexts #-}-module NLP.GenI.Gui(guiGeni) where-\end{code}--\ignore{-\begin{code}-import Graphics.UI.WX-import Graphics.UI.WXCore--import qualified Control.Monad as Monad -import qualified Data.Map as Map--import Data.IORef-import Data.List (isPrefixOf, nub, delete, (\\), find)-import Data.Maybe (isJust)-import System.Directory -import System.Exit (exitWith, ExitCode(ExitSuccess))--import qualified NLP.GenI.Builder as B-import qualified NLP.GenI.BuilderGui as BG-import NLP.GenI.Geni- ( ProgState(..), ProgStateRef, combine, initGeni- , loadEverything, loadTestSuite, loadTargetSemStr)-import NLP.GenI.General (boundsCheck, geniBug, trim, fst3)-import NLP.GenI.Btypes (ILexEntry(isemantics), TestCase(..), showFlist,)-import NLP.GenI.Tags (idname, tpolarities, tsemantics, TagElem)-import NLP.GenI.GeniShow (geniShow)-import NLP.GenI.Configuration- ( Params(..), Instruction, hasOpt- , hasFlagP, deleteFlagP, setFlagP, getFlagP, getListFlagP- , parseFlagWithParsec- --- , ExtraPolaritiesFlg(..)- , IgnoreSemanticsFlg(..)- , LexiconFlg(..)- , MacrosFlg(..)- , MaxTreesFlg(..)- , MorphCmdFlg(..)- , MorphInfoFlg(..)- , OptimisationsFlg(..)- , RootFeatureFlg(..)- , TestSuiteFlg(..)- , TestCaseFlg(..)- , TestInstructionsFlg(..)- , ViewCmdFlg(..)- --- , Optimisation(..)- , BuilderType(..), mainBuilderTypes )-import NLP.GenI.GeniParsers-import NLP.GenI.GuiHelper--import NLP.GenI.Polarity-import NLP.GenI.Simple.SimpleGui-import NLP.GenI.CkyEarley.CkyGui---\end{code}-}--\section{Main Gui}--\begin{code}-guiGeni :: ProgStateRef -> IO() -guiGeni pstRef = start $ mainGui pstRef-\end{code}--When you first start GenI, you will see this screen:-[[FIXME:screenshot wanted]]--It allows you to type in an input semantics (or to modify the one that was-automatically loaded up), twiddle some optimisations and run the realiser. You-can also opt to run the debugger instead of the realiser; see page-\pageref{sec:gui:debugger}.--\begin{code}-mainGui :: ProgStateRef -> IO ()-mainGui pstRef - = do --- pst <- readIORef pstRef- -- Top Window- f <- frame [text := "Geni Project"]- -- create statusbar field- status <- statusField []- -- create the file menu- fileMen <- menuPane [text := "&File"]- loadMenIt <- menuItem fileMen [text := "&Open files or configure GenI"]- quitMenIt <- menuQuit fileMen [text := "&Quit"]- set quitMenIt [on command := close f ]- -- create the tools menu- toolsMen <- menuPane [text := "&Tools"]- gbrowserMenIt <- menuItem toolsMen [ text := "&Inspect grammar" - , help := "Displays the trees in the grammar" ]- -- create the help menu- helpMen <- menuPane [text := "&Help"]- aboutMeIt <- menuAbout helpMen [help := "About"]- -- Tie the menu to this window- set f [ statusBar := [status] - , menuBar := [fileMen, toolsMen, helpMen]- -- put the menu event handler for an about box on the frame.- , on (menu aboutMeIt) := infoDialog f "About GenI" "The GenI generator.\nhttp://wiki.loria.fr/wiki/GenI" - -- event handler for the tree browser- , on (menu gbrowserMenIt) := do { loadEverything pstRef; treeBrowserGui pstRef } - ]- -- ------------------------------------------------------------------ -- buttons- -- ------------------------------------------------------------------ let config = pa pst - hasSem = hasFlagP TestSuiteFlg config- ignoreSem = hasFlagP IgnoreSemanticsFlg config- -- Target Semantics- testSuiteChoice <- choice f [ selection := 0, enabled := hasSem ]- tsTextBox <- textCtrl f [ wrap := WrapWord- , clientSize := sz 400 80- , enabled := hasSem - , text := if ignoreSem- then "% --ignoresemantics set" else "" ]- testCaseChoice <- choice f [ selection := 0 - , enabled := hasSem ]- -- Box and Frame for files loaded - macrosFileLabel <- staticText f [ text := getListFlagP MacrosFlg config ]- lexiconFileLabel <- staticText f [ text := getListFlagP LexiconFlg config ]- -- Generate and Debug - let genfn = doGenerate f pstRef tsTextBox- pauseOnLexChk <- checkBox f [ text := "Inspect lex", tooltip := "Affects debugger only" ]- debugBt <- button f [ text := "Debug"- , on command := get pauseOnLexChk checked >>= genfn True ]- genBt <- button f [text := "Generate", on command := genfn False False ]- quitBt <- button f [ text := "Quit",- on command := close f]- -- ------------------------------------------------------------------ -- optimisations- -- ------------------------------------------------------------------ algoChoiceBox <- radioBox f Vertical (map show mainBuilderTypes)- [ selection := case builderType config of- SimpleBuilder -> 0- SimpleOnePhaseBuilder -> 1- CkyBuilder -> 2- EarleyBuilder -> 3- NullBuilder -> 0 ]- set algoChoiceBox [ on select := toggleAlgo pstRef algoChoiceBox ]- polChk <- optCheckBox Polarised pstRef f- [ text := "Polarities"- , tooltip := "Use the polarity optimisation"- ]- useSemConstraintsChk <- antiOptCheckBox NoConstraints pstRef f- [ text := "Sem constraints"- , tooltip := "Use any sem constraints the user provides"- ]- iafChk <- optCheckBox Iaf pstRef f- [ text := "Idx acc filter"- , tooltip := "Only available in CKY/Earley for now"- ]- semfilterChk <- optCheckBox SemFiltered pstRef f- [ text := "Semantic filters"- , tooltip := "(2p only) Filter away semantically incomplete structures before adjunction phase"- ]- rootfilterChk <- optCheckBox RootCatFiltered pstRef f- [ text := "Root filters"- , tooltip := "(2p only) Filter away non-root structures before adjunction phase"- ]- extrapolText <- staticText f - [ text := maybe "" showLitePm $ getFlagP ExtraPolaritiesFlg config- , tooltip := "Use the following additional polarities" - ]- -- commands for the checkboxes- let togglePolStuff = do c <- get polChk checked- set extrapolText [ enabled := c ]- set polChk [on command :~ (>> togglePolStuff) ]- -- ------------------------------------------------------------------ -- layout; packing it all together- -- ------------------------------------------------------------------ -- set any last minute handlers, run any last minute functions- let onLoad = readConfig f pstRef macrosFileLabel lexiconFileLabel testSuiteChoice tsTextBox testCaseChoice- set loadMenIt [ on command := do configGui pstRef onLoad ]- onLoad- togglePolStuff- --- let labeledRow l w = row 1 [ label l, hfill (widget w) ]- let gramsemBox = boxed "Files last loaded" $ - hfill $ column 1 - [ labeledRow "trees:" macrosFileLabel- , labeledRow "lexicon:" lexiconFileLabel- ]- optimBox = --boxed "Optimisations " $ -- can't used boxed with wxwidgets 2.6 -- bug?- column 5 [ label "Algorithm"- , dynamic $ widget algoChoiceBox- , label "Optimisations"- , dynamic $ widget polChk - , row 5 [ label " ", column 5 - [ dynamic $ row 5 [ label "Extra: ", widget extrapolText ] ] ]- , dynamic $ widget useSemConstraintsChk- , dynamic $ widget semfilterChk - , dynamic $ widget rootfilterChk- , dynamic $ widget iafChk- ]- set f [layout := column 5 [ gramsemBox- , row 5 [ fill $ -- boxed "Input Semantics" $ - hfill $ column 5 - [ labeledRow "test suite: " testSuiteChoice- , labeledRow "test case: " testCaseChoice- , fill $ widget tsTextBox ]- , vfill optimBox ]- -- ----------------------------- Generate and quit - , row 1 [ widget quitBt - , hfloatRight $ row 5 [ widget pauseOnLexChk, widget debugBt, widget genBt ]] ]- , clientSize := sz 525 325- , on closing := exitWith ExitSuccess - ]- -- this is to make the menubar appear on OS X (in app bundles)- -- don't know why we need to do this though, bug?- windowHide f- windowShow f- windowRaise f -\end{code}--\subsection{Configuration}--Most of the optimisations are availalable as checkboxes. Note the following-point about anti-optimisations: An anti-optimisation disables a default-behaviour which is assumed to be ``optimisation''. But of course we don't-want to confuse the GUI user, so we confuse the programmer instead:-Given an anti-optimisation DisableFoo, we have a check box UseFoo. If UseFoo-is checked, we remove DisableFoo from the list; if it is unchecked, we add-it to the list. This is the opposite of the default behaviour, but the-result, I hope, is intuitive for the user.--\begin{code}-toggleAlgo :: (Selection a, Items a String) => ProgStateRef -> a -> IO ()-toggleAlgo pstRef box =- do asel <- get box selection- aitems <- get box items- let selected = aitems !! asel- btable = zip (map show mainBuilderTypes) mainBuilderTypes- btype = case [ b | (name, b) <- btable, name == selected ] of- [] -> geniBug $ "Unknown builder type " ++ selected- [b] -> b- _ -> geniBug $ "More than one builder has the name " ++ selected- modifyIORef pstRef (\x -> x { pa = (pa x) { builderType = btype } })--optCheckBox, antiOptCheckBox ::- Optimisation -> ProgStateRef- -> Window a -> [Prop (CheckBox ())]- -> IO (CheckBox ())---- | Checkbox for enabling or disabling an optimisation--- You need not set the checked or on command attributes--- as this is done for you (but you can if you want,--- setting checked will override the default, and any--- command you set will be run before the toggle stuff)-optCheckBox = optCheckBoxHelper id---- | Same as 'optCheckBox' but for anti-optimisations-antiOptCheckBox = optCheckBoxHelper not--optCheckBoxHelper :: (Bool -> Bool) -> Optimisation -> ProgStateRef- -> Window a -> [Prop (CheckBox ())]- -> IO (CheckBox ())-optCheckBoxHelper idOrNot o pstRef f as =- do pst <- readIORef pstRef- chk <- checkBox f $ [ checked := idOrNot $ hasOpt o $ pa pst ] ++ as- set chk [ on command :~ (>> onCheck chk) ]- return chk- where- onCheck chk =- do isChecked <- get chk checked- pst <- readIORef pstRef- let config = pa pst- modopt = if idOrNot isChecked then (o:) else delete o- newopts = nub.modopt $ getListFlagP OptimisationsFlg config- modifyIORef pstRef (\x -> x{pa = setFlagP OptimisationsFlg newopts (pa x)})-\end{code}--% ---------------------------------------------------------------------\section{Loading files}-% ----------------------------------------------------------------------\paragraph{readConfig} is used to update the graphical interface after-you run the \fnref{configGui}. It is also called when you first launch-the GUI--\begin{code}-readConfig :: (Textual l, Textual t, Able ch, Items ch String, Selection ch, Selecting ch)- => Window w -> ProgStateRef -> l -> l -> ch -> t -> ch -> IO ()-readConfig f pstRef macrosFileLabel lexiconFileLabel suiteChoice tsBox caseChoice =- do pst <- readIORef pstRef- let config = pa pst- -- errHandler title err = errorDialog f title (show err)- set macrosFileLabel [ text := getListFlagP MacrosFlg config ]- set lexiconFileLabel [ text := getListFlagP LexiconFlg config ]- -- set tsFileLabel [ text := getListFlagP TestSuiteFlg config ]- -- read the test suite if there is one- case getListFlagP TestInstructionsFlg config of- [] ->- do set suiteChoice [ enabled := False, items := [] ]- set caseChoice [ enabled := False, items := [] ]- is ->- do -- handler for selecting a test suite- let imap = Map.fromList $ zip [0..] is- onTestSuiteChoice = do- sel <- get suiteChoice selection- case Map.lookup sel imap of- Nothing -> geniBug $ "No such index in test suite selector (gui): " ++ show sel- Just t -> loadTestSuiteAndRefresh f pstRef t tsBox caseChoice- set suiteChoice [ enabled := True, items := map fst is- , on select := onTestSuiteChoice, selection := 0 ]- set caseChoice [ enabled := True ]- onTestSuiteChoice -- load the first suite---- | Load the given test suite and update the GUI accordingly.--- This is used when you first start the graphical interface--- or when you run the configuration menu.-loadTestSuiteAndRefresh :: (Textual a, Selecting b, Selection b, Items b String) - => Window w -> ProgStateRef -> Instruction -> a -> b -> IO ()-loadTestSuiteAndRefresh f pstRef (suitePath,mcs) tsBox caseChoice =- do modifyIORef pstRef $ \pst ->- pst { pa = setFlagP TestSuiteFlg suitePath- $ deleteFlagP TestCaseFlg -- shouldn't change anything- $ pa pst }- catch- (loadTestSuite pstRef)- (\e -> errorDialog f ("Error reading test suite " ++ suitePath) (show e))- pst <- readIORef pstRef- let suite = tsuite pst- theCase = tcase pst- filterCases =- case mcs of- Nothing -> id- Just cs -> filter (\c -> tcName c `elem` cs)- suiteCases = filterCases suite- suiteCaseNames = map tcName suiteCases- -- we number the cases for easy identification, putting - -- a star to highlight the selected test case (if available)- let numfn :: Int -> String -> String- numfn n t = (if t == theCase then "* " else "")- ++ (show n) ++ ". " ++ t- tcaseLabels = zipWith numfn [1..] suiteCaseNames- -- we select the first case in cases_, if available- let fstInCases _ [] = 0 - fstInCases n (x:xs) = - if (x == theCase) then n else fstInCases (n+1) xs- caseSel = if null theCase then 0 - else fstInCases 0 suiteCaseNames- ----------------------------------------------------- -- handler for selecting a test case- ----------------------------------------------------- let displaySemInput (TestCase { tcSem = si, tcSemString = str }) =- geniShow $ toSemInputString si str- let onTestCaseChoice = do- csel <- get caseChoice selection- if (boundsCheck csel suite)- then do let s = (suiteCases !! csel)- set tsBox [ text :~ (\_ -> displaySemInput s) ]- else geniBug $ "Gui: test case selector bounds check error: " ++- show csel ++ " of " ++ show suite ++ "\n" - ----------------------------------------------------- set caseChoice [ items := tcaseLabels - , selection := caseSel- , on select := onTestCaseChoice ]- when (not $ null suite) onTestCaseChoice -- run this once-\end{code}- -% ---------------------------------------------------------------------\section{Configuration}-% ----------------------------------------------------------------------\paragraph{configGui}\label{fn:configGui} provides a graphical interface which-aims to be a complete substitute for the command line switches. In addition to-the program state \fnparam{pstRef}, it takes a continuation \fnparam{loadFn}-which tells what to do when the user closes the window.--The only thing which are not provided in this GUI are a list of optimisations-and a test case selector (which are already handled by the main interface).-This GUI is a standalone window with two tabbed sections. Note: one thing-you may want to note is that we do not divide the same way between basic-and advanced options as with the console interface.--\begin{code}-configGui :: ProgStateRef -> IO () -> IO () -configGui pstRef loadFn = do - pst <- readIORef pstRef- let config = pa pst- -- - f <- frame []- p <- panel f []- nb <- notebook p []- let browseTxt = "Browse"- --- let fakeBoxed title lst = hstretch $ column 3 $ map hfill $ - [ hrule 1 , alignRight $ label title, vspace 5 ] - ++ map hfill lst- let shortSize = sz 10 25- let longSize = sz 20 25-\end{code}--The first tab contains only the basic options:--\begin{code}- pbas <- panel nb []- -- files loaded (labels)- macrosFileLabel <- staticText pbas [ text := getListFlagP MacrosFlg config ]- lexiconFileLabel <- staticText pbas [ text := getListFlagP LexiconFlg config ]- tsFileLabel <- staticText pbas [ text := getListFlagP TestSuiteFlg config ]- -- "Browse buttons"- macrosBrowseBt <- button pbas [ text := browseTxt ]- lexiconBrowseBt <- button pbas [ text := browseTxt ]- tsBrowseBt <- button pbas [ text := browseTxt ]- -- root feature- rootFeatTxt <- entry pbas- [ text := showFlist $ getListFlagP RootFeatureFlg config- , size := longSize ]- let layFiles = [ row 1 [ label "trees:" - , fill $ widget macrosFileLabel- , widget macrosBrowseBt ]- , row 1 [ label "lexicon:"- , fill $ widget lexiconFileLabel- , widget lexiconBrowseBt ] - , row 1 [ label "test suite:"- , fill $ widget tsFileLabel- , widget tsBrowseBt ]- , hspace 5- , hfill $ vrule 1- , row 3 [ label "root features"- , hglue- , rigid $ widget rootFeatTxt ] - ] - -- the layout for the basic stuff- let layBasic = dynamic $ container pbas $ -- boxed "Basic options" $ - hfloatLeft $ dynamic $ fill $ column 4 $ map (dynamic.hfill) $ layFiles -\end{code}--The second tab contains more advanced options. Maybe we should split this-into more tabs?--\begin{code}- padv <- panel nb []- -- XMG tools - viewCmdTxt <- entry padv - [ tooltip := "Command used for XMG tree viewing"- , text := getListFlagP ViewCmdFlg config ]- let layXMG = fakeBoxed "XMG tools" - [ row 3 [ label "XMG view command"- , marginRight $ hfill $ widget viewCmdTxt ] ]- -- polarities- extraPolsTxt <- entry padv - [ text := maybe "" showLitePm $ getFlagP ExtraPolaritiesFlg config- , size := shortSize ]- let layPolarities = fakeBoxed "Polarities" [ hfill $ row 1 - [ label "extra polarities", rigid $ widget extraPolsTxt ] ]- -- morphology- morphFileLabel <- staticText padv [ text := getListFlagP MorphInfoFlg config ]- morphFileBrowseBt <- button padv [ text := browseTxt ]- morphCmdTxt <- entry padv - [ tooltip := "Commmand used for morphological generation" - , text := getListFlagP MorphCmdFlg config ]- let layMorph = fakeBoxed "Morphology" - [ row 3 [ label "morph info:"- , expand $ hfill $ widget morphFileLabel- , widget morphFileBrowseBt ]- , row 3 [ label "morph command"- , (marginRight.hfill) $ widget morphCmdTxt ] ]- -- ignore semantics- ignoreSemChk <- checkBox padv - [ text := "Ignore semantics"- , tooltip := "Useful as a corpus generator"- , checked := hasFlagP IgnoreSemanticsFlg config ]- let maxTreesStr = maybe "" show $ getFlagP MaxTreesFlg config- maxTreesText <- entry padv - [ text := maxTreesStr - , tooltip := "Limit number of elementary trees in a derived tree" - , size := shortSize ]- let layIgnoreSem = fakeBoxed "Ignore Semantics Mode" - [ row 3 [ widget ignoreSemChk - , hspace 5 - , label "max trees", rigid $ widget maxTreesText ] ]- -- put the whole darn thing together- let layAdvanced = hfloatLeft $ container padv $ column 10 - $ [ layXMG, layPolarities, layMorph, layIgnoreSem ]-\end{code}--When the user clicks on a Browse button, an open file dialogue should pop up.-It gets its value from the file label on its left (passed in as an argument),-and updates said label when the user has made a selection.--\begin{code}- -- helper functions- curDir <- getCurrentDirectory- let curDir2 = curDir ++ "/"- trim2 pth = if curDir2 `isPrefixOf` pth2- then drop (length curDir2) pth2- else pth2- where pth2 = trim pth- let onBrowse theLabel - = do rawFilename <- get theLabel text- let filename = trim2 rawFilename- filetypes = [("Any file",["*","*.*"])]- fsel <- fileOpenDialog f False True- "Choose your file..." filetypes "" filename- case fsel of- -- if the user does not select any file there are no changes- Nothing -> return () - Just file -> set theLabel [ text := trim2 file ]- -- end onBrowse- -- activate those "Browse" buttons- let setBrowse w l = set w [ on command := onBrowse l ]- setBrowse macrosBrowseBt macrosFileLabel- setBrowse lexiconBrowseBt lexiconFileLabel - setBrowse tsBrowseBt tsFileLabel- setBrowse morphFileBrowseBt morphFileLabel-\end{code}--Let's not forget the layout which puts the whole configGui together and the-command that makes everything ``work'':--\begin{code}- let parsePol = parseFlagWithParsec "polarities" geniPolarities- parseRF = parseFlagWithParsec "root features" geniFeats- onLoad - = do macrosVal <- get macrosFileLabel text- lexconVal <- get lexiconFileLabel text- tsVal <- get tsFileLabel text- --- rootCatVal <- get rootFeatTxt text- extraPolVal <- get extraPolsTxt text- --- viewVal <- get viewCmdTxt text - --- morphCmdVal <- get morphCmdTxt text- morphInfoVal <- get morphFileLabel text- --- ignoreVal <- get ignoreSemChk checked - maxTreesVal <- get maxTreesText text- --- let maybeSet fl fn x =- if null x then deleteFlagP fl else setFlagP fl (fn x)- maybeSetStr fl x = maybeSet fl id x- toggleFlag fl b = if b then setFlagP fl () else deleteFlagP fl- let setConfig = id- . (maybeSet MaxTreesFlg read maxTreesVal)- . (toggleFlag IgnoreSemanticsFlg ignoreVal)- . (maybeSetStr MacrosFlg macrosVal)- . (maybeSetStr LexiconFlg lexconVal)- . (maybeSetStr TestSuiteFlg tsVal)- . (maybeSet RootFeatureFlg parseRF rootCatVal)- . (maybeSet ExtraPolaritiesFlg parsePol extraPolVal)- . (maybeSetStr ViewCmdFlg viewVal)- . (maybeSetStr MorphCmdFlg morphCmdVal)- . (maybeSetStr MorphInfoFlg morphInfoVal)- modifyIORef pstRef $ \x -> x { pa = setConfig (pa x) }- loadFn - -- end onLoad- -- the button bar- cancelBt <- button p - [ text := "Cancel", on command := close f ]- loadBt <- button p - [ text := "Load", on command := do { onLoad; close f } ]- let layButtons = hfill $ row 1 - [ hfloatLeft $ widget cancelBt- , hfloatRight $ widget loadBt ]- --- set f [ layout := dynamic $ fill $ container p $ column 0 - [ fill $ tabs nb [ tab "Basic" layBasic- , tab "Advanced" layAdvanced ] - , hfill $ layButtons ]- ] -\end{code}- -% ---------------------------------------------------------------------\section{Running the generator}-% ----------------------------------------------------------------------\paragraph{doGenerate} parses the target semantics, then calls the-generator and displays the result in a results gui (below).--\begin{code}-doGenerate :: Textual b => Window a -> ProgStateRef -> b -> Bool -> Bool -> IO ()-doGenerate f pstRef sembox useDebugger pauseOnLex =- do loadEverything pstRef- sem <- get sembox text- loadTargetSemStr pstRef sem- --- pst <- readIORef pstRef- let config = pa pst- withBuilderGui a =- case builderType config of- NullBuilder -> error "No gui available for NullBuilder"- SimpleBuilder -> a simpleGui_2p- SimpleOnePhaseBuilder -> a simpleGui_1p- CkyBuilder -> a ckyGui- EarleyBuilder -> a earleyGui- --- let doDebugger bg = debugGui bg pstRef pauseOnLex- doResults bg = resultsGui bg pstRef- do catch (withBuilderGui $ if useDebugger then doDebugger else doResults)- (handler "Error during realisation")- -- FIXME: it would be nice to distinguish between generation and ts- -- parsing errors- `catch` (handler "Error parsing the input semantics")- where- handler title err = errorDialog f title (show err)-\end{code}--\paragraph{resultsGui} displays generation result in a window. The window-consists of various tabs for intermediary results in lexical-selection, derived trees, derivation trees and generation statistics.--\begin{code}-resultsGui :: BG.BuilderGui -> ProgStateRef -> IO ()-resultsGui builderGui pstRef =- do -- results window- f <- frame [ text := "Results"- , fullRepaintOnResize := False- , layout := stretch $ label "Generating..."- , clientSize := sz 300 300- ]- p <- panel f []- nb <- notebook p []- -- realisations tab- (results,stats,resTab) <- BG.resultsPnl builderGui pstRef nb- -- statistics tab- let sentences = (fst . unzip) results- statTab <- statsGui nb sentences stats- -- pack it all together- set f [ layout := container p $ column 0 [ tabs nb- -- we put the realisations tab last because of what- -- seems to be buggy behaviour wrt to wxhaskell- -- or wxWidgets 2.4 and the splitter- [ tab "summary" statTab- , tab "realisations" resTab ] ]- , clientSize := sz 700 600 ]- return ()-\end{code}--\paragraph{debuggerGui} All GenI builders can make use of an interactive-graphical debugger. We provide here a universal debugging interface,-which makes use of some parameterisable bits as defined in the BuilderGui-module. This window shows a seperate tab for each surface realisation-task (lexical selection, filtering, building). We also rely heavily on-helper code defined in \ref{sec:debugger_helpers}.--\begin{code}-debugGui :: BG.BuilderGui -> ProgStateRef -> Bool -> IO ()-debugGui builderGui pstRef pauseOnLex =- do pst <- readIORef pstRef- let config = pa pst- btype = show $ builderType config- --- f <- frame [ text := "GenI Debugger - " ++ btype ++ " edition"- , fullRepaintOnResize := False- , clientSize := sz 300 300 ]- p <- panel f []- nb <- notebook p []- -- generation step 1- initStuff <- initGeni pstRef- let (tsem,_,_) = B.inSemInput initStuff- (cand,_) = unzip $ B.inCands initStuff- lexonly = B.inLex initStuff- -- continuation for candidate selection tab- let step2 newCands =- do -- generation step 2.A (run polarity stuff)- let newInitStuff = initStuff { B.inCands = map (\x -> (x, -1)) newCands }- (input2, _, autstuff) = B.preInit newInitStuff config- -- automata tab- let (auts, _, finalaut, _) = autstuff- autPnl <- if hasOpt Polarised config- then fst3 `fmap` polarityGui nb auts finalaut- else messageGui nb "polarity filtering disabled"- -- generation step 2.B (start the generator for each path)- debugPnl <- BG.debuggerPnl builderGui nb config input2 btype- let autTab = tab "automata" autPnl- debugTab = tab (btype ++ "-session") debugPnl- genTabs = if hasOpt Polarised config- then [ autTab, debugTab ] else [ debugTab ]- --- set f [ layout := container p $ tabs nb genTabs- , clientSize := sz 700 600 ]- return ()- -- candidate selection tab- let missedSem = tsem \\ (nub $ concatMap tsemantics cand)- -- we assume that for a tree to correspond to a lexical item,- -- it must have the same semantics- hasTree l = isJust $ find (\t -> tsemantics t == lsem) cand- where lsem = isemantics l- missedLex = [ l | l <- lexonly, (not.hasTree) l ]- (canPnl,_,_) <- if pauseOnLex- then pauseOnLexGui pst nb cand missedSem missedLex step2- else candidateGui pst nb cand missedSem missedLex- -- basic tabs- let basicTabs = [ tab "lexical selection" canPnl ]- --- set f [ layout := container p $ tabs nb basicTabs- , clientSize := sz 700 600 ]- -- display all tabs if we are not told to pause on lex selection- when (not pauseOnLex) (step2 cand)-\end{code}--- -% ---------------------------------------------------------------------\section{Tree browser}-\label{sec:treebrowser_gui}-% ----------------------------------------------------------------------This is a very simple semantically-separated browser for all the-trees in the grammar. Note that we can't just reuse candidateGui's-code because we label and sort the trees differently. Here we -ignore the arguments in tree semantics, and we display the tree-polarities in its label.--\begin{code}-treeBrowserGui :: ProgStateRef -> IO () -treeBrowserGui pstRef = do- pst <- readIORef pstRef- -- ALL THE TREES in the grammar... muahahaha!- let semmap = combine (gr pst) (le pst)- -- browser window- f <- frame [ text := "Tree Browser" - , fullRepaintOnResize := False - ] - -- the heavy GUI artillery- let sem = Map.keys semmap- --- lookupTr k = Map.findWithDefault [] k semmap- treesfor k = Nothing : (map Just $ lookupTr k)- labsfor k = ("___" ++ k ++ "___") : (map fn $ lookupTr k)- where fn t = idname t ++ polfn (tpolarities t)- polfn p = if Map.null p - then "" - else " (" ++ showLitePm p ++ ")"- --- trees = concatMap treesfor sem- itNlabl = zip trees (concatMap labsfor sem)- (browser,_,_) <- tagViewerGui pst f "tree browser" "grambrowser" itNlabl- -- the button panel- let count = length trees - length sem- quitBt <- button f [ text := "Close", on command := close f ]- -- pack it all together - set f [ layout := column 5 [ browser, - row 5 [ label ("number of trees: " ++ show count)- , hfloatRight $ widget quitBt ] ]- , clientSize := sz 700 600 ]- return ()-\end{code}
− NLP/GenI/GuiHelper.lhs
@@ -1,858 +0,0 @@-% GenI surface realiser-% Copyright (C) 2005 Carlos Areces and Eric Kow-%-% This program is free software; you can redistribute it and/or-% modify it under the terms of the GNU General Public License-% as published by the Free Software Foundation; either version 2-% of the License, or (at your option) any later version.-%-% This program is distributed in the hope that it will be useful,-% but WITHOUT ANY WARRANTY; without even the implied warranty of-% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the-% GNU General Public License for more details.-%-% You should have received a copy of the GNU General Public License-% along with this program; if not, write to the Free Software-% Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.--\chapter{GUI Helper} --This module provides helper functions for building the GenI graphical-user interface--\begin{code}-{-# LANGUAGE FlexibleContexts #-}-module NLP.GenI.GuiHelper where-\end{code}--\ignore{-\begin{code}-import Graphics.UI.WX-import Graphics.UI.WXCore--import qualified Control.Monad as Monad -import Control.Monad.State ( execStateT, runState )-import qualified Data.Map as Map--import Data.IORef-import Data.List (intersperse)-import System.Directory -import System.Process (runProcess)-import Text.ParserCombinators.Parsec (parseFromFile)--import NLP.GenI.Graphviz-import NLP.GenI.Automaton (numStates, numTransitions)-import NLP.GenI.Statistics (Statistics, showFinalStats)--import NLP.GenI.Configuration ( getFlagP, MacrosFlg(..), ViewCmdFlg(..) )-import NLP.GenI.GeniShow(geniShow)-import NLP.GenI.GraphvizShow ()-import NLP.GenI.Tags (TagItem(tgIdName), tagLeaves)-import NLP.GenI.Geni- ( ProgState(..), showRealisations )-import NLP.GenI.GeniParsers ( geniTagElems )-import NLP.GenI.General- (geniBug, boundsCheck, (///), dropTillIncluding, basename, ePutStrLn)-import NLP.GenI.Btypes- ( showAv, showPred, showSem, showLexeme, Sem, ILexEntry(iword, ifamname), )-import NLP.GenI.Tags- ( idname, mapBySem, TagElem(ttrace, tinterface) )--import NLP.GenI.Configuration- ( Params(..), MetricsFlg(..), setFlagP )--import qualified NLP.GenI.Builder as B-import NLP.GenI.Builder (queryCounter, num_iterations, chart_size,- num_comparisons)-import NLP.GenI.Polarity (PolAut, detectPolFeatures)-import NLP.GenI.GraphvizShowPolarity ()-\end{code}-}--\subsection{Lexically selected items}--We have a browser for the lexically selected items. We group the lexically-selected items by the semantics they subsume, inserting along the way some-fake trees and labels for the semantics.--The arguments \fnparam{missedSem} and \fnparam{missedLex} are used to -indicate to the user respectively if any bits of the input semantics-have not been accounted for, or if there have been lexically selected-items for which no tree has been found.--\begin{code}-candidateGui :: ProgState -> (Window a) -> [TagElem] -> Sem -> [ILexEntry]- -> GvIO Bool (Maybe TagElem)-candidateGui pst f xs missedSem missedLex = do- p <- panel f [] - (tb,gvRef,updater) <- tagViewerGui pst p "lexically selected item" "candidates"- $ sectionsBySem xs- let warningSem = if null missedSem then ""- else "WARNING: no lexical selection for " ++ showSem missedSem- warningLex = if null missedLex then ""- else "WARNING: '" ++ (concat $ intersperse ", " $ map showLex missedLex)- ++ "' were lexically selected, but are not anchored to"- ++ " any trees"- where showLex l = (showLexeme $ iword l) ++ "-" ++ (ifamname l)- --- polFeats = "Polarity attributes detected: " ++ (unwords.detectPolFeatures) xs- warning = unlines $ filter (not.null) [ warningSem, warningLex, polFeats ]- -- side panel- sidePnl <- panel p []- ifaceLst <- singleListBox sidePnl [ tooltip := "interface for this tree (double-click me!)" ]- traceLst <- singleListBox sidePnl [ tooltip := "trace for this tree (double-click me!)" ]- tNoted <- textCtrl sidePnl [ wrap := WrapWord, text := "Hint: copy from below and paste into the sem:\n" ]- let laySide = container sidePnl $ column 2- [ label "interface"- , fill $ widget ifaceLst- , label "trace"- , fill $ widget traceLst- , label "notes"- , fill $ widget tNoted ]- -- handlers- let addLine :: String -> String -> String- addLine x y = y ++ "\n" ++ x- --- addToNoted w =- do sel <- get w selection- things <- get w items- when (sel > 0) $ set tNoted [ text :~ addLine (things !! sel) ]- set ifaceLst [ on doubleClick := \_ -> addToNoted ifaceLst ]- set traceLst [ on doubleClick := \_ -> addToNoted traceLst ]- -- updaters : what happens when the user selects an item- let updateTrace = gvOnSelect (return ())- (\s -> set traceLst [ items := ttrace s ])- updateIface = gvOnSelect (return ())- (\s -> set ifaceLst [ items := map showAv $ tinterface s ])- Monad.unless (null xs) $ do- addGvHandler gvRef updateTrace- addGvHandler gvRef updateIface- -- first time run- gvSt <- readIORef gvRef- updateIface gvSt- updateTrace gvSt- --- let layMain = fill $ row 2 [ fill tb, vfill laySide ]- theItems = if null warning then [ layMain ] else [ hfill (label warning) , layMain ]- lay = fill $ container p $ column 5 theItems- return (lay, gvRef, updater)--sectionsBySem :: (TagItem t) => [t] -> [ (Maybe t, String) ]-sectionsBySem tsem =- let semmap = mapBySem tsem- sem = Map.keys semmap- --- lookupTr k = Map.findWithDefault [] k semmap- section k = (Nothing, header) : (map tlab $ lookupTr k)- where header = "___" ++ showPred k ++ "___"- tlab t = (Just t, tgIdName t)- in concatMap section sem-\end{code}- -\subsection{Polarity Automata}--A browser to see the automata constructed during the polarity optimisation-step.--\begin{code}-polarityGui :: (Window a) -> [(String,PolAut,PolAut)] -> PolAut- -> GvIO () PolAut-polarityGui f xs final = do- let stats a = " (" ++ (show $ numStates a) ++ "st " ++ (show $ numTransitions a) ++ "tr)"- aut2 (_ , a1, a2) = [ a1, a2 ]- autLabel (fv,a1,a2) = [ fv ++ stats a1, fv ++ " pruned" ++ stats a2]- autlist = (concatMap aut2 xs) ++ [ final ]- labels = (concatMap autLabel xs) ++ [ "final" ++ stats final ]- --- gvRef <- newGvRef () labels "automata"- setGvDrawables gvRef autlist- graphvizGui f "polarity" gvRef-\end{code}- -\paragraph{statsGui} displays the generation statistics and provides a-handy button for saving results to a text file.--\begin{code}-statsGui :: (Window a) -> [String] -> Statistics -> IO Layout-statsGui f sentences stats =- do let msg = showRealisations sentences- --- p <- panel f []- t <- textCtrl p [ text := msg, enabled := False ]- statsTxt <- staticText p [ text := showFinalStats stats ]- --- saveBt <- button p [ text := "Save to file"- , on command := maybeSaveAsFile f msg ]- return $ fill $ container p $ column 1 $- [ hfill $ label "Performance data"- , hfill $ widget statsTxt- , hfill $ label "Realisations"- , fill $ widget t- , hfloatRight $ widget saveBt ]-\end{code}--\subsection{TAG trees}--Our graphical interfaces have to display a great variety of items. To-keep things nicely factorised, we define some type classes to describe-the things that these items may have in common.--\begin{code}--- | Any data structure which has corresponds to a TAG tree and which--- has some notion of derivation-class XMGDerivation a where- getSourceTrees :: a -> [String]--instance XMGDerivation TagElem where- getSourceTrees te = [idname te]-\end{code}--\fnlabel{toSentence} almost displays a TagElem as a sentence, but only-good enough for debugging needs. The problem is that each leaf may be-an atomic disjunction. Our solution is just to display each choice and-use some delimiter to seperate them. We also do not do any-morphological processing.--\begin{code}-toSentence :: TagElem -> String-toSentence = unwords . map squishLeaf . tagLeaves--squishLeaf :: (a,([String], b)) -> String-squishLeaf = showLexeme.fst.snd-\end{code}--\subsection{TAG viewer}--A TAG viewer is a graphvizGui that lets the user toggle the display-of TAG feature structures.--\begin{code}-tagViewerGui :: (GraphvizShow Bool t, TagItem t, XMGDerivation t)- => ProgState -> (Window a) -> String -> String -> [(Maybe t,String)]- -> GvIO Bool (Maybe t)-tagViewerGui pst f tip cachedir itNlab = do- p <- panel f [] - let (tagelems,labels) = unzip itNlab- gvRef <- newGvRef False labels tip- setGvDrawables gvRef tagelems - (lay,ref,updaterFn) <- graphvizGui p cachedir gvRef- -- button bar widgets- detailsChk <- checkBox p [ text := "Show features"- , checked := False ]- viewTagLay <- viewTagWidgets p gvRef (pa pst)- -- handlers- let onDetailsChk =- do isDetailed <- get detailsChk checked- setGvParams gvRef isDetailed- updaterFn- set detailsChk [ on command := onDetailsChk ]- -- pack it all in - let cmdBar = hfill $ row 5 - [ dynamic $ widget detailsChk- , viewTagLay ]- lay2 = fill $ container p $ column 5 [ fill lay, cmdBar ]- return (lay2,ref,updaterFn)-\end{code}--\subsection{XMG Metagrammar stuff}--XMG trees are produced by the XMG metagrammar system-(\url{http://sourcesup.cru.fr/xmg/}). To debug these grammars, it is useful,-given a TAG tree, to see what its metagrammar origins are. We provide here an-interface to Yannick Parmentier's handy visualisation tool ViewTAG.--\begin{code}-viewTagWidgets :: (GraphvizShow Bool t, TagItem t, XMGDerivation t)- => Window a -> GraphvizRef (Maybe t) Bool -> Params- -> IO Layout-viewTagWidgets p gvRef config =- do viewTagBtn <- button p [ text := "ViewTAG" ]- viewTagCom <- choice p [ tooltip := "derivation tree" ]- -- handlers- let onViewTag = readIORef gvRef >>=- gvOnSelect (return ())- (\t -> do let derv = getSourceTrees t- ds <- get viewTagCom selection- if boundsCheck ds derv- then runViewTag config (derv !! ds)- else geniBug $ "Gui: bounds check in onViewTag"- )- set viewTagBtn [ on command := onViewTag ]- -- when the user selects a tree, we want to update the list of derivations- let updateDerivationList = gvOnSelect- (set viewTagCom [ enabled := False ])- (\s -> set viewTagCom [ enabled := True- , items := getSourceTrees s- , selection := 0] )- addGvHandler gvRef updateDerivationList- updateDerivationList =<< readIORef gvRef- --- return $ row 5 $ map dynamic [ widget viewTagCom, widget viewTagBtn ]--runViewTag :: Params -> String -> IO ()-runViewTag params drName =- case getFlagP MacrosFlg params of- Nothing -> ePutStrLn "Warning: No macros files specified (runViewTag)"- Just f -> do- -- figure out what grammar file to use- let gramfile = basename f ++ ".rec"- treenameOnly = takeWhile (/= ':') . dropTillIncluding ':' . dropTillIncluding ':'- -- run the viewer- case getFlagP ViewCmdFlg params of- Nothing -> ePutStrLn "Warning: No viewcmd specified (runViewTag)"- Just c -> do -- run the viewer- runProcess c [gramfile, treenameOnly drName]- Nothing Nothing Nothing Nothing Nothing- return ()-\end{code}--% ---------------------------------------------------------------------\section{Graphical debugger}-\label{sec:debugger_helpers}-% ----------------------------------------------------------------------All GenI builders can make use of an interactive graphical debugger. In-this section, we provide some helper code to build such a debugger. --\paragraph{pauseOnLexGui} sometimes it is useful for the user to see the-lexical selection only and either dump it to file or read replace it by-the contents of some other file. We provide an optional wrapper around-\fnref{candidateGui} which adds this extra functionality. The wrapper-also includes a "Begin" button which runs your continuation on the new-lexical selection.--\begin{code}-pauseOnLexGui :: ProgState -> (Window a) -> [TagElem] -> Sem -> [ILexEntry]- -> ([TagElem] -> IO ()) -- ^ continuation- -> GvIO Bool (Maybe TagElem)-pauseOnLexGui pst f xs missedSem missedLex job = do- p <- panel f []- candV <- varCreate xs- (tb, ref, updater) <- candidateGui pst p xs missedSem missedLex- -- supplementary button bar- let saveCmd =- do c <- varGet candV- let cStr = unlines $ map geniShow c- maybeSaveAsFile f cStr- loadCmd =- do let filetypes = [("Any file",["*","*.*"])]- fsel <- fileOpenDialog f False True "Choose your file..." filetypes "" ""- case fsel of- Nothing -> return ()- Just file ->- do parsed <- parseFromFile geniTagElems file- case parsed of- Left err -> errorDialog f "" (show err)- Right c -> do varSet candV c- setGvDrawables2 ref (unzip $ sectionsBySem c)- updater- --- saveBt <- button p [ text := "Save to file", on command := saveCmd ]- loadBt <- button p [ text := "Load from file", on command := loadCmd ]- nextBt <- button p [ text := "Begin" ]- let disableW w = set w [ enabled := False ]- set nextBt [ on command := do mapM disableW [ saveBt, loadBt, nextBt ]- varGet candV >>= job ]- --- let lay = fill $ container p $ column 5- [ fill tb, hfill (vrule 1)- , row 0 [ row 5 [ widget saveBt, widget loadBt ]- , hfloatRight $ widget nextBt ] ]- return (lay, ref, updater)-\end{code}--\paragraph{debuggerTab} is potentially the most useful part of the-debugger. It shows you the contents of chart, agenda and other-such structures used during the actual surface realisation process.-This may be a bit complicated to use because there is lots of extra-stuff you need to pass in order to parameterise the whole deal.--The function \fnreflite{debuggerTab} fills the parent window with the-standard components of a graphical debugger:-\begin{itemize}-\item An item viewer which allows the user to select one of the items- in the builder state.-\item An item bar which provides some options on how to view the - currently selected item, for example, if you want to display the- features or not. -\item A dashboard which lets the user do things like ``go ahead 6- steps''.-\end{itemize}--See the API for more details.--\begin{code}-type DebuggerItemBar flg itm - = (Panel ()) -- ^ parent panel- -> GraphvizRef (Maybe itm) flg - -- ^ gv ref to use- -> GvUpdater -- ^ updaterFn- -> IO Layout---- | A generic graphical debugger widget for GenI--- --- Besides the Builder, there are two functions you need to pass in make this--- work: ------ 1. a 'stateToGv' which converts the builder state into a list of items--- and labels the way 'graphvizGui' likes it------ 2. an 'item bar' function which lets you control what bits you display--- of a selected item (for example, if you want a detailed view or not)--- the item bar should return a layout ------ Note that we don't constrain the type of item returned by the builder to--- be the same as the type handled by your gui: that's quite normal because--- you might want to decorate the type with some other information-debuggerPanel :: (GraphvizShow flg itm) - => B.Builder st itm2 Params -- ^ builder to use- -> flg -- ^ initial value for the flag argument in GraphvizShow- -> (st -> ([Maybe itm], [String])) - -- ^ function to convert a Builder state into lists of items- -- and their labels, the way graphvizGui likes it- -> (DebuggerItemBar flg itm)- -- ^ 'itemBar' function returning a control panel configuring- -- how you want the currently selected item in the debugger- -- to be displayed- -> (Window a) -- ^ parent window- -> Params -- ^ geni params- -> B.Input -- ^ builder input- -> String -- ^ graphviz cache directory- -> IO Layout -debuggerPanel builder gvInitial stateToGv itemBar f config input cachedir = - do let initBuilder = B.init builder - nextStep = B.step builder - allSteps = B.stepAll builder - --- let (initS, initStats) = initBuilder input config2- config2 = setFlagP MetricsFlg (B.defaultMetricNames) config- (theItems,labels) = stateToGv initS- p <- panel f [] - -- ---------------------------------------------------------- -- item viewer: select and display an item- -- ---------------------------------------------------------- gvRef <- newGvRef gvInitial labels "debugger session" - setGvDrawables gvRef theItems- (layItemViewer,_,updaterFn) <- graphvizGui p cachedir gvRef- -- ----------------------------------------------------------- -- item bar: controls for how an individual item is displayed- -- ----------------------------------------------------------- layItemBar <- itemBar p gvRef updaterFn- -- ------------------------------------------- - -- dashboard: controls for the debugger itself - -- ------------------------------------------- - db <- panel p []- restartBt <- button db [text := "Start over"]- nextBt <- button db [text := "Leap by..."]- leapVal <- entry db [ text := "1", clientSize := sz 30 25 ]- finishBt <- button db [text := "Continue"]- statsTxt <- staticText db []- -- dashboard commands- let showQuery c gs = case queryCounter c gs of- Nothing -> "???"- Just q -> show q- updateStatsTxt gs = set statsTxt [ text :~ (\_ -> txtStats gs) ]- txtStats gs = "itr " ++ (showQuery num_iterations gs) ++ " "- ++ "chart sz: " ++ (showQuery chart_size gs)- ++ "\ncomparisons: " ++ (showQuery num_comparisons gs)- let genStep _ (st,stats) = runState (execStateT nextStep st) stats- let showNext s_stats = - do leapTxt <- get leapVal text- let leapInt :: Integer- leapInt = read leapTxt- (s2,stats2) = foldr genStep s_stats [1..leapInt]- setGvDrawables2 gvRef (stateToGv s2)- setGvSel gvRef 1- updaterFn- updateStatsTxt stats2- set nextBt [ on command :~ (\_ -> showNext (s2,stats2) ) ]- let showLast = - do -- redo generation from scratch- let (s2, stats2) = runState (execStateT allSteps initS) initStats - setGvDrawables2 gvRef (stateToGv s2)- updaterFn- updateStatsTxt stats2- let showReset = - do set nextBt [ on command := showNext (initS, initStats) ]- updateStatsTxt initStats - setGvDrawables2 gvRef (stateToGv initS)- setGvSel gvRef 1- updaterFn- -- dashboard handlers- set finishBt [ on command := showLast ]- set restartBt [ on command := showReset ]- showReset- -- dashboard layout - let layCmdBar = hfill $ container db $ row 5- [ widget statsTxt, hfloatRight $ row 5 - [ widget restartBt, widget nextBt - , widget leapVal, label " step(s)"- , widget finishBt ] ]- -- ------------------------------------------- - -- overall layout- -- ------------------------------------------- - return $ fill $ container p $ column 5 [ layItemViewer, layItemBar, hfill (vrule 1), layCmdBar ] -\end{code}--% ---------------------------------------------------------------------\section{Graphviz GUI}-\label{sec:graphviz_gui}-% ----------------------------------------------------------------------A general-purpose GUI for displaying a list of items graphically via-AT\&T's excellent Graphviz utility. We have a list box where we display-all the labels the user provided. If the user selects an entry from-this box, then the item corresponding to that label will be displayed.-See section \ref{sec:draw_item}.--\paragraph{gvRef}--We use IORef as a way to keep track of the gui state and to provide you-the possibility for modifying the contents of the GUI. The idea is that --\begin{enumerate}-\item you create a GvRef with newGvRef-\item you call graphvizGui and get back an updater function-\item whenever you want to modify something, you use setGvWhatever- and call the updater function-\item if you want to react to the selection being changed,- you should set gvhandler-\end{enumerate}--\begin{code}-data GraphvizOrder = GvoParams | GvoItems | GvoSel - deriving Eq-data GraphvizGuiSt a b = - GvSt { gvitems :: Map.Map Int a,- gvparams :: b,- gvlabels :: [String],- -- tooltip for the selection box- gvtip :: String, - -- handler function to call when the selection is- -- updated (note: before displaying the object)- gvhandler :: Maybe (GraphvizGuiSt a b -> IO ()),- gvsel :: Int,- gvorders :: [GraphvizOrder] }-type GraphvizRef a b = IORef (GraphvizGuiSt a b)--newGvRef :: forall a . forall b . b -> [String] -> String -> IO (GraphvizRef a b)-newGvRef p l t =- let st = GvSt { gvparams = p,- gvitems = Map.empty,- gvlabels = l, - gvhandler = Nothing,- gvtip = t,- gvsel = 0,- gvorders = [] }- in newIORef st--setGvSel :: GraphvizRef a b -> Int -> IO ()-setGvSel gvref s =- do let fn x = x { gvsel = s,- gvorders = GvoSel : (gvorders x) }- modifyIORef gvref fn - -setGvParams :: GraphvizRef a b -> b -> IO ()-setGvParams gvref c =- do let fn x = x { gvparams = c,- gvorders = GvoParams : (gvorders x) }- modifyIORef gvref fn --modifyGvParams :: GraphvizRef a b -> (b -> b) -> IO ()-modifyGvParams gvref fn =- do gvSt <- readIORef gvref- setGvParams gvref (fn $ gvparams gvSt)--setGvDrawables :: GraphvizRef a b -> [a] -> IO ()-setGvDrawables gvref it =- do let fn x = x { gvitems = Map.fromList $ zip [0..] it,- gvorders = GvoItems : (gvorders x) }- modifyIORef gvref fn --setGvDrawables2 :: GraphvizRef a b -> ([a],[String]) -> IO ()-setGvDrawables2 gvref (it,lb) =- do let fn x = x { gvlabels = lb }- modifyIORef gvref fn - setGvDrawables gvref it---- | Helper function for making selection handlers (see 'addGvHandler')--- Note that this was designed for cases where the contents is a Maybe-gvOnSelect :: IO () -> (a -> IO ()) -> GraphvizGuiSt (Maybe a) b -> IO ()-gvOnSelect onNothing onJust gvSt =- let sel = gvsel gvSt- things = gvitems gvSt- in case Map.lookup sel things of- Just (Just s) -> onJust s- _ -> onNothing--setGvHandler :: GraphvizRef a b -> Maybe (GraphvizGuiSt a b -> IO ()) -> IO ()-setGvHandler gvref mh =- do gvSt <- readIORef gvref- modifyIORef gvref (\x -> x { gvhandler = mh })- case mh of - Nothing -> return ()- Just fn -> fn gvSt---- | add a selection handler - if there already is a handler--- this handler will be called before the new one-addGvHandler :: GraphvizRef a b -> (GraphvizGuiSt a b -> IO ()) -> IO ()-addGvHandler gvref h =- do gvSt <- readIORef gvref- let newH = case gvhandler gvSt of - Nothing -> Just h- Just oldH -> Just (\g -> oldH g >> h g)- setGvHandler gvref newH-\end{code}--\paragraph{graphvizGui} returns a layout (wxhaskell container) and a-function for updating the contents of this GUI.--Arguments:-\begin{enumerate}-\item f - (parent window) the GUI is provided as a panel within the parent.- Note: we use window in the WxWidget's sense, meaning it could be- anything as simple as a another panel, or a notebook tab.-\item glab - (gui labels) a tuple of strings (tooltip, next button text)-\item cachedir - the cache subdirectory. We intialise this by creating a cache- directory for images which will be generated from the results-\item gvRef - see above-\end{enumerate}--Returns: a function for updating the GUI. FIXME: it's not entirely clear-what the updater function is for; note that it's not the same as the -handler function!--\begin{code}-graphvizGui :: (GraphvizShow f d) => - (Window a) -> String -> GraphvizRef d f -> GvIO f d-type GvIO f d = IO (Layout, GraphvizRef d f, GvUpdater)-type GvUpdater = IO ()--graphvizGui f cachedir gvRef = do- initGvSt <- readIORef gvRef- -- widgets- p <- panel f [ fullRepaintOnResize := False ]- split <- splitterWindow p []- (dtBitmap,sw) <- scrolledBitmap split - rchoice <- singleListBox split [tooltip := gvtip initGvSt]- -- set handlers- let openFn = openImage sw dtBitmap - -- pack it all together- let lay = fill $ container p $ margin 1 $ fill $ - vsplit split 5 200 (widget rchoice) (widget sw) - set p [ on closing := closeImage dtBitmap ]- -- bind an action to rchoice- let showItem = do createAndOpenImage cachedir p gvRef openFn- `catch` \e -> errorDialog f "" (show e)- ------------------------------------------------- -- create an updater function- ------------------------------------------------- let updaterFn = do - gvSt <- readIORef gvRef- let orders = gvorders gvSt - labels = gvlabels gvSt- sel = gvsel gvSt- initCacheDir cachedir - Monad.when (GvoItems `elem` orders) $ - set rchoice [ items :~ (\_ -> labels) ]- Monad.when (GvoSel `elem` orders) $- set rchoice [ selection :~ (\_ -> sel) ]- modifyIORef gvRef (\x -> x { gvorders = []})- -- putStrLn "updaterFn called" - showItem - ------------------------------------------------- -- enable the tree selector- -- FIXME: not sure that this is correct- ------------------------------------------------- let selectAndShow = do- -- putStrLn "selectAndShow called" - sel <- get rchoice selection- -- note: do not use setGvSel (infinite loop)- modifyIORef gvRef (\x -> x { gvsel = sel })- -- call the handler if there is one - gvSt <- readIORef gvRef- case (gvhandler gvSt) of - Nothing -> return ()- Just h -> h gvSt- -- now do the update- updaterFn- ------------------------------------------------- set rchoice [ on select := selectAndShow ]- -- call the updater function for the first time- -- setGvSel gvRef 1- updaterFn - -- return the layout, the gvRef, and an updater function- -- The gvRef is to make it easier for users to muck around with the- -- state of the gui. Here, it's trivial, but when people combine guis- -- together, it might be easier to keep track of when returned- return (lay, gvRef, updaterFn)-\end{code}--\subsection{Scroll bitmap}--Bitmap with a scrollbar--\begin{code}-scrolledBitmap :: Window a -> IO(VarBitmap, ScrolledWindow ())-scrolledBitmap p = do- dtBitmap <- variable [value := Nothing]- sw <- scrolledWindow p [scrollRate := sz 10 10, bgcolor := white,- on paint := onPaint dtBitmap,- fullRepaintOnResize := False ] - return (dtBitmap, sw)-\end{code}--\subsection{Bitmap functions}--The following helper functions were taken directly from the WxHaskell-sample code.--\begin{code}-type OpenImageFn = FilePath -> IO ()-type VarBitmap = Var (Maybe (Bitmap ())) --openImage :: Window a -> VarBitmap -> OpenImageFn-openImage sw vbitmap fname = do - -- load the new bitmap- bm <- bitmapCreateFromFile fname -- can fail with exception- closeImage vbitmap- set vbitmap [value := Just bm]- -- reset the scrollbars - bmsize <- get bm size - set sw [virtualSize := bmsize]- repaint sw- `catch` \_ -> repaint sw--closeImage :: VarBitmap -> IO ()-closeImage vbitmap = do - mbBitmap <- swap vbitmap value Nothing- case mbBitmap of- Nothing -> return ()- Just bm -> objectDelete bm--onPaint :: VarBitmap -> DC a -> b -> IO ()-onPaint vbitmap dc _ = do - mbBitmap <- get vbitmap value- case mbBitmap of- Nothing -> return () - Just bm -> do dcClear dc- drawBitmap dc bm pointZero False []-\end{code}--\subsection{Drawing stuff}-\label{sec:draw_item}--\paragraph{createAndOpenImage} Attempts to draw an image -(or retrieve it from cache) and opens it if we succeed. Otherwise, it-does nothing at all; the creation function will display an error message-if it fails.--\begin{code}-createAndOpenImage :: (GraphvizShow f b) => - FilePath -> Window a -> GraphvizRef b f -> OpenImageFn -> IO ()-createAndOpenImage cachedir f gvref openFn = do - let errormsg g = "The file " ++ g ++ " was not created!\n"- ++ "Is graphviz installed?"- r <- createImage cachedir f gvref - case r of - Just graphic -> do exists <- doesFileExist graphic - if exists - then openFn graphic- else fail (errormsg graphic)- Nothing -> return ()---- | Creates a graphical visualisation for anything which can be displayed--- by graphviz.-createImage :: (GraphvizShow f b)- => FilePath -- ^ cache directory- -> Window a -- ^ parent window- -> GraphvizRef b f -- ^ stuff to display- -> IO (Maybe FilePath)-createImage cachedir f gvref = do- gvSt <- readIORef gvref- -- putStrLn $ "creating image via graphviz"- let drawables = gvitems gvSt- sel = gvsel gvSt- config = gvparams gvSt- dotFile <- createDotPath cachedir (show sel)- graphicFile <- createImagePath cachedir (show sel)- let create x = do toGraphviz config x dotFile graphicFile- return (Just graphicFile)- handler err = do errorDialog f "Error calling graphviz" (show err) - return Nothing- exists <- doesFileExist graphicFile- -- we only call graphviz if the image is not in the cache- if exists- then return (Just graphicFile)- else case Map.lookup sel drawables of- Nothing -> return Nothing- Just it -> create it `catch` handler-\end{code}--\subsection{Cache directory}--We create a directory to put image files in so that we can avoid regenerating-images. If the directory already exists, we can just delete all the files-in it.--\begin{code}-initCacheDir :: String -> IO()-initCacheDir cachesubdir = do - mainCacheDir <- gv_CACHEDIR- cmainExists <- doesDirectoryExist mainCacheDir - Monad.when (not cmainExists) $ createDirectory mainCacheDir - -- - let cachedir = mainCacheDir /// cachesubdir- cExists <- doesDirectoryExist cachedir- if (cExists)- then do let notdot x = (x /= "." && x /= "..")- contents <- getDirectoryContents cachedir- olddir <- getCurrentDirectory- setCurrentDirectory cachedir- mapM removeFile $ filter notdot contents- setCurrentDirectory olddir- return ()- else createDirectory cachedir-\end{code}--\section{Miscellaneous}-\label{sec:gui_misc}--\begin{code}--- | Save the given string to a file, if the user selets one via the file save--- dialog. Otherwise, don't do anything.-maybeSaveAsFile :: (Window a) -> String -> IO ()-maybeSaveAsFile f msg =- do let filetypes = [("Any file",["*","*.*"])]- fsel <- fileSaveDialog f False True "Save to" filetypes "" ""- case fsel of- Nothing -> return ()- Just file -> writeFile file msg---- | A message panel for use by the Results gui panels.-messageGui :: (Window a) -> String -> IO Layout -messageGui f msg = do - p <- panel f []- -- sw <- scrolledWindow p [scrollRate := sz 10 10 ]- t <- textCtrl p [ text := msg, enabled := False ]- return (fill $ container p $ column 1 $ [ fill $ widget t ]) -\end{code}--\begin{code}-gv_CACHEDIR :: IO String-gv_CACHEDIR = do- home <- getHomeDirectory- return $ home /// ".gvcache"--createImagePath :: String -> String -> IO String-createImagePath subdir name = do- cdir <- gv_CACHEDIR- return $ cdir /// subdir /// name ++ ".png"--createDotPath :: String -> String -> IO String-createDotPath subdir name = do - cdir <- gv_CACHEDIR- return $ cdir /// subdir /// name ++ ".dot"-\end{code}--
− NLP/GenI/Simple/SimpleGui.lhs
@@ -1,201 +0,0 @@-% GenI surface realiser-% Copyright (C) 2005 Carlos Areces and Eric Kow-%-% This program is free software; you can redistribute it and/or-% modify it under the terms of the GNU General Public License-% as published by the Free Software Foundation; either version 2-% of the License, or (at your option) any later version.-%-% This program is distributed in the hope that it will be useful,-% but WITHOUT ANY WARRANTY; without even the implied warranty of-% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the-% GNU General Public License for more details.-%-% You should have received a copy of the GNU General Public License-% along with this program; if not, write to the Free Software-% Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.--\chapter{Simple GUI}--\begin{code}-{-# OPTIONS_GHC -fno-warn-orphans #-}-module NLP.GenI.Simple.SimpleGui where-\end{code}--\ignore{-\begin{code}-import Graphics.UI.WX-import Graphics.UI.WXCore--import Data.IORef-import qualified Data.Map as Map--import NLP.GenI.Statistics (Statistics)--import NLP.GenI.Btypes (GNode(gnname, gup), emptyGNode, GeniVal(GConst))-import NLP.GenI.Configuration ( Params(..) )-import NLP.GenI.General ( snd3 )-import NLP.GenI.Geni ( ProgStateRef, runGeni, GeniResult )-import NLP.GenI.Graphviz ( GraphvizShow(..), gvNewline, gvUnlines )-import NLP.GenI.GuiHelper- ( messageGui, tagViewerGui,- debuggerPanel, DebuggerItemBar, setGvParams, GvIO, newGvRef,- viewTagWidgets, XMGDerivation(getSourceTrees),- )-import NLP.GenI.Tags (tsemantics, TagElem(idname, ttree), TagItem(..), emptyTE)-import NLP.GenI.GraphvizShow ( graphvizShowDerivation )--import qualified NLP.GenI.Builder as B-import qualified NLP.GenI.BuilderGui as BG-import NLP.GenI.Polarity-import NLP.GenI.Simple.SimpleBuilder- ( simpleBuilder, SimpleStatus, SimpleItem(..), SimpleGuiItem(..)- , unpackResult- , theResults, theAgenda, theAuxAgenda, theChart, theTrash)-\end{code}-}--% ---------------------------------------------------------------------\section{Interface}-% ----------------------------------------------------------------------\begin{code}-simpleGui_2p, simpleGui_1p :: BG.BuilderGui-simpleGui_2p = simpleGui True-simpleGui_1p = simpleGui False--simpleGui :: Bool -> BG.BuilderGui-simpleGui twophase = BG.BuilderGui {- BG.resultsPnl = resultsPnl twophase- , BG.debuggerPnl = simpleDebuggerTab twophase }--resultsPnl :: Bool -> ProgStateRef -> Window a -> IO ([GeniResult], Statistics, Layout)-resultsPnl twophase pstRef f =- do (sentences, stats, st) <- runGeni pstRef (simpleBuilder twophase)- (lay, _, _) <- realisationsGui pstRef f (theResults st)- return (sentences, stats, lay)-\end{code}--% ---------------------------------------------------------------------\section{Results}-\label{sec:results_gui}-% ----------------------------------------------------------------------\subsection{Derived Trees}--Browser for derived/derivation trees, except if there are no results, we show a-message box--\begin{code}-realisationsGui :: ProgStateRef -> (Window a) -> [SimpleItem]- -> GvIO Bool (Maybe SimpleItem)-realisationsGui _ f [] =- do m <- messageGui f "No results found"- g <- newGvRef False [] ""- return (m, g, return ())-realisationsGui pstRef f resultsRaw =- do let tip = "result"- itNlabl = map (\t -> (Just t, siToSentence t)) resultsRaw- --- pst <- readIORef pstRef- -- FIXME: have to show the semantics again- tagViewerGui pst f tip "derived" itNlabl-\end{code}--% ---------------------------------------------------------------------\section{Debugger}-\label{sec:simple_debugger_gui}-\label{fn:simpleDebugGui}-% ----------------------------------------------------------------------\begin{code}-simpleDebuggerTab :: Bool -> (Window a) -> Params -> B.Input -> String -> IO Layout-simpleDebuggerTab twophase x1 (pa@x2) =- debuggerPanel (simpleBuilder twophase) False stToGraphviz (simpleItemBar pa)- x1 x2- -stToGraphviz :: SimpleStatus -> ([Maybe SimpleItem], [String])-stToGraphviz st = - let agenda = section "AGENDA" $ theAgenda st- auxAgenda = section "AUXILIARY" $ theAuxAgenda st- trash = section "TRASH" $ theTrash st- chart = section "CHART" $ theChart st- results = section "RESULTS" $ theResults st- --- section n i = hd : (map tlFn i)- where hd = (Nothing, "___" ++ n ++ "___")- tlFn x = (Just x, siToSentence x ++ (showPaths $ siPolpaths x))- showPaths t = " (" ++ showPolPaths t ++ ")"- in unzip $ agenda ++ auxAgenda ++ chart ++ trash ++ results --simpleItemBar :: Params -> DebuggerItemBar Bool SimpleItem-simpleItemBar pa f gvRef updaterFn =- do ib <- panel f []- detailsChk <- checkBox ib [ text := "Show features"- , checked := False ]- viewTagLay <- viewTagWidgets ib gvRef pa- -- handlers- let onDetailsChk = - do isDetailed <- get detailsChk checked - setGvParams gvRef isDetailed- updaterFn- set detailsChk [ on command := onDetailsChk ]- --- return . hfloatCentre . (container ib) . row 5 $- [ hspace 5- , widget detailsChk- , hglue- , viewTagLay- , hspace 5 ]-\end{code}--% ---------------------------------------------------------------------\section{Miscellaneous}-% ---------------------------------------------------------------------\begin{code}-instance TagItem SimpleItem where- tgIdName = siIdname.siGuiStuff- tgIdNum = siId- tgSemantics = siFullSem.siGuiStuff--instance XMGDerivation SimpleItem where- -- Note: this is XMG-related stuff- getSourceTrees it = tgIdName it : (map snd3 . siDerivation $ it)-\end{code}--\begin{code}-instance GraphvizShow Bool SimpleItem where- graphvizLabel f c =- graphvizLabel f (toTagElem c) ++ gvNewline ++ (gvUnlines $ siDiagnostic $ siGuiStuff c)-- graphvizParams f c = graphvizParams f (toTagElem c)- graphvizShowAsSubgraph f p it =- let isHiglight n = gnname n `elem` (siHighlight.siGuiStuff) it- info n | isHiglight n = (n, Just "red")- | otherwise = (n, Nothing)- in "\n// ------------------- elementary tree --------------------------\n"- ++ graphvizShowAsSubgraph (f, info) (p ++ "TagElem") (toTagElem it)- ++ "\n// ------------------- derivation tree --------------------------\n"- -- derivation tree is displayed without any decoration- ++ (graphvizShowDerivation . siDerivation $ it)--toTagElem :: SimpleItem -> TagElem-toTagElem si =- emptyTE { idname = tgIdName si- , tsemantics = tgSemantics si- , ttree = fmap lookupOrBug (siDerived si) }- where- nodes = siNodes.siGuiStuff $ si- nodeMap = Map.fromList $ zip (map gnname nodes) nodes- lookupOrBug k = case Map.lookup k nodeMap of- Nothing -> emptyGNode { gup = [ ("cat",GConst ["error looking up " ++ k]) ] }- Just x -> x-\end{code}--\begin{code}-siToSentence :: SimpleItem -> String-siToSentence si = case unpackResult si of- [] -> siIdname.siGuiStuff $ si- (h:_) -> unwords . map fst . fst $ h-\end{code}
− NLP/GenI/SysGeni.lhs
@@ -1,110 +0,0 @@-% GenI surface realiser-% Copyright (C) 2005 Carlos Areces and Eric Kow-%-% This program is free software; you can redistribute it and/or-% modify it under the terms of the GNU General Public License-% as published by the Free Software Foundation; either version 2-% of the License, or (at your option) any later version.-%-% This program is distributed in the hope that it will be useful,-% but WITHOUT ANY WARRANTY; without even the implied warranty of-% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the-% GNU General Public License for more details.-%-% You should have received a copy of the GNU General Public License-% along with this program; if not, write to the Free Software-% Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.--\chapter{SysGeni}--The SysGeni module mainly exists for running GenI as an application bundle-under MacOS X. We mostly re-export stuff from System.Process, but if we -are in a MacOS X application bundle, then we add \verb!../Resources/bin!-to the path for all the random crap that we ship with with GenI.--\begin{code}-{-# LANGUAGE ForeignFunctionInterface #-}-module NLP.GenI.SysGeni-where-\end{code}--\ignore{-\begin{code}-import qualified System.Process as S--#ifdef darwin_TARGET_OS -import Data.List(isSuffixOf)-import NLP.GenI.General((///))-#endif--import System.IO (Handle)-import System.Exit (ExitCode)--#ifdef __GLASGOW_HASKELL__-import Foreign-import Foreign.C-import Control.Monad-#include "ghcconfig.h"-#endif-\end{code}-}--\section{Running a process}--\begin{code}-waitForProcess :: S.ProcessHandle -> IO ExitCode-waitForProcess = S.waitForProcess-\end{code}--But one thing special we need to do for Macs is to detect if we're-running from an application bundle. If we are, we assume that any-processes we want to run are in \texttt{../Resources/bin}.--\begin{code}-runInteractiveProcess :: String -> [String]- -> Maybe FilePath- -> Maybe [(String, String)]- -> IO (Handle, Handle, Handle, S.ProcessHandle)-#ifdef darwin_TARGET_OS -runInteractiveProcess cmd args x y = do- dirname <- getProgDirName- -- detect if we're in an .app bundle, i.e. if - -- we are running from something.app/Contents/MacOS- let appBundle = ".app/Contents/MacOS/"- resBinCmd = "../Resources/bin" /// cmd- -- if we're in an .app bundle, we should prefix the- -- path with ../Resources/bin- let cmd2 = if appBundle `isSuffixOf` dirname - then resBinCmd else cmd- S.runInteractiveProcess cmd2 args x y -#else --- if not on a Mac-runInteractiveProcess = S.runInteractiveProcess-#endif-\end{code}--\paragraph{Process helpers}--\begin{code}-foreign import ccall unsafe "getProgArgv"- getProgArgv :: Ptr CInt -> Ptr (Ptr CString) -> IO ()--getProgDirName :: IO String-getProgDirName = - alloca $ \ p_argc ->- alloca $ \ p_argv -> do- getProgArgv p_argc p_argv- argv <- peek p_argv- s <- peekElemOff argv 0 >>= peekCString- return $ dirname s- where- dirname :: String -> String- dirname f = reverse $ dropWhile (not.isPathSeparator) $ reverse f- isPathSeparator :: Char -> Bool- isPathSeparator '/' = True-#ifdef mingw32_TARGET_OS - isPathSeparator '\\' = True-#endif- isPathSeparator _ = False-\end{code}-
− NLP/GenI/unused/Predictors.lhs
@@ -1,315 +0,0 @@-% GenI surface realiser-% Copyright (C) 2005 Carlos Areces and Eric Kow-%-% This program is free software; you can redistribute it and/or-% modify it under the terms of the GNU General Public License-% as published by the Free Software Foundation; either version 2-% of the License, or (at your option) any later version.-%-% This program is distributed in the hope that it will be useful,-% but WITHOUT ANY WARRANTY; without even the implied warranty of-% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the-% GNU General Public License for more details.-%-% You should have received a copy of the GNU General Public License-% along with this program; if not, write to the Free Software-% Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.--\chapter{Predictor Optimisation}--One optimisation is to annotate the macros with a set of-\jargon{predictors}. This allows macros to predict that they will-combine with certain (usually) null-semantic trees. For example, a-common noun would predict that it needs a determiner. --\begin{code}-module Predictors -where-\end{code}--\begin{code}-import Debug.Trace-import qualified Data.Map as Map-import Data.List (nub, sortBy, groupBy, intersect)-import Monad (when, ap, foldM)-import MonadState (get, put)--import Bfuncs (Sem, Flist, AvPair, showSem, showAv, isVar)-import Tags (TagElem(TE), emptyTE, idname, tsemantics, substnodes, - derivation, tpredictors, drawTagTrees)-import Configuration (defaultParams)-import Mstate (MS, Gstats, initGstats, addGstats, initMState,- runState, genstats, - incrNumcompar, incrSzchart, incrGeniter,- renameTagElem,- addToInitRep, - getGenRep, lookupGenRep, genRepToList, addListToGenRep,- iapplySubstNode,- nullInitRep, getInitRep, genrep, getSem, selectGiven)--import Polarity (showLite)-\end{code}--% -----------------------------------------------------------------------\section{Optimisation}-% ------------------------------------------------------------------------We attempt substitution between macro and any predictors that it has.-Whenever we succeed, we can pass the combined tree as a candidate.-Whenever we fail, we have to pass both the macro and its predictors.-This is basically an indirect means of adding some kind of indexing to-the generator's chart.--Note: there are actually two cases here. For those predictors that-we can substitute into the macro, we return the resulting tree and-discard the predictor. --\begin{code}-optimisePredictors :: [[TagElem]] -> PredictorMap -> ([[TagElem]], Gstats)-optimisePredictors cands predictmap =- let trees = nub $ concat cands - -- calculate predicted trees- sumup = foldr addGstats initGstats - optTree t = optimisePredictors' predictmap t- optPath p = (r, sumup s)- where (r,s) = unzip $ map optTree p - (res, stats) = optPath trees- treemap = Map.fromList $ zip trees res- -- replace trees with their predicted equivalents- repTree t = lookupWithDefaultFM treemap [t] t - repPath p = concatMap repTree p- {- repPath p = trace ("\n==============\npath\n=============\n" ++ drawTagTrees l) l- where l = repPath' p -}- in (map repPath cands, stats)-\end{code}--\paragraph{optimisePredictors'} is a helper function that tries to-fulfill as many of a tree's predictors as possible. Any predictors-it cannot use are also returned so that they can be passed to the -generator proper.--\begin{code}-optimisePredictors' :: PredictorMap -> TagElem -> ([TagElem], Gstats)-optimisePredictors' predictmap te =- let -- grab the predictors (helper fns)- isneg _ e = e < 0 - predictors t = Map.keys $ filterFM isneg $ tpredictors t- ptrees t = concatMap fn (predictors t)- where fn = lookupWithDefaultFM predictmap []- -- generate- tePtrees = ptrees te- initSt = initMState tePtrees [te] (tsemantics te) defaultParams- (res', st) = runState miniGenerate initSt- -- pick the trees with the largest derivation history- derSz = length.snd.derivation- cmpDerSz t1 t2 = compare (derSz t2) (derSz t1) -- note the inversion - sameDerSz t1 t2 = (derSz t2) == (derSz t1) - groupedres = groupBy sameDerSz $ sortBy cmpDerSz res' - -- return the results- result = head groupedres -- trace ("\n==============\nresults for " ++ idname te ++ "\n=============\n" ++ drawTagTrees res) $ - rejects = concatMap ptrees result- stats = genstats st- --- debugstr = "\n===================" - ++ "\noptimising " ++ showLite te - ++ "\nptrees: " ++ showLite (tePtrees)- ++ "\n==================== "- errormsg = "Geni: Predictors.optimisePredictors' is broken"- in case () of _ | null tePtrees -> ([te], stats)- | null groupedres -> error errormsg - | otherwise -> (result ++ rejects, stats)-\end{code}--% -----------------------------------------------------------------------\subsection{miniGenerate}-% ------------------------------------------------------------------------miniGenerate is a lightweight version of the generator which operates -on the following principles: --\begin{enumerate}-\item There is one primary tree (chart) and some secondary trees - (agenda), which should not be confused with auxiliary trees-\item All operations are performed between the primary- tree and the secondary trees, that is, you won't- have any interaction between secondary trees-\item The primary tree may substitute with or be - substituted any number of secondary trees-\item Secondary trees may only be used once-\end{enumerate}--It is used as a helper function for optimisePredictors. --\begin{code}-miniGenerate :: MS [TagElem]-miniGenerate = do - nir <- nullInitRep- gr <- getGenRep- if nir then return (concat $ elems gr) else do - incrGeniter 1- tsem <- getSem- -- choose a secondary tree from the agenda- given <- selectGiven- -- perform any substitutions - chart <- lookupGenRep given - let (res', cost') = unzip $ map (timidSubstitution given) chart- res = concat res'- cost = foldr (+) 0 cost' - incrSzchart (length res)- incrNumcompar cost- -- add any succesful results to the chart- st <- get- put st { genrep = addListToGenRep gr res }- miniGenerate-\end{code}--\paragraph{timidSubstitution} attempts to perform substitution between-input trees $te_1$ and $te_2$. This is meant strictly to be a helper-function for optimisePredictors, so we'll have a somewhat conservative-and quirky behaviour:-\begin{itemize}-\item If there are no ways to perform substitution, we return the empty-list-\item If there is exactly one way to perform substitution-(either $te_1$ into $te_2$ or vice versa), we-return that substitution. -\item If there is more than one way to do it, we return the empty list.-This is because the situation is ambiguous and could lead to unpredictable-results (see section \ref{sec:optimisePredictors_tricky})-\end{itemize}--This is somewhat similar to MState's applySubstitution, except that we-rule out the case of multiple results, and that we do not require the-substitution nodes to be in any particular order.--\begin{code}-timidSubstitution :: TagElem -> TagElem -> ([TagElem],Int)-timidSubstitution te1 te2 = - let tesem = tsemantics te1- -- we only substitute tags with no overlaping semantics- notOverlap = null $ intersect (tsemantics te2) tesem- -- we rename tags to do a proper substitution- rte1 = renameTagElem 'A' te1- rte2 = renameTagElem 'B' te2- -- perform the substitution- subst t1 t2 = concatMap (iapplySubstNode t1 t2) $ substnodes t2- res' = (subst rte1 rte2) ++ (subst rte2 rte1)- res = if (length res' == 1) then res' else []- -- measuring efficiency- cost = fn te1 + fn te2 - where fn t = length $ substnodes t - in if notOverlap then (res, cost) else ([], 0)-\end{code}--\subsection{Trickiness in optimisePredictors} -\label{sec:optimisePredictors_tricky}--Rejecting ambiguous substitutions is crucial to the idea that-secondary trees may only be used once.--Consider the trees for \textit{the N, enemy of N, friend}.-The idea is that we eventually want to generate \textit {the enemy of the-friend}, so the result of optimisePredictors should ideally be something like:-\textit{the friend, the enemy of N} --But this isn't so easy to achieve. In fact, if we tried to achieve-the above result, we would instead get a highly undesirable result -like this \textit{the friend, the enemy of the N} --Do you see why the above result is bad? It is because now there is-no way to substitute friend into that noun-substitution node. To-avoid this sort of over-ambitiousness, we avoid ambiguous cases where a -tree could both substitute into or be substituted into another. So we-get a less optimal, but much safer result \textit{the friend, enemy of, -the}:--% -----------------------------------------------------------------------\section{Cleanup}-% ------------------------------------------------------------------------\paragraph{fillPredictors} This is neccesary when either the-predictor optimisation is disabled or if there are some-predictor substitutions which do not succeed. It takes a list of paths-and inserts all required predictors on the paths.--\begin{code}-fillPredictors :: [[TagElem]] -> PredictorMap -> [[TagElem]]-fillPredictors paths predictmap =- let isneg _ pol = pol < 0 - getP = lookupWithDefaultFM predictmap []- predictors te = Map.keys $ filterFM isneg $ tpredictors te- addP te = te : (concatMap getP $ predictors te)- in map (\p -> nub $ concatMap addP p) paths-\end{code}--% ---------------------------------------------------------------------\section{Instatiation of predictors}-% ----------------------------------------------------------------------We combine the predictors from the lexicon and macros. The idea is-to do this in a way which lets the grammar writer be lazy while having-as simple and predictable a behaviour as possible. Any predictors that-the lexicon has must correspond to some variable predictor in the-macros, so if I say in the lexicon that a tree as predictor-$+vsup:avoir$ there had better be a $+vsup:X$ in the macros to back it-up.--\begin{code}-combinePredictors tt le = - let -- fn to add an item to the predictors map- addP (fv,c) fm = addToFM_C (+) fm fv c- -- lexicon predictors - lpr = sort $ ipredictors le- -- tree predictors (variable vs constant predictors)- tpr = sort $ ptpredictors tt- isVpr ((f,v),_) = (not $ null v) && isVar v- (varPr,constPr) = partition isVpr tpr- constPrFm = foldr addP Map.empty constPr- -- separating the charges from the fv- (lfv, lc) = unzip lpr - (vfv, vc) = unzip varPr- -- the unification- unify [] [] = []- unify ((tf,tv):tnext) ((lf,lv):lnext) - | tf /= lf = error errmsg- | isVar lv = error errvlex- | isVar tv = (lf,lv):(unify (substFlist' tnext (tv,lv)) lnext)- | lv == tv = (lf,lv):(unify tnext lnext)- | otherwise = error errmsg- unification = unify vfv lfv - -- error messages in case things don't line up- errmsg = "Word '" ++ (iword le) ++ "' does not correctly " - ++ " instantiate the variable predictors in tree " - ++ (itreename le) - ++ "\n Tree predictors: " ++ (show $ map fst varPr) - ++ "\n Word predictors: " ++ (show $ map fst lpr)- ++ "\n Hint: only the variable predictors should be instantiated" - errvlex = "Word '" ++ (iword le) ++ "' contains variable " - ++ " predictors in " ++ (show $ map fst lpr)- in if (lc == vc) -- note: this implies list equality- then foldr addP constPrFm $ zip unification lc - else error errmsg-\end{code}---% -----------------------------------------------------------------------\section{PredictorMap}-% ------------------------------------------------------------------------We create a map between predictors and the trees that provide them.--\begin{code}-type PredictorMap = Map AvPair [TagElem]-\end{code}--\begin{code}-mapByPredictors :: [TagElem] -> PredictorMap -mapByPredictors trees = foldr mapByPredictors' Map.empty trees --mapByPredictors' :: TagElem -> PredictorMap -> PredictorMap -mapByPredictors' tree fm = - let ispos _ pol = (pol > 0)- predictors = Map.keys $ filterFM ispos $ tpredictors tree- addp p f = addToFM_C (++) f p [tree]- in foldr addp fm predictors -\end{code}
+ README view
@@ -0,0 +1,24 @@+documentation+-------------+make doc # (pdflatex and haddock are needed)++1. users manual: + http://wiki.loria.fr/wiki/GenI+2. semi-literate source code: + doc/genidoc.pdf +3. API: + doc/api/index.html++installing GenI+---------------+(tested on Linux and MacOs X)++see INSTALL for details++assuming everything above is installed correctly,+it should possible to just make++contact us!+-----------+Please let us know if you are using GenI; we'd like to hear about your+experiences, both positive and negative.
Setup.lhs view
@@ -27,7 +27,7 @@ Normally, this should just be "macosx-app" > macosxApp :: String-> macosxApp = "macstuff/macosx-app"+> macosxApp = "etc/macstuff/macosx-app" Nothing to configure from here on ---------------------------------
+ etc/macstuff/Info.plist view
@@ -0,0 +1,36 @@+<?xml version="1.0" encoding="UTF-8"?>+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">+<plist version="1.0">+<dict>+ <key>CFBundleDevelopmentRegion</key>+ <string>English</string>+ <key>CFBundleExecutable</key>+ <string>geni</string>+ <key>CFBundleGetInfoString</key>+ <string>GenI unstable 0.8</string>+ <key>CFBundleIconFile</key>+ <string>wxmac.icns</string>+ <key>CFBundleIdentifier</key>+ <string>fr.loria.geni</string>+ <key>CFBundleInfoDictionaryVersion</key>+ <string>6.0</string>+ <key>CFBundleLongVersionString</key>+ <string>0.8, (c) 2005 LORIA</string>+ <key>CFBundleName</key>+ <string>GenI</string>+ <key>CFBundlePackageType</key>+ <string>APPL</string>+ <key>CFBundleShortVersionString</key>+ <string>0.8</string>+ <key>CFBundleSignature</key>+ <string>????</string>+ <key>CFBundleVersion</key>+ <string>0.8</string>+ <key>CSResourcesFileMapped</key>+ <true/>+ <key>LSRequiresCarbon</key>+ <true/>+ <key>NSHumanReadableCopyright</key>+ <string>Copyright 2005 LORIA</string>+</dict>+</plist>
+ etc/macstuff/macosx-app view
@@ -0,0 +1,114 @@+#!/bin/sh+icnsfile=etc/macstuff/wxmac.icns+infofile=etc/macstuff/Info.plist+bundlename=GenI+rezcomp="/Developer/Tools/Rez -t APPL Carbon.r $rezfile -o"++#------------------------------------------------------------------------+# Helper script to create a MacOS X application from a binary.+# Hacked up with lots of GenI-specific stuff.+# Meant to be run from the bin directory directly.+#+# Daan Leijen and Arthur Baars.+#+# Copyright (c) 2003,2004 Daan Leijen, Arthur Baars+#------------------------------------------------------------------------++# $Id: macosx-app-template,v 1.4 2005/04/29 14:16:51 dleijen Exp $+arg=""++# variables+program=""+verbose="yes"+++# Parse command-line arguments+while : ; do+ # put optional argument in the $arg variable+ case "$1" in+ -*=*) arg=`echo "$1" | sed 's/[-_a-zA-Z0-9]*=//'` ;;+ *) arg= ;;+ esac++ # match on the arguments+ case "$1" in+ "") break;;+ -\?|--help)+ echo "usage:"+ echo " macosx-app [options] <program (a.out)>"+ echo ""+ echo "options: [defaults in brackets]"+ echo " --help | -? show this information"+ echo " --verbose | -v be verbose"+ echo ""+ exit 1;;+ -v|--verbose)+ verbose="yes";;+ -*) echo "error: Unknown option \"$1\". Use \"--help\" to show valid options." 1>&2+ echo "" 1>&2+ exit 2;;+ *) if test "$program"; then+ echo "error: [program] is specified twice. Use \"--help\" to show valid options." 1>&2+ echo ""1>&2+ exit 2+ fi+ program="$1";;+ esac+ shift+done++# default program+if test -z "$program"; then+ echo "error: you need to specify a program. Use \"--help\" to show valid options." 1>&2+ echo "" 1>&2+ exit 2+fi++# show when verbose is true.+show()+{+ if test "$verbose" = "yes"; then + echo "$1"+ fi+}++# link with default resources+# this is neccesary only to run the GUI from the command line +if test "$rezcomp"; then+ show "creating resource:" + show " > $rezcomp $program"+ $rezcomp $program+fi++# create a bundle+bundle="$program.app/Contents"++# create bundle directories+show "creating app directories:"+show " - $program.app"+mkdir -p $program.app+show " - $bundle"+mkdir -p $bundle+show " - $bundle/MacOS"+mkdir -p $bundle/MacOS+show " - $bundle/Resources"+mkdir -p $bundle/Resources++cp -f $program $bundle/MacOS/++# copy the icon +cp -f ${icnsfile} $bundle/Resources++# package info+show "creating package info:"+show " - $bundle/PkgInfo"+echo -n "APPL????" > $bundle/PkgInfo++# create program information file+cp ${infofile} $bundle/Info.plist ++# tell finder that there's an icon +/Developer/Tools/SetFile -a C $bundle++show "done."+show ""
+ etc/macstuff/wxmac.icns view
binary file changed (absent → 46234 bytes)
+ examples/artificial/lexicon view
@@ -0,0 +1,56 @@+_a yi1 () +semantics: [a()]++_b er2 () +semantics: [b()]++_t1 term (t1 bar1)+semantics: [t1()]++_t2bar2 term (t2 bar2)+semantics: [t2()]++_t1 term (t1 ?X)+semantics: [t1(?X)]++_t2 term (t2 ?X)+semantics: [t2(?X)]++_xb aux-bad () +semantics: [xb()]++_xg aux-good () +semantics: [xg()]++ne aux-nepas () +semantics: [xnepas()]++_xg2 aux-good () +semantics: [xg2()]++_d wu5 () +semantics: [d()]++_d-b wu5-bad()+semantics: [db()]++_e liu6-good () semantics:[eg()]+_e-b1 liu6-bad1 () semantics:[eb1()]+_e-b2 liu6-bad2 () semantics:[eb2()]++whatever k2p-trivial () semantics:[k2p()]+iaf-g iaf-killer-good (?X) semantics:[iaf(?X)]++whatever aconstr-with-anchor () semantics:[aconstr-with-anchor()]++tb-unification-noadj tb-unification-noadj () semantics:[tb-unification-noadj()]+tb-unification-bot tb-unification-bot () semantics:[tb-unification-bot()]+tb-unification-adj tb-unification-adj () semantics:[tb-unification-adj()]++no-thing no-thing () semantics:[no-thing()]+thing-good thing-good () semantics:[thing-good()]+thing-bad thing-bad () semantics:[thing-bad()]++"string-literal-in-lemma" term () semantics:["string-lit'+!|"(foo)]++lemanchor lemanchor() semantics:[lemanchor()]
+ examples/artificial/macros view
@@ -0,0 +1,126 @@+% trivial example of the kid to parent rule+k2p-trivial() initial +Mother [cat:a]![] { Anchor anchor [ cat:x ]![] }++% note: the alphabetical names below are meaningless - they are just+% chinese numbers (which is why i follow them by the equivalent +% arabic numerals)+%+% why do i name them this way? dunno... figured they'd be easier+% to see or something+++yi1() initial+YiMo [cat:a tb:?T]![tb:?B] {+ YiLe anchor [ cat:x ]![]+ YiRi type:subst [ cat:b ]![]+}++er2() initial+Mother [cat:b]![] {+ Left anchor [ cat:x ]![]+ Right type:subst [ cat:t1 ]![]+}++term(?C ?X) initial+T anchor [cat:?C foo:?X]![]++aux-bad() auxiliary+Mother [cat:t1 tb:foo]![] {+ Left anchor [ cat:x ]![]+ Foot type:foot [cat:t1]![tb:bar]+}++aux-good() auxiliary+MothA [cat:t1 tb:ping]![] {+ LeftA anchor [cat:x]![]+ FootA type:foot [cat:t1]![tb:ping]+}++aux-nepas () auxiliary+Mother [cat:t1 tb:ping]![] {+ Left anchor [cat:x]![]+ Foot type:foot [cat:t1]![tb:ping]+ Right type:lex "pas"+}++++% meant to receive substitution from san3 +% if it works - ?A should be set to bar+wu5 () initial+WuM [cat:a foo:?A]![] {+ WuL anchor [cat:x]![]+ WuR type:subst [cat:t1 foo:?A]![]+}++% a simple tb unification which ought to work+liu6-good () initial +Mother [cat:a]![cat:a] { Anchor anchor [cat:x]![] }++% unification + subst should work, but this should NOT propagate up! +% this should NOT propagate up+wu5-bad () initial+Mother [cat:a foo:?A]![] {+ Anch anchor [cat:x]![]+ Left type:subst [cat:t1 foo:?A]![]+ Right type:subst [cat:t2 foo:?A]![]+}++++% top-bot unification failure (simple)+liu6-bad1 () initial +Mother [cat:a]![cat:b] { Anch anchor [cat:x]![] }++% top-bot unification (complex)+liu6-bad2 () initial +Mother [cat:a tb:?X]![tb:?Y] {+ Anch anchor [cat:x]![]+ Left type:subst [cat:t1 foo:?X]![]+ Right type:subst [cat:t2 foo:?Y]![]+}++% this should still be passed+iaf-killer-good (?X) initial+Mother [cat:a]![cat:a] {+ Anch anchor [cat:x]![]+ Left type:subst [cat:t1 idx:?X]![]+ Right type:subst [cat:t2 idx:?X]![]+}++aconstr-with-anchor () initial+Anch anchor aconstr:noadj [cat:a]![]++% this succeeds iff thing is good or left unset+tb-unification-noadj () initial+M [cat:a]![] {+ X type:subst [cat:x thing:?X]![thing:good]+ Y anchor [cat:b]![]+}++tb-unification-bot() initial+M anchor [cat:a]![]++tb-unification-adj() auxiliary+M [cat:a]![idx:b] {+ X anchor [cat:b]![]+ Y type:foot [cat:a]![]+}++no-thing () initial+X anchor [cat:x]![]++thing-good () initial+X anchor [cat:x thing:good]![]++thing-bad () initial+X anchor [cat:x thing:bad]![]++lemanchor () initial+M [cat:x]![] {+ X anchor [cat:x]![]+ Y type:subst [cat:y]![lemanchor:hello]+ Z type:subst [cat:y]![lemanchor:world]+}+
+ examples/artificial/suite view
@@ -0,0 +1,29 @@+% there should be exactly one way to produce the answer here+% (as opposed to two) - the idea is that GenI enforces the+% condition that only trees with empty subst nodes list may+% subst up+abc semantics:[a() b() t1()]+sub semantics:[b() t1()]++% adjunction +adj semantics:[t1() xg()]+adj-nepas semantics:[t1() xnepas()]+adj-multi semantics:[t1() xg() xg2()]++% kidsToParents rule+k2p semantics:[t1() d()]+k2p-trivial semantics:[k2p()]++% this should still work+iaf-killer-good semantics:[t1(x) t2(x) iaf(x)]++tb-success semantics:[eg()]++aconstr-with-anchor semantics:[aconstr-with-anchor()]++tb-noadj-good-1 semantics:[tb-unification-noadj() no-thing()]+tb-noadj-good-2 semantics:[tb-unification-noadj() thing-good()]+tb-unification-1 semantics:[tb-unification-bot()]+tb-unification-2 semantics:[tb-unification-bot() tb-unification-adj()]++lemanchor semantics:[lemanchor()]
+ examples/artificial/suite-bad view
@@ -0,0 +1,11 @@+% these should all fail++k2p-conflict semantics:[t1() t2() db()]+adj-failure semantics:[a() xb()] % top/bot conflict++tb-failure1 semantics:[eb1()]+tb-failure2 semantics:[eb2() t1() t2()]++% aconstr:noadj nodes+tb-noadj-bad semantics:[tb-unification-noadj() thing-bad()]+
+ examples/chatnoir/lexicon view
@@ -0,0 +1,65 @@+%% 05 mai 2004+%% Modifiers (adjectives, relatives and pp)+++%% Determinants++un Det(?I)+semantics:[indef(?I)]++le Det(?I)+semantics:[def(?I)]++%% Noms communs++chat nC(?I)+semantics:[cat(?I)]++souris nC(?I)+semantics:[mouse(?I)]++%% Noms Propres++marie nP(?I)+semantics:[marie(?I)]++claire nP(?I)+semantics:[claire(?I)]++olivier nP(?I)+semantics:[olivier(?I)]++pierre nP(?I)+semantics:[pierre(?I)]++jean nP(?I)+semantics:[jean(?I)]++paul nP(?I)+semantics:[paul(?I)]++%% --------------------------------------------------------------------- +%% Adjectives+%% --------------------------------------------------------------------- ++noir adj_post(?I)+semantics:[black(?I)]++%noir2 (?I)+noir adj_post(?I)+semantics:[black(?I)]++petit adj_pre(?I)+semantics:[little(?I)]++mechant adj_pre(?I)+semantics:[fierce(?I)]+++%% --------------------------------------------------------------------- +%% chasse+%% --------------------------------------------------------------------- ++chasser vArity2(?E ?X ?Y)+semantics:[chase(?E ?X ?Y)]+
+ examples/chatnoir/macros view
@@ -0,0 +1,132 @@+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+% DETERMINERS+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%++Det(?I) auxiliary+n1[cat:n idx:?I det:plus qu:minus]![cat:n idx:?I det:plus qu:minus]+{+ n2 anchor [cat:det]![]+ n4 type:foot [cat:n idx:?I det:?_ qu:?_]![cat:n idx:?I det:minus qu:?_]+}++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+% NOUNS+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%++% Common Nouns: voyage+nC(?I) initial+n1 anchor [cat:n idx:?I det:?_ qu:?_]![cat:n idx:?I det:minus qu:minus ]++% Proper Nouns: Jean+nP(?I) initial+n1 anchor [cat:n idx:?I det:plus qu:minus]![cat:n idx:?I det:plus qu:minus ]++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+% ADJECTIVES+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%++adj_post(?I) auxiliary+n0[cat:n idx:?I det:?_ qu:?_]![cat:n idx:?I det:minus qu:minus ]+{+ n1 type:foot [cat:n idx:?I det:minus qu:minus]!+ [cat:n idx:?I det:minus qu:?_ ]+ n2 anchor [cat:a]![]+}++adj_pre(?I) auxiliary+n0[cat:n idx:?I det:?_ qu:?_]![cat:n idx:?I det:minus qu:minus ]+{+ n1 anchor [cat:a]![]+ n3 type:foot [cat:n idx:?I det:minus qu:minus]!+ [cat:n idx:?I det:minus qu:?_ ]+}++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+% TRANSITIVE VERBS+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%++%infinitive+ % chasser une souris vinfn1+vArity2:vinfn1(?E ?X ?Y) initial+n1[cat:p idx:?E mode:inf sujidx:?X]![cat:p idx:?E mode:?_ sujidx:?_]+{+ n2 anchor [cat:v idx:?E]![]+ n4 type:subst [cat:n idx:?Y det:plus qu:minus]![cat:n idx:?Y det:?_ qu:?_ ]+}++% declarative+ % le chat chasse la souris+vArity2:n0vn1(?E ?X ?Y) initial+n1[cat:p]![]+{+ n2 type:subst [cat:n idx:?X det:plus qu:minus]!+ [cat:n idx:?X det:?_ qu:?_ ]+ n3 anchor [cat:v idx:?E]![]+ n5 type:subst [cat:n idx:?Y det:plus qu:minus]![cat:n idx:?Y+ det:?_ qu:?_ ]+}++% question sujet + % qui chasse une souris ?+vArity2:qu0vn1(?E ?X ?Y) initial+n1[cat:p]![]+{+ n2 type:subst [cat:c idx:?X det:plus qu:minus]!+ [cat:c idx:?X det:?_ qu:?_ ]+ n3 anchor [cat:v idx:?E]![]+ n5 type:subst [cat:n idx:?Y det:plus qu:minus]!+ [cat:n idx:?Y det:?_ qu:?_ ]+}++% question objet+ % que chasse le chat ?+vArity2:qu1vn0(?E ?X ?Y) initial+n1[cat:p]![]+{+ n2 type:subst [cat:n idx:?Y det:plus qu:plus]!+ [cat:n idx:?Y det:?_ qu:plus ]+ n3[cat:p idx:?E]![]+ {+ % FIXME: EYK - the bottom node was cat:p, i set it to cat:v+ % to validate+ n4 anchor [cat:v idx:?E]![cat:v idx:?E]+ n6 type:subst [cat:n idx:?X det:plus qu:minus]![cat:n idx:?X+ det:?_ qu:?_ ]+ }+}++% relative sujet+% le chat qui chasse la souris++vArity2:rel0vn1(?E ?X ?Y) auxiliary+n0[cat:n idx:?X det:plus qu:?_]![cat:n idx:?X det:?_ qu:?_ ]+{+ n1 type:foot [cat:n idx:?X det:?_ qu:?_]![cat:n idx:?X det:?_ qu:?_ ]+ n2[cat:p]![]+ {+ n3 type:subst [cat:c idx:?X det:plus qu:minus]!+ [cat:c idx:?X det:?_ qu:?_ ]+ n4 anchor [cat:v idx:?E]![]+ n6 type:subst [cat:n idx:?Y det:plus qu:minus]!+ [cat:n idx:?Y det:?_ qu:?_ ]+ }}++% relative objet+% la souris que chasse le chat++vArity2:rel1vn0(?E ?X ?Y) auxiliary+n0[cat:n idx:?Y det:plus qu:?_]![cat:n idx:?Y det:?_ qu:?_ ]+{+ n1 type:foot [cat:n idx:?Y det:?_ qu:?_]!+ [cat:n idx:?Y det:?_ qu:?_ ]+ n2[cat:p]![]+ {+ n3 type:subst [cat:c idx:?X det:plus qu:minus]!+ [cat:c idx:?X det:?_ qu:?_ ]+ n4 anchor [cat:v idx:?E]![]+ n6 type:subst [cat:n idx:?X det:plus qu:minus]!+ [cat:n idx:?X det:?_ qu:?_ ] + }+}++% vi: set cinoptions=0,p0:
+ examples/chatnoir/suite view
@@ -0,0 +1,36 @@+le_mechant_chat_noir_chasser_le_souris+semantics : [ chase(e1 a b)+ cat(a)+ black(a)+ fierce(a)+ def(a)+ def(b)+ mouse(b)+ ]+[ le mechant chat noir chasser le souris ]++le_chat_noir_chasser_le_souris+semantics : [ chase(e1 a b)+ cat(a)+ def(a)+ black(a)+ def(b)+ mouse(b)+ ]+[ le chat noir chasser le souris ]++le_chat_chasser_le_souris+semantics : [ chase(e1 a b)+ cat(a)+ def(a)+ def(b)+ mouse(b)+ ]+[ le chat chasser le souris ]++le_chat_noir+semantics : [ cat(a)+ def(a)+ black(a)+ ]+[ le chat noir ]
+ examples/demo/README view
@@ -0,0 +1,48 @@+---------------------------------------------+How to do a live demo of GenI with polarities+---------------------------------------------++(this is a simplified version of the promettre grammar)++1) first donnersem; launch the debugger++ 0 - lexical selection - note multiple trees for donner+ now we are in the substitution loop+ note trees moving from agenda to chart (leap 1 x 3)+ 3 - substitution with livre and donner (leap 1 x 2)+ 5 - and again with livre - with this newly created tree (leap 1 x 3)+ 8 - note the result where all substitutions are done (leap 1)+ 9 - now that substitution is finished, we switch to the adjunction + phase (leap 1 x 3)++ 12 - now we try to insert the tree "un" into the trees of the+ agenda (leap 1)+ 13 - un livre (leap 1 x 3) + 16 - jean donner livre a paul (leap 1)+ 17 - and now this is semantically complete so we output the+ realisation "Jean donne livre à Paul"++ KEEP THIS DEBUGGER OPEN!++2) enable the polarity optimisation; + hit generate; + show automaton tab (tiny automota);+ launch the debugger+ + 0 - notice fewer trees (compare with other debugger; close old debugger)+ (leap 2 x 13)+ +3) load promettredonnersem; + hit generate+ show automaton tab (big automata q vs q pruned)+ launch debugger (keep it open) - show few trees+ disable optimisation + launch debugger - show many trees+++++ +++
+ examples/demo/lexicon view
@@ -0,0 +1,96 @@+%% 21 april 2004+%% French TAG+%% X promet Y a Z+%% Canonique, interrogatives, relatives+++%% Determinants++un Det(?I)+semantics:[indef(?I)]++%% Noms communs++livre nC(?I)+semantics:[book(?I)]++%% Noms Propres++Marie nP(?I)+semantics:[marie(?I)]++Claire nP(?I)+semantics:[claire(?I)]++Olivier nP(?I)+semantics:[olivier(?I)]++Pierre nP(?I)+semantics:[pierre(?I)]++Jean nP(?I)+semantics:[jean(?I)]++Paul nP(?I)+semantics:[paul(?I)]++%% --------------------------------------------------------------------- +%% Verbs +%% --------------------------------------------------------------------- ++donner vinfn1sp2(?E ?X ?Y ?Z)+semantics:[give(?E ?X ?Y ?Z)]+donner n0vn1sp2(?E ?X ?Y ?Z)+semantics:[give(?E ?X ?Y ?Z)]+donner qu0vn1sp2(?E ?X ?Y ?Z)+semantics:[give(?E ?X ?Y ?Z)]+donner qu1vn0sp2(?E ?X ?Y ?Z)+semantics:[give(?E ?X ?Y ?Z)]+donner qu2n0vn1(?E ?X ?Y ?Z)+semantics:[give(?E ?X ?Y ?Z)]+donner rel0vn1sp2(?E ?X ?Y ?Z)+semantics:[give(?E ?X ?Y ?Z)]+donner rel1vn0sp2(?E ?X ?Y ?Z)+semantics:[give(?E ?X ?Y ?Z)]+donner rel2n0vn1(?E ?X ?Y ?Z)+semantics:[give(?E ?X ?Y ?Z)]++promettre n0vn1sp2(?E ?X ?Y ?Z)+semantics:[promise(?E ?X ?Y ?Z)]+promettre vinfn1sp2(?E ?X ?Y ?Z)+semantics:[promise(?E ?X ?Y ?Z)]+promettre qu0vn1sp2(?E ?X ?Y ?Z)+semantics:[promise(?E ?X ?Y ?Z)]+promettre qu1vn0sp2(?E ?X ?Y ?Z)+semantics:[promise(?E ?X ?Y ?Z)]+promettre qu2n0vn1(?E ?X ?Y ?Z)+semantics:[promise(?E ?X ?Y ?Z)]+promettre rel0vn1sp2(?E ?X ?Y ?Z)+semantics:[promise(?E ?X ?Y ?Z)]+promettre rel1vn0sp2(?E ?X ?Y ?Z)+semantics:[promise(?E ?X ?Y ?Z)]+promettre rel2n0vn1(?E ?X ?Y ?Z)+semantics:[promise(?E ?X ?Y ?Z)]+promettre n0vsp2pinf1(?E ?X ?Y ?Z)+semantics:[promise(?E ?X ?Y ?Z)]+promettre vinfsp2pinf1(?E ?X ?Y ?Z)+semantics:[promise(?E ?X ?Y ?Z)]+promettre qu0vsp2pinf1(?E ?X ?Y ?Z)+semantics:[promise(?E ?X ?Y ?Z)]+promettre qu2n0vpinf1(?E ?X ?Y ?Z)+semantics:[promise(?E ?X ?Y ?Z)]+promettre rel0vsp2pinf1(?E ?X ?Y ?Z)+semantics:[promise(?E ?X ?Y ?Z)]+promettre rel2n0vpinf1(?E ?X ?Y ?Z)+semantics:[promise(?E ?X ?Y ?Z)]++persuade n0vn2pinf1(?E ?X ?Y ?Z)+semantics:[convince(?E ?X ?Y ?Z)]+persuade qu0vn2pinf1(?E ?X ?Y ?Z)+semantics:[convince(?E ?X ?Y ?Z)]+persuade qu2n0vpinf1(?E ?X ?Y ?Z)+semantics:[convince(?E ?X ?Y ?Z)]+persuade rel0vn2pinf1(?E ?X ?Y ?Z)+semantics:[convince(?E ?X ?Y ?Z)]+persuade rel1n0vn2(?E ?X ?Y ?Z)+semantics:[convince(?E ?X ?Y ?Z)]
+ examples/demo/macros view
@@ -0,0 +1,439 @@+%% 02 april 2004+%% 1. Jean promet un cadeau a Marie+%% 2. Jean promet a Marie de partir+%% 3. Qui promet un cadeau a Marie?+%% 4. Que promet Jean a Marie?+%% 5. A qui Jean promet-il un cadeau?+%% 6. la personne qui promet un cadeau a Marie+%% 7. le cadeau que Jean promet a Marie+%% 8. la personne a qui Jean promet un cadeau+%% 9. promettre un cadeau a Marie+%% 10. promettant un cadeau a Marie+%% 11. donner un livre a marie++Det(?I) auxiliary+n1[cat:n idx:?I det:_ qu:minus]![cat:n idx:?I det:_ qu:minus]+{+ n2 anchor [cat:det]![]+ n4 type:foot [cat:n idx:?I det:_ qu:_ ]![cat:n idx:?I det:minus qu:_ ]+}++ % Common Nouns: voyage+nC(?I) initial+ n1 anchor [cat:n idx:?I det:_ qu:?W ]![cat:n idx:?I det:minus qu:minus ]+% Proper Nouns: Jean++nP(?I) initial+ n1 anchor [cat:n idx:?I det:plus qu:minus]![cat:n idx:?I det:plus qu:minus ]++ %1 declarative gn promet gn sp_a+n0vn1sp2(?E ?X ?Y ?Z) initial+ n1[cat:p]![]+{+ n2 type:subst [cat:n idx:?X det:plus qu:minus]![cat:n idx:?X det:_ qu:_ ]+ n3 anchor [cat:v idx:?E]![]+ n5 type:subst [cat:n idx:?Y det:plus qu:minus]![cat:n idx:?Y det:_ qu:_ ]+ n6[cat:sp idx:?Z det:plus]![det:_ ]+ { n8[cat:prep]![]+ {+ n9 type:lex "a"+ }+ n10 type:subst [cat:n idx:?Z det:plus qu:minus]![cat:n idx:?Z+ det:_ qu:_] + }+}++ %2 infinitive V GN SP_a+vinfn1sp2(?E ?X ?Y ?Z) initial+ n1[cat:p idx:?E mode:inf sujidx:?X]![cat:p idx:?E mode:_ sujidx:_]+{+ n2 anchor [cat:v idx:?E]![]+ n4 type:subst [cat:n idx:?Y det:plus qu:minus]![cat:n idx:?Y det:_ qu:_ ]+ n5[cat:sp idx:?Z det:plus]![det:_ ]+ { n6[cat:prep]![]+ {+ n7 type:lex "a"+ }+ n8 type:subst [cat:n idx:?Z det:plus qu:minus]![cat:n idx:?Z+ det:_ qu:_] + }+}++ %3 question sujet qui V GN GP_a ?+qu0vn1sp2(?E ?X ?Y ?Z) initial+ n1[cat:p]![]+{+ n2 type:subst [cat:c idx:?X det:plus qu:minus]![idx:?X det:_ qu:_ ]+ n3 anchor [cat:v idx:?E]![]+ n5 type:subst [cat:n idx:?Y det:plus qu:minus]![cat:n idx:?Y det:_ qu:_ ]+ n6[cat:sp idx:?Z det:plus]![det:_ ]+ { n8[cat:prep]![]+ {+ n9 type:lex "a"+ }+ n10 type:subst [cat:n idx:?Z det:plus qu:minus]![cat:n idx:?Z det:_ qu:_ ] + }+}++ %4 question objet+qu1vn0sp2(?E ?X ?Y ?Z) initial+ n1[cat:p]![]+{+ n2 type:subst [cat:n idx:?Y det:plus qu:plus]![cat:n idx:?Y det:_ qu:plus ]+ n3[cat:p idx:?E]![]+ {+ n4 anchor [cat:v idx:?E]![idx:?E]+ n6 type:subst [cat:n idx:?X det:plus qu:minus]![cat:n idx:?X+ det:_ qu:_ ]+ n7[cat:sp idx:?Z det:plus]![det:_ ]+ { n8[cat:prep]![]+ {+ n9 type:lex "a"+ }+ n10 type:subst [cat:n idx:?Z det:plus qu:minus]![cat:n idx:?Z det:_ qu:_] + }+ }+}++ %5 question objet indirect+qu2n0vn1(?E ?X ?Y ?Z) initial+ n1[cat:p]![]+{+ n2[cat:sp idx:?Z det:plus]![det:_ ]+ { n3[cat:prep]![]+ {+ n4 type:lex "a"+ }+ n5 type:subst [cat:n idx:?Z det:plus qu:plus]![cat:n idx:?Z det:_ qu:plus] + }+ n6[cat:p idx:?E]![]+ {+ n7 type:subst [cat:n idx:?X det:plus qu:minus]![cat:n idx:?X det:_ qu:minus ]+ n8 anchor [cat:v idx:?E]![idx:?E]+ n10 type:subst [cat:n idx:?Y det:plus qu:minus]![cat:n idx:?Y det:_ qu:_ ]+ }+}++%6 relative sujet qui V gn sp_a +rel0vn1sp2(?E ?X ?Y ?Z) auxiliary+n0[cat:n idx:?X det:plus qu:_]![cat:n idx:?X det:_ qu:_ ]+{+ n1 type:foot [cat:n idx:?X det:_ qu:_]![cat:n idx:?X det:_ qu:_ ]+ n2[cat:p]![]+ {+ n3 type:subst [cat:c idx:?X det:plus qu:minus]![cat:n idx:?X det:_ qu:_ ]+ n4 anchor [cat:v idx:?E]![]+ n6 type:subst [cat:n idx:?Y det:plus qu:minus]![cat:n idx:?Y det:_ qu:_ ]+ n7[cat:sp idx:?Z ]![]+ { n8[cat:prep]![]+ {+ n9 type:lex "a"+ }+ n10 type:subst [cat:n idx:?Z det:plus qu:minus]![cat:n idx:_+ det:_ qu:_] + }+ n9[cat:p]![]+ {n10[cat:prep]![]+ {+ n11 type:lex "de"+ } + n12 type:subst [cat:p idx:?Z mode:inf sujidx:?X]![cat:p idx:?Z + mode:_ sujidx:_]+ }}}++%7 relative objet+rel1vn0sp2(?E ?X ?Y ?Z) auxiliary+n0[cat:n idx:?Y det:plus qu:_]![cat:n idx:?Y det:_ qu:_ ]+{+ n1 type:foot [cat:n idx:?Y det:_ qu:_]![cat:n idx:?Y det:_ qu:_ ]+ n2[cat:p]![]+ {+ n3 type:subst [cat:c idx:?X det:plus qu:minus]![cat:n idx:?X det:_ qu:_ ]+ n4 anchor [cat:v idx:?E]![]+ n6 type:subst [cat:n idx:?X det:plus qu:minus]![cat:n idx:?X det:_ qu:_ ]+ n7[cat:sp idx:?Z ]![]+ { n8[cat:prep]![]+ {+ n9 type:lex "a"+ }+ n10 type:subst [cat:n idx:?Z det:plus qu:minus]![cat:n idx:_+ det:_ qu:_] + }+ }+}++%8 relative objet indirect+rel2n0vn1(?E ?X ?Y ?Z) auxiliary+n0[cat:n idx:?Z det:plus qu:_]![cat:n idx:?Z det:_ qu:_ ]+{+ n1 type:foot [cat:n idx:?Z det:_ qu:_]![cat:n idx:?Z det:_ qu:_]+ n2[cat:p]![]+ {+ n3[cat:sp idx:?Z det:plus]![det:_ ]+ { n4[cat:prep]![]+ {+ n5 type:lex "a"+ }+ n6 type:subst [cat:n idx:?Z det:plus qu:plus]![cat:n idx:?Z det:_ qu:plus] + }+ n7[cat:p idx:?E]![]+ {+ n8 type:subst [cat:n idx:?X det:plus qu:minus]![cat:n idx:?X det:_ qu:minus ]+ n9 anchor [cat:v idx:?E]![cat:p idx:?E]+ n11 type:subst [cat:n idx:?Y det:plus qu:minus]![cat:n idx:?Y det:_ qu:_ ]+ }+ }+}++ %9 declarative GN V GN_a Pinf_de+n0vsp2pinf1(?E ?X ?Y ?Z) initial+ n1[cat:p]![]+{+ n2 type:subst [cat:n idx:?X det:plus qu:minus]![cat:n idx:?X det:_ qu:_ ]+ n3 anchor [cat:v idx:?E]![]+ n5[cat:sp idx:?Y det:plus]![det:_ ]+ { n6[cat:prep]![]+ {+ n7 type:lex "a"+ }+ n8 type:subst [cat:n idx:?Y det:plus qu:minus]![cat:n idx:?Y det:_ qu:_] + }+ n9[cat:p]![]+ {n10[cat:prep]![]+ {+ n11 type:lex "de"+ } + n12 type:subst [cat:p idx:?Z mode:inf sujidx:?X]![idx:?Z mode:_ sujidx:_]+ }+}++ %10 infinitive V SP_a Inf_de+vinfsp2pinf1(?E ?X ?Y ?Z) initial+ n1[cat:p idx:?E mode:inf sujidx:?X]![idx:?Y mode:_ sujidx:_]+{+ n2 anchor [cat:v idx:?E]![]+ n4[cat:sp idx:?Z det:plus]![det:_ ]+ { n5[cat:prep]![]+ {+ n6 type:lex "a"+ }+ n7 type:subst [cat:n idx:?Z det:plus qu:minus]![cat:n idx:?Z+ det:_ qu:_] + }+ n8[cat:p]![]+ {n9[cat:prep]![]+ {+ n10 type:lex "de"+ } + n11 type:subst [cat:p idx:?Z mode:inf sujidx:?X]![cat:p idx:?Z + mode:_ sujidx:_]+ }+}++ %11 question sujet qui V GP_a Pinf_de ?+qu0vsp2pinf1(?E ?X ?Y ?Z) initial + n1[cat:p]![]+{+ n2 type:subst [cat:c idx:?X det:plus qu:minus]![cat:n idx:?X det:_ qu:_ ]+ n3 anchor [cat:v idx:?E]![]+ n5[cat:sp idx:?Z det:plus]![det:_ ]+ { n6[cat:prep]![]+ {+ n7 type:lex "a"+ }+ n8 type:subst [cat:n idx:?Z det:plus qu:minus]![cat:n idx:?Z det:_ qu:_ ] + }+ n9[cat:p]![]+ {n10[cat:prep]![]+ {+ n11 type:lex "de"+ } + n12 type:subst [cat:p idx:?Z mode:inf sujidx:?X]![cat:p idx:?Z + mode:_ sujidx:_]+ }}++ %12 question objet indirect+qu2n0vpinf1(?E ?X ?Y ?Z) initial+ n1[cat:p]![]+{+ n2[cat:sp idx:?Z det:plus]![det:_ ]+ { n3[cat:prep]![]+ {+ n4 type:lex "a"+ }+ n5 type:subst [cat:n idx:?Z det:plus qu:plus]![cat:n idx:?Z det:_ qu:plus] + }+ n6[cat:p idx:?E]![]+ {+ n7 type:subst [cat:n idx:?X det:plus qu:minus]![cat:n idx:?X det:_ qu:minus ]+ n8 anchor [cat:v idx:?E]![cat:p idx:?E]+ }+ n10[cat:prep]![]+ {+ n11 type:lex "de"+ } + n12 type:subst [cat:p idx:?Z mode:inf sujidx:?X]![cat:p idx:?Z + mode:_ sujidx:_]+}++%13 relative sujet qui V sp_a pinf_de+rel0vsp2pinf1(?E ?X ?Y ?Z) auxiliary+n0[cat:n idx:?X det:plus qu:_]![cat:n idx:?X det:_ qu:_ ]+{+ n1 type:foot [cat:n idx:?X det:_ qu:_]![cat:n idx:?X det:_ qu:_ ]+ n2[cat:p]![]+ {+ n3 type:subst [cat:c idx:?X det:plus qu:minus]![cat:n idx:?X det:_ qu:_ ]+ n4 anchor [cat:v idx:?E]![]+ n6[cat:sp idx:?Z ]![]+ { n7[cat:prep]![]+ {+ n8 type:lex "a"+ }+ n9 type:subst [cat:n idx:?Z det:plus qu:minus]![cat:n idx:_+ det:_ qu:_] + }+ n10[cat:p]![]+ {n11[cat:prep]![]+ {+ n12 type:lex "de"+ } + n13 type:subst [cat:p idx:?Z mode:inf sujidx:?X]![cat:p idx:?Z + mode:_ sujidx:_]+ }}}++%14 relative objet indirect a qui GN v Pinf_de+rel2n0vpinf1(?E ?X ?Y ?Z) auxiliary+n0[cat:n idx:?Z det:plus qu:_]![cat:n idx:?Z det:_ qu:_ ]+{+ n1 type:foot [cat:n idx:?Z det:_ qu:_]![cat:n idx:?Z det:_ qu:_]+ n2[cat:p]![]+ {+ n3[cat:sp idx:?Z det:plus]![det:_ ]+ { n4[cat:prep]![]+ {+ n5 type:lex "a"+ }+ n6 type:subst [cat:n idx:?Z det:plus qu:plus]![cat:n idx:?Z det:_ qu:plus] + }+ n7[cat:p idx:?E]![]+ {+ n8 type:subst [cat:n idx:?X det:plus qu:minus]![cat:n idx:?X det:_ qu:minus ]+ n9 anchor [cat:v idx:?E]![cat:p idx:?E]+ }+ n9[cat:p]![]+ {n10[cat:prep]![]+ {+ n11 type:lex "de"+ } + n12 type:subst [cat:p idx:?Y mode:inf sujidx:?X]![cat:p idx:?Y+ mode:_ sujidx:_]+ }+ }}+++ %15 declarative gn0 persuade gn2 pinf_de1 n0vn2pinf1 +n0vn2pinf1(?E ?X ?Y ?Z) initial+ n1[cat:p]![]+{+ n2 type:subst [cat:n idx:?X det:plus qu:minus]![cat:n idx:?X det:_ qu:_ ]+ n3 anchor [cat:v idx:?E]![]+ n5 type:subst [cat:n idx:?Y det:plus qu:minus]![cat:n idx:?Y det:_ qu:_]+ n6[cat:p]![]+ {n7[cat:prep]![]+ {+ n8 type:lex "de"+ } + n9 type:subst [cat:p idx:?Z mode:inf sujidx:?X]![idx:?Z mode:_ sujidx:_]+ }+}++ %15 infinitive persuader gn pinf_de vinfn2pinf1+vinfn2pinf1(?E ?X ?Y ?Z) initial+ n1[cat:p idx:?E mode:inf sujidx:?X]![cat:p idx:?Y mode:_ sujidx:_]+{+ n2 anchor [cat:v idx:?E]![]+ n4 type:subst [cat:n idx:?Y det:plus qu:minus]![cat:n idx:?Y+ det:_ qu:_] + n5[cat:p]![]+ {n6[cat:prep]![]+ {+ n7 type:lex "de"+ } + n8 type:subst [cat:p idx:?Z mode:inf sujidx:?Y]![cat:p idx:?Z + mode:_ sujidx:_]+ }+}++ %16 qu-sujet qui persuade gn pinf_de ? qu0vn2pinf1+qu0vn2pinf1(?E ?X ?Y ?Z) initial+ n1[cat:p]![]+{+ n2 type:subst [cat:c idx:?X det:plus qu:minus]![cat:n idx:?X det:_ qu:_ ]+ n3 anchor [cat:v idx:?E]![]+ n5 type:subst [cat:n idx:?Z det:plus qu:minus]![cat:n idx:?Z det:_ qu:_ ] + n6[cat:p]![]+ {n7[cat:prep]![]+ {+ n8 type:lex "de"+ } + n9 type:subst [cat:p idx:?Z mode:inf sujidx:?X]![cat:p idx:?Z + mode:_ sujidx:_]+ }}++ %17 qu-obj qui gn persuade-il pinf_de ? qu2n0vpinf1+qu2n0vpinf1(?E ?X ?Y ?Z) initial+ n1[cat:p]![]+{+ n2 type:subst [cat:n idx:?Z det:plus qu:plus]![cat:n idx:?Z det:_ qu:plus] + n3[cat:p idx:?E]![]+ {+ n4 type:subst [cat:n idx:?X det:plus qu:minus]![cat:n idx:?X det:_ qu:minus ]+ n5 anchor [cat:v idx:?E]![cat:p idx:?E]+ }+ n7[cat:prep]![]+ {+ n8 type:lex "de"+ } + n9 type:subst [cat:p idx:?Y mode:inf sujidx:?X]![cat:p idx:?Y + mode:_ sujidx:_]+}++%18 rel-sjt n qui persuade gn pinf_de rel0vn2pinf1+rel0vn2pinf1(?E ?X ?Y ?Z) auxiliary+n0[cat:n idx:?X det:plus qu:_]![cat:n idx:?X det:_ qu:_ ]+{+ n1 type:foot [cat:n idx:?X det:_ qu:_]![cat:n idx:?X det:_ qu:_ ]+ n2[cat:p]![]+ {+ n3 type:subst [cat:c idx:?X det:plus qu:minus]![cat:n idx:?X det:_ qu:_ ]+ n4 anchor [cat:v idx:?E]![]+ n6 type:subst [cat:n idx:?Z det:plus qu:minus]![cat:n idx:_+ det:_ qu:_] + n7[cat:p]![]+ {n8[cat:prep]![]+ {+ n9 type:lex "de"+ } + n10 type:subst [cat:p idx:?Y mode:inf sujidx:?X]![cat:p idx:?Y + mode:_ sujidx:_]+ }}}++%19 rel-obj n dont gn persuade gn rel1n0vn2+rel1n0vn2(?E ?X ?Y ?Z) auxiliary+n0[cat:n idx:?Y det:plus qu:_]![cat:n idx:?Y det:_ qu:_ ]+{+ n1 type:foot [cat:n idx:?Y det:_ qu:_]![cat:n idx:?Y det:_ qu:_]+ n2[cat:p]![]+ {+ n3 type:subst [cat:c idx:?Y det:plus qu:_]![cat:c idx:?Y det:_ qu:_] + n4[cat:p idx:?E]![]+ {+ n5 type:subst [cat:n idx:?X det:plus qu:minus]![cat:n idx:?X det:_ qu:minus ]+ n6 anchor [cat:v idx:?E]![cat:p idx:?E]+ }+ n8 type:subst [cat:n idx:?Y det:plus qu:minus]![cat:n idx:?Y + det:_ qu:minus ]+ }+}+
+ examples/demo/suite view
@@ -0,0 +1,19 @@+donner + semantics : [ jean(a)+ give(e2 a c d)+ indef(c)+ book(c)+ paul(d)+ ]++promettre_donner+ semantics : [ promise(e1 a b e2)+ jean(a)+ marie(b)+ give(e2 a c d)+ indef(c)+ book(c)+ paul(d)+ ]++
+ examples/ej/lexicon view
@@ -0,0 +1,100 @@+%%+%% The LEXICON+%%+%% +%% Nouns+%% common nouns+%% (hat, man, rabbit, woman)+%% proper names+%% (mia, vincent)+%% Verbs+%% intransitive verbs+%% (runs, sleeps)+%% transitive verbs+%% (loves, removes)+%% Modifiers+%% adjectives+%% (big, tall, white)+%% adverbs+%% (fast)+%% Determiners+%% (a, the)+++%% Nouns+%% common nouns++hat Cn(?Entity ! agr:sg3)+semantics:[hat(_ ?Entity)]++man Cn(?Entity ! agr:sg3)+semantics:[man(_ ?Entity)]++rabbit Cn(?Entity ! agr:sg3)+semantics:[rabbit(_ ?Entity)]++woman Cn(?Entity ! agr:sg3)+semantics:[woman(_ ?Entity)]+++%% proper names++Mia Pn(?Entity ! agr:sg3)+semantics:[name(_ ?Entity mia)]++Vincent Pn(?Entity ! agr:sg3)+semantics:[name(_ ?Entity vincent)]++Vinny Pn(?Entity ! agr:sg3)+semantics:[name(_ ?Entity vincent)]++Émilie Pn(?Entity ! agr:sg3)+semantics:[name(_ ?Entity émilie)]+++%% Verbs+%% intransitive verbs++runs vArity1(?Event ?Agent ! agr:sg3)+semantics:[run(?Event ?Agent)]++gallops vArity1(?Event ?Agent ! agr:sg3)+semantics:[run(?Event ?Agent)]++sleeps vArity1(?Event ?Agent ! agr:sg3)+semantics:[sleep(?Event ?Agent)]++%% transitive verbs++loves vArity2(?Event ?Agent ?Experiencer ! agr:sg3)+semantics:[love(?Event ?Agent ?Experiencer)]++removes vArity3(?Event ?Agent ?Theme ?Loc ! agr:sg3)+semantics:[remove(?Event ?Agent ?Theme ?Loc)]+++%% Modifiers+%% adjectives++big Adj(?Entity)+semantics:[big(_ ?Entity)]++tall Adj(?Entity)+semantics:[tall(_ ?Entity)]++white Adj(?Entity)+semantics:[white(_ ?Entity)]+++%% adverbs++fast Adv(?Event)+semantics:[fast(_ ?Event)]+++%% ?Determiners++%a Dp(?Entity ! agr:sg3)++the Dp(?Entity)+semantics:[def(?Entity)]
+ examples/ej/macros view
@@ -0,0 +1,92 @@+%% +%% Tree Templates+%%+%%+%% INITIAL TREES+%% s trees+%% (IntrV, TrV, TrVPP)+%% np trees+%% (Dp, Pn)+%% n trees+%% (Cn)+%%+%% AUXILIARY TREES+%% n trees+%% (Adj)+%% vp trees+%% (Adv)+++%% INITIAL TREES++vArity1:IntrV(?Event ?Agent ! agr:?A) initial+ n1[cat:s idx:?Event]![]+ { + n2 type:subst [cat:np idx:?Agent]![]+ n3 [cat:vp idx:?Event]![]+ {+ n4 anchor [cat:v idx:?Event]![]+ }+ }++vArity2:TrV(?Event ?Agent ?Experiencer ! agr:?A) initial+ n1[cat:s idx:?Event]![]+ {+ n2 type:subst [cat:np idx:?Agent ]![]+ n3[cat:vp idx:?Event]![]+ {+ n4 anchor [cat:v idx:?Event]![]+ n6 type:subst [cat:np idx:?Experiencer ]![]+ }+ }++vArity3:TrVPP(?Event ?Agent ?Theme ?Loc ! agr:?A) initial+ n1[cat:s idx:?Event]![]+ {+ n2 type:subst [cat:np idx:?Agent ]![]+ n3[cat:vp idx:?Event]![]+ {+ n4 anchor [cat:v idx:?Event]![]+ n6 type:subst [cat:np idx:?Theme ]![]+ n7 aconstr:noadj [cat:pp]![]+ {+ n8[cat:p]![]+ {+ n9 type:lex "from"+ }+ n10 type:subst [cat:np idx:?Loc ]![]+ }+ }+ }++Dp(?Entity ! agr:?A) initial+ n1[cat:np idx:?Entity]![]+ {+ n2 anchor [cat:det]![]+ n4 type:subst [cat:n idx:?Entity ]![]+ }++Pn(?Entity ! agr:?A) initial+ n1[cat:np idx:?Entity]![]+ {+ n2 anchor [cat:pn idx:?Entity]![]+ }++Cn(?Entity ! agr:?A) initial+ n1 anchor [cat:n idx:?Entity]![]++%% ?AUXILIARY ?TREES++Adj(?Entity) auxiliary+ n1[cat:n idx:?Entity]![]+ {+ n2 anchor [cat:adj]![]+ n4 type:foot [cat:n idx:?Entity ]![]+ }++Adv(?Event) auxiliary+ n1[cat:vp idx:?Event]![]+ {+ n2 type:foot [cat:vp idx:?Event]![]+ n3 anchor [cat:adv]![]+ }
+ examples/ej/suite view
@@ -0,0 +1,88 @@+v_runs +semantics: [ name(s1 a vincent)+ run(e a) ]+[Vincent runs]+[Vincent gallops]+[Vinny runs]+[Vinny gallops]++v_loves_m +semantics: [ name(s1 a vincent)+ love(e a ex)+ name(s2 ex mia)]+[Vincent loves Mia]++v_loves_e+semantics: [ name(s1 a vincent)+ love(e a ex)+ name(s2 ex émilie)]+[Vincent loves Émilie]++v_loves_m_f+semantics : [name(s1 a vincent)+ love(e a ex)+ fast(s3 e)+ name(s2 ex mia)]+[Vincent loves Mia fast] % loves fast?!++big_tall_man_run+semantics : [def(a)+ man(s1 a)+ run(e a)+ big(s3 a)+ tall(s3 a)+ ]+[the big tall man runs]++man_love_m+semantics: [man(s1 a) + def(a)+ love(e a ex)+ name(s2 ex mia)]+[the man loves Mia]++man_remove_rabbit+semantics : [man(s1 a) + def(a)+ remove(e a ex h)+ def(ex)+ rabbit(s2 ex)+ hat(s3 h)+ def(h)]+[the man removes the rabbit from the hat]++man_remove_white_rabbit_fast+semantics : [man(s1 a)+ def(a)+ remove(e a ex h)+ rabbit(s2 ex)+ def(ex)+ white(s6 ex)+ fast (s5 e)+ hat(s3 h)+ def(h)]+[the man removes the white rabbit from the hat fast]++man_remove_rabbit_fast+semantics : [man(s1 a) + remove(e a ex h)+ def(a)+ fast(s4 e)+ rabbit(s2 ex)+ def(ex)+ hat(s3 h)+ def(h)]+[the man removes the rabbit from the hat fast]++man_remove_rabbit_fast_def+semantics : [man(s1 a)+ def(a) + remove(e a ex h)+ fast(s4 e)+ rabbit(s2 ex)+ def(ex)+ white(s4 ex)+ def(h)+ hat(s3 h)]+[the man removes the white rabbit from the hat fast]+
+ examples/nosemantics/README.txt view
@@ -0,0 +1,19 @@+in order to make geni work without semantics,+the Geni/.genirc file must set IgnoreSemantics+to true. here is an example .genirc:++Grammar = examples/media/index++% True or False+Graphical = True +IgnoreSemantics = True +MaxTrees = 3 ++% Optimisations should be a comma delimited list containing any +% number of the following items: +% Polarised, PolSig, ChartSharing, +% SemFiltered, FootConstraint +% There is also PolOpts (all polarity optimisations) +% and AdjOpts (all adjunction optimisations) ++Optimisations = FootConstraint
+ examples/nosemantics/lexicon view
@@ -0,0 +1,20 @@++reserver n0_v()+reserver n0_v_n1()+dernier adj_substantif()+trop adv_adj()+absolument mod_adv_right()+mercredi enum_n()+six det_det()+la n_la()+soixante-dix-neuf det_n()+type n()+il pro()+cher n_adj()+invalide n_adj()+fort adj_n()+americain n()+auberge n()+Loire np()+Luneville np()+Lyon np()
+ examples/nosemantics/macros view
@@ -0,0 +1,363 @@+%%+%% GENI Macro+%% This macro was automatically generated by+%% a tagml->macro script, tagml2genimacro.xsl+%% 2005+%% contact: kow@loria.fr lai@loria.fr++n() initial+n1 anchor++{+}++pro() initial+n1 anchor++{+}++pro_substantif() initial+n1 [cat:nom]![cat:nom]+{+n1.1 anchor+}++det_n() auxiliary+n1 [cat:nom]![cat:nom]+{+n1.1 anchor+n1.2 type:foot [cat:nom]![cat:nom]+}++n_la() auxiliary+n1 [cat:nom]![cat:nom]+{+n1.1 type:foot [cat:nom]![cat:nom]+n1.2 anchor+}++det_substantif() initial+n1 [cat:nom]![cat:nom]+{+n1.1 anchor+}++det_det() auxiliary+n1 [cat:det]![cat:det]+{+n1.1 type:foot [cat:det]![cat:det]+n1.2 anchor+}++np() initial+n1 anchor++{+}++np_substantif() initial+n1 [cat:nom]![cat:nom]+{+n1.1 anchor+}++n_np() auxiliary+n1 [cat:nom]![cat:nom]+{+n1.1 type:foot [cat:nom]![cat:nom]+n1.2 anchor+}++n_con_n() initial+n1 [cat:nom]![cat:nom]+{+n1.1 type:subst [cat:nom]![cat:nom]+n1.2 anchor+n1.3 type:subst [cat:nom]![cat:nom]+}++enum_n() auxiliary+n1 [cat:nom]![cat:nom]+{+n1.1 type:foot [cat:nom]![cat:nom]+n1.2 anchor+}++n_pred() initial+n1 [cat:nom]![cat:nom]+{+n1.1 type:subst [cat:nom]![cat:nom]+n1.2 [cat:gp]![cat:gp]+{+n1.2.1 anchor+n1.2.2 type:subst [cat:nom]![cat:nom]+}+}++compl_n() initial+n1 [cat:nom]![cat:nom]+{+n1.1 type:subst [cat:nom]![cat:nom]+n1.2 [cat:cdn]![cat:cdn]+{+n1.2.1 anchor+n1.2.2 type:subst [cat:nom]![cat:nom]+}+}++adj() initial+n1 anchor++{+}++adj_substantif() initial+n1 [cat:nom]![cat:nom]+{+n1.1 anchor+}++adv_adj() auxiliary+n1 [cat:adj]![cat:adj]+{+n1.1 anchor+n1.2 type:foot [cat:adj]![cat:adj]+}++adj_n() auxiliary+n1 [cat:nom]![cat:nom]+{+n1.1 anchor+n1.2 type:foot [cat:nom]![cat:nom]+}++n_adj() auxiliary+n1 [cat:nom]![cat:nom]+{+n1.1 type:foot [cat:nom]![cat:nom]+n1.2 anchor+}++n_gp() auxiliary+n1 [cat:nom]![cat:nom]+{+n1.1 type:foot [cat:nom]![cat:nom]+n1.2 [cat:gp]![cat:gp]+{+n1.2.1 anchor+n1.2.2 type:subst [cat:nom]![cat:nom]+}+}++s_gp() auxiliary+n1 [cat:sen]![cat:sen]+{+n1.1 type:foot [cat:sen]![cat:sen]+n1.2 [cat:gp]![cat:gp]+{+n1.2.1 anchor+n1.2.2 type:subst [cat:nom]![cat:nom]+}+}++gp_s() auxiliary+n1 [cat:sen]![cat:sen]+{+n1.1 type:foot [cat:sen]![cat:sen]+n1.2 [cat:gp]![cat:gp]+{+n1.2.1 anchor+n1.2.2 type:subst [cat:nom]![cat:nom]+}+}++gp() initial+n1 [cat:gp]![cat:gp]+{+n1.1 anchor+n1.2 type:subst [cat:nom]![cat:nom]+}++gpro() initial+n1 [cat:nom]![cat:nom]+{+n1.1 anchor+n1.2 type:subst [cat:nom]![cat:nom]+}++gpro_n_right() auxiliary+n1 [cat:nom]![cat:nom]+{+n1.1 type:foot [cat:nom]![cat:nom]+n1.2 anchor+n1.3 type:subst [cat:nom]![cat:nom]+}++n0_v() initial+n1 [cat:sen]![cat:sen]+{+n1.1 type:subst [cat:nom]![cat:nom]+n1.2 anchor+}++n0_v_n1() initial+n1 [cat:sen]![cat:sen]+{+n1.1 type:subst [cat:nom]![cat:nom]+n1.2 anchor+n1.3 type:subst [cat:nom]![cat:nom]+}++pro_v() initial+n1 [cat:sen]![cat:sen]+{+n1.1 type:subst [cat:pro]![cat:pro]+n1.2 anchor+}++pro_v_n1() initial+n1 [cat:sen]![cat:sen]+{+n1.1 type:subst [cat:pro]![cat:pro]+n1.2 anchor+n1.3 type:subst [cat:nom]![cat:nom]+}++v_inf() initial+n1 [cat:sen]![cat:sen]+{+n1.1 anchor+}++v_n1_inf() initial+n1 [cat:sen]![cat:sen]+{+n1.1 anchor+n1.2 type:subst [cat:nom]![cat:nom]+}++n0_v_s() initial+n1 [cat:sen]![cat:sen]+{+n1.1 type:subst [cat:nom]![cat:nom]+n1.2 anchor+n1.3 type:subst [cat:sen]![cat:sen]+}++pro_v_s() initial+n1 [cat:sen]![cat:sen]+{+n1.1 type:subst [cat:pro]![cat:pro]+n1.2 anchor+n1.3 type:subst [cat:sen]![cat:sen]+}++mod_v() auxiliary+n1 [cat:ver]![cat:ver]+{+n1.1 anchor+n1.2 type:foot [cat:ver]![cat:ver]+}++il_faut_n0() initial+n1 [cat:sen]![cat:sen]+{+n1.1 type:lex "il"+n1.2 anchor+n1.3 type:subst [cat:nom]![cat:nom]+}++mod_pro_left() auxiliary+n1 [cat:ver]![cat:ver]+{+n1.1 anchor+n1.2 type:foot [cat:ver]![cat:ver]+}++qu_est_ce_que() initial+n1 [cat:sen]![cat:sen]+{+n1.1 anchor+n1.2 type:subst [cat:sen]![cat:sen]+}++v_pro() initial+n1 [cat:sen]![cat:sen]+{+n1.1 anchor+n1.2 type:subst [cat:pro]![cat:pro]+}++v_pro_n() initial+n1 [cat:sen]![cat:sen]+{+n1.1 anchor+n1.2 type:subst [cat:pro]![cat:pro]+n1.3 type:subst [cat:nom]![cat:nom]+}++v_pro_s() initial+n1 [cat:sen]![cat:sen]+{+n1.1 anchor+n1.2 type:subst [cat:pro]![cat:pro]+n1.3 type:subst [cat:sen]![cat:sen]+}++mod_adv_left() auxiliary+n1 [cat:ver]![cat:ver]+{+n1.1 anchor+n1.2 type:foot [cat:ver]![cat:ver]+}++mod_adv_right() auxiliary+n1 [cat:ver]![cat:ver]+{+n1.1 type:foot [cat:ver]![cat:ver]+n1.2 anchor+}++mod_adv() auxiliary+n1 [cat:adv]![cat:adv]+{+n1.1 anchor+n1.2 type:foot [cat:adv]![cat:adv]+}++mod_adv_n_left() auxiliary+n1 [cat:nom]![cat:nom]+{+n1.1 type:foot [cat:nom]![cat:nom]+n1.2 anchor+}++mod_adv_n_right() auxiliary+n1 [cat:nom]![cat:nom]+{+n1.1 anchor+n1.2 type:foot [cat:nom]![cat:nom]+}++adv() initial+n1 anchor++{+}++entre_np_et_np() initial+n1 [cat:gp]![cat:gp]+{+n1.1 anchor+n1.2 type:subst [cat:nompropre]![cat:nompropre]+n1.3 type:lex "et"+n1.4 type:subst [cat:nompropre]![cat:nompropre]+}++int() initial+n1 anchor++{+}+
+ examples/promettre/lexicon view
@@ -0,0 +1,78 @@+%% 21 april 2004+%% French TAG+%% X promet Y a Z+%% Canonique, interrogatives, relatives++il pronoun(?H ?X)+semantics:[?H:il(+?X)]++se clitic(?H ?X)+semantics:[]++le clitic(?H ?X)+semantics:[]++%% Determinants++un Det(?I)+semantics:[_:indef(?I)]++le Det(?I)+semantics:[_:def(?I)]++quel DetQ(?I)+semantics:[_:qu(?I)]++%% Noms communs++livre nC(?I)+semantics:[book(+?I)]++cadeau nC(?I)+semantics:[present(+?I)]++%% Noms Propres++Claire nP(?I)+semantics:[claire(+?I)]++Marie nP(?I)+semantics:[mary(+?I)]++Jean nP(?I)+semantics:[john(+?I)]++Paul nP(?I)+semantics:[paul(+?I)]++Pierre nP(?I)+semantics:[peter(+?I)]++Olivier nP(?I)+semantics:[oliver(+?I)]+++++%% --------------------------------------------------------------------- +%% Verbs+%% --------------------------------------------------------------------- +promettre vArity3(?E ?X ?Y ?Z)+semantics:[+?E:promise(-?X -?Y -?Z)]++promettre vArity3control(?E ?X ?Y ?Z)+semantics:[+?E:promise( ?X -?Y -?Z)]++persuader vArity3(?E ?X ?Y ?Z)+semantics:[+?E:convince(-?X -?Y -?Z)]++persuader vArity3controlObj(?E ?X ?Y ?Z)+semantics:[+?E:convince(-?X -?Y ?Z)]++donner vArity3(?E ?X ?Y ?Z)+semantics:[+?E:give(-?X -?Y -?Z)]++aimer vArity2(?E ?X ?Y)+semantics:[+?E:love(-?X -?Y)]++
+ examples/promettre/macros view
@@ -0,0 +1,450 @@+%% 02 april 2004+%% 1. Jean promet un cadeau a Marie+%% 2. Jean promet a Marie de partir+%% 6. la personne qui promet un cadeau a Marie+%% 7. le cadeau que Jean promet a Marie+%% 8. la personne a qui Jean promet un cadeau+%% 9. promettre un cadeau a Marie+%% 10. promettant un cadeau a Marie+%% 11. donner un livre a marie++% FIXME: eric the non-linguist set all cat:p nodes with unspecified mode to FIXME++Det(?I) auxiliary+n1[cat:n idx:?I det:plus qu:minus]![cat:n idx:?I qu:minus]+{+ n2 type:anchor [cat:det]![]+ n4 type:foot [cat:n idx:?I]![cat:n idx:?I det:minus]+}++clitic:cl(?H ?X ! idx:?X ) initial+n1 type:anchor [cat:cl idx:?X]![cat:cl idx:?X] {}++pronoun:pn(?H ?X ! idx:?X num:?Num gen:?Gen pers:?Pers) initial+n1 [cat:n idx:?X num:?Num gen:?Gen pers:?Pers]![cat:n idx:?X]+{+ n2 type:anchor [cat:pn num:?Num gen:?Gen pers:?Pers]![] +}++% Common Nouns: voyage+nC(?I!num:?Num gen:?Gen) initial+n1 [cat:n num:?Num gen:?Gen idx:?I]![cat:n num:?Num gen:?Gen idx:?I det:minus qu:minus]+{+ n2 type:anchor [cat:n num:?Num gen:?Gen]![]+}++% Proper Nouns: Jean+nP(?I!pers:?Pers num:?Num gen:?Gen) initial+n1 type:anchor [cat:n num:?Num pers:?Pers gen:?Gen idx:?I det:plus qu:minus]![cat:n idx:?I det:plus qu:minus] {}++++% jean se aimer -- note: we rely on top/bottom unification+% for this to work+vArity2:n0v(?E ?X ?Y) initial +n1[cat:p mode:FIXME]![]+{+ n2 type:subst [cat:n idx:?X det:plus qu:minus]![cat:n idx:?Y]+ n5 [cat:se idx:?X det:plus qu:minus]![cat:se idx:?Y]+ n3 type:anchor [cat:v idx:?E]![]+}++vArity2:n0cl1v(?E ?X ?Y) initial+n1[cat:p mode:FIXME]![]+{+ n2 type:subst [cat:n idx:?X det:plus qu:minus]![cat:n idx:?X]+ n5 type:subst [cat:cl idx:?Y det:plus qu:minus]![cat:cl idx:?Y]+ n3 type:anchor [cat:v idx:?E]![]+}++vArity2:n0vn1(?E ?X ?Y) initial+n1[cat:p mode:FIXME]![]+{+ n2 type:subst [cat:n idx:?X det:plus qu:minus]![cat:n idx:?X]+ n3 type:anchor [cat:v idx:?E]![]+ n5 type:subst [cat:n idx:?Y det:plus qu:minus]![cat:n idx:?Y]+} ++% aimer N (jean espere [aimer Marie])+vArity2:vinfn1(?E ?X ?Y) initial +n1[cat:p idx:?E mode:inf sujidx:?X]![cat:p]+{+ n2 type:anchor [cat:v idx:?E]![]+ n4 type:subst [cat:n idx:?Y det:plus qu:minus]![cat:n idx:?Y]+}++% le aimer (jean espere [le aimer])+cl0vinf(?E ?X ?Y) initial +n1[cat:p idx:?E mode:inf sujidx:?X]![cat:p]+{+ n5 type:subst [cat:cl idx:?Y det:plus qu:minus]![cat:cl idx:?Y]+ n2 type:anchor [cat:v idx:?E]![]+}++% infinitive: le donner un livre (je promets de le donner un livre)+vArity3:cl2vinfn0(?E ?X ?Y ?Z) initial +n1[cat:p idx:?E mode:inf sujidx:?X]![cat:p]+{+ n5 type:subst [cat:cl idx:?Z det:plus qu:minus]![cat:cl idx:?Z]+ n2 type:anchor [cat:v idx:?E]![]+ n4 type:subst [cat:n idx:?Y det:plus qu:minus]![cat:n idx:?Y]+} ++ %1 declarative gn promet gn sp_a+vArity3:n0vn1sp2(?E ?X ?Y ?Z) initial+n1[cat:p mode:FIXME]![]+{+ n2 type:subst [cat:n pers:?Pers num:?Num idx:?X det:plus qu:minus]![cat:n idx:?X]+ n3[cat:v idx:?E]![]+ {+ n4 type:anchor [cat:v pers:?Pers num:?Num]![] + }+ n5 type:subst [cat:n idx:?Y det:plus qu:minus]![cat:n idx:?Y]+ n6[cat:sp idx:?Z det:plus]![]+ { n8[cat:prep]![]+ {+ n9 type:lex "a"+ }+ n10 type:subst [cat:n idx:?Z det:plus qu:minus]![cat:n idx:?Z] + }+}+++ %2 infinitive ?V GN SP_a+vArity3:vinfn1sp2(?E ?X ?Y ?Z) initial+n1[cat:p idx:?E mode:inf sujidx:?X]![cat:p]+{+ n2 type:anchor [cat:v idx:?E]![]+ n4 type:subst [cat:n idx:?Y det:plus qu:minus]![cat:n idx:?Y]+ n5[cat:sp idx:?Z det:plus]![]+ { n6[cat:prep]![]+ {+ n7 type:lex "a"+ }+ n8 type:subst [cat:n idx:?Z det:plus qu:minus]![cat:n idx:?Z] + }+}++vArity3:n0vn1inf2(?E ?X ?Y ?Z) initial+n1[cat:p mode:FIXME]![]+{+ n2 type:subst [cat:n idx:?X pers:?Pers num:?Num det:plus qu:minus]![cat:n idx:?X]+ n3[cat:v idx:?E]![]+ {+ n4 type:anchor [cat:v pers:?Pers num:?Num]![] + }+ n5[cat:sp idx:?Z det:plus]![]+ { n6[cat:p]![]+ {+ n7 type:lex "a"+ }+ n8 type:subst [cat:n idx:?Z det:plus qu:minus]![cat:n idx:?Z+ ] + }+ n9 type:subst [cat:p idx:?Y mode:inf]![cat:p idx:?Y] +}++%8 relative objet indirect+vArity3:rel2n0vn1(?E ?X ?Y ?Z) auxiliary+n0[cat:n idx:?Z det:plus]![cat:n idx:?Z]+{+ n1 type:foot [cat:n idx:?Z]![cat:n idx:?Z]+ n2[cat:p]![]+ {+ n3[cat:sp idx:?Z det:plus]![]+ { n4[cat:prep]![]+ {+ n5 type:lex "a"+ }+ n6 type:subst [cat:n idx:?Z det:plus qu:plus]![cat:n idx:?Z qu:plus] + }+ n7[cat:p idx:?E]![]+ {+ n8 type:subst [cat:n idx:?X det:plus qu:minus]![cat:n idx:?X qu:minus]+ n9 type:anchor [cat:v idx:?E]![cat:p idx:?E]+ n11 type:subst [cat:n idx:?Y det:plus qu:minus]![cat:n idx:?Y]+ }+ }+}++ %9 declarative GN V GN_a Pinf_de+vArity3control:n0vsp2pinf1(?E ?X ?Y ?Z) initial+n1[cat:p mode:FIXME]![]+{+ n2 type:subst [cat:n pers:?Pers num:?Num idx:?X det:plus qu:minus]![cat:n idx:?X]+ n3[cat:v idx:?E]![]+ {+ n4 type:anchor [cat:v pers:?Pers num:?Num]![]+ }+ n5[cat:sp idx:?Z det:plus]![]+ { n6[cat:prep]![]+ {+ n7 type:lex "a"+ }+ n8 type:subst [cat:n idx:?Z det:plus qu:minus]![cat:n idx:?Z] + }+ n9[cat:p]![]+ {n10[cat:prep]![]+ {+ n11 type:lex "de"+ } + n12 type:subst [cat:p idx:?Y mode:inf sujidx:?X]![cat:p idx:?Y]+ }+}++ %10 infinitive V SP_a Inf_de+vArity3control:vinfsp2pinf1(?E ?X ?Y ?Z) initial+n1[cat:p idx:?E mode:inf sujidx:?X]![cat:p] +{+ n2 type:anchor [cat:v idx:?E]![]+ n4[cat:sp idx:?Y det:plus]![]+ { n5[cat:prep]![]+ {+ n6 type:lex "a"+ }+ n7 type:subst [cat:n idx:?Y det:plus qu:minus]![cat:n idx:?Y+ ] + }+ n8[cat:p]![]+ {n9[cat:prep]![]+ {+ n10 type:lex "de"+ } + n11 type:subst [cat:p idx:?Y mode:inf sujidx:?X]![cat:p idx:?Y]++ }+}++ % kowey: promettre a marie de faire...+vArity3control:vinfn2pinf1(?E ?X ?Y ?Z) initial+n1[cat:p idx:?E mode:inf sujidx:?X]![cat:p] +{+ n2 type:anchor [cat:v idx:?E]![]++ n5[cat:sp idx:?Z det:plus]![]+ { n6[cat:prep]![]+ {+ n7 type:lex "a"+ }+ n8 type:subst [cat:n idx:?Z det:plus qu:minus]![cat:n idx:?Z] + }++ n10[cat:p]![]+ {n11[cat:prep]![]+ {+ n12 type:lex "de"+ } + n13 type:subst [cat:p idx:?Y mode:inf sujidx:?X]![cat:p idx:?Y]+ }+}++ %13 relative sujet qui V sp_a pinf_de+vArity3control:rel0vsp2pinf1(?E ?X ?Y ?Z) auxiliary+n0[cat:n idx:?X det:plus]![cat:n idx:?X]+{+ n1 type:foot [cat:n idx:?X]![cat:n idx:?X]+ n2[cat:p]![]+ {+ n3 type:subst [cat:cl idx:?X det:plus qu:minus]![cat:cl idx:?X]+ n4 type:anchor [cat:v idx:?E]![]+ n6[cat:sp idx:?Y]![]+ { n7[cat:prep]![]+ {+ n8 type:lex "a"+ }+ n9 type:subst [cat:n idx:?Y det:plus qu:minus]![cat:n + ] + }+ n10[cat:p]![]+ {n11[cat:prep]![]+ {+ n12 type:lex "de"+ } + n13 type:subst [cat:p idx:?Y mode:inf sujidx:?X]![cat:p idx:?Y]+ }}}+++%14 relative objet indirect a qui GN v Pinf_de+vArity3control:rel2n0vpinf1(?E ?X ?Y ?Z) auxiliary+n0[cat:n idx:?Y det:plus]![cat:n idx:?Y]+{+ n1 type:foot [cat:n idx:?Y]![cat:n idx:?Y]+ n2[cat:p]![]+ {+ n3[cat:sp idx:?Y det:plus]![]+ { n4[cat:prep]![]+ {+ n5 type:lex "a"+ }+ n6 type:subst [cat:n idx:?Y det:plus qu:plus]![cat:n idx:?Y qu:plus] + }+ n7[cat:p idx:?E]![]+ {+ n8 type:subst [cat:n idx:?X det:plus qu:minus]![cat:n idx:?X qu:minus]+ n9 type:anchor [cat:v idx:?E]![cat:p idx:?E]+ }+ n9[cat:p]![]+ {n10[cat:prep]![]+ {+ n11 type:lex "de"+ } + n12 type:subst [cat:p idx:?Z mode:inf sujidx:?X]![cat:p idx:?Z]+ }+ }}+++vArity3control:rel0vn2pinf1(?E ?X ?Y ?Z) auxiliary+n0[cat:n idx:?X det:plus]![cat:n idx:?X]+{+ n1 type:foot [cat:n idx:?X]![cat:n idx:?X]+ n2[cat:p]![]+ {+ n3 type:subst [cat:cl idx:?X det:plus qu:minus]![cat:n idx:?X]+ n4 type:anchor [cat:v idx:?E]![]+ n6 type:subst [cat:n idx:?Y det:plus qu:minus]![cat:n + ] + n7[cat:p]![]+ {n8[cat:prep]![]+ {+ n9 type:lex "de"+ } + n10 type:subst [cat:p idx:?Z mode:inf sujidx:?X]![cat:p idx:?Z]+ }}}++% relative sujet+vArity3control:rel0vn1sp2(?E ?X ?Y ?Z) auxiliary+n0[cat:n idx:?X det:plus]![cat:n idx:?X]+{+ n1 type:foot [cat:n idx:?X]![cat:n idx:?X]+ n2[cat:p]![]+ {+ n3 type:subst [cat:cl idx:?X det:plus qu:minus]![cat:n idx:?X]+ n4 type:anchor [cat:v idx:?E]![]+ n6 type:subst [cat:n idx:?Z det:plus qu:minus]![cat:n idx:?Z]+ n7[cat:sp idx:?Y]![]+ { n8[cat:p]![]+ {+ n9 type:lex "a"+ }+ n10 type:subst [cat:n idx:?Y det:plus qu:minus]![cat:n] + }+ }+}++% relative objet+vArity3control:rel1vn0sp2(?E ?X ?Y ?Z) auxiliary+n0[cat:n idx:?X det:plus]![cat:n idx:?X]+{+ n1 type:foot [cat:n idx:?X]![cat:n idx:?X]+ n2[cat:p]![]+ {+ n3 type:subst [cat:cl idx:?X det:plus qu:minus]![cat:cl idx:?X]+ n4 type:anchor [cat:v idx:?E]![]+ n6 type:subst [cat:n idx:?Z det:plus qu:minus]![cat:n idx:?Z]+ n7[cat:sp idx:?Y]![]+ { n8[cat:p]![]+ {+ n9 type:lex "a"+ }+ n10 type:subst [cat:n idx:?Y det:plus qu:minus]![cat:n] + }+ }+}++ % relative objet indirect+rel2vn0n1(?E ?X ?Y ?Z) auxiliary+n0[cat:n idx:?Y det:plus]![cat:n idx:?Y]+{+ n1 type:foot [cat:n idx:?Y]![cat:n idx:?Y]+ n2[cat:p]![]+ {+ n3[cat:sp idx:?Y det:plus]![]+ { n4[cat:p]![]+ {+ n5 type:lex "a"+ }+ n6 type:subst [cat:n idx:?Y det:plus qu:plus]![cat:n idx:?Y qu:plus] + }+ n7[cat:p idx:?E]![]+ {+ n8 type:subst [cat:n idx:?X det:plus qu:minus]![cat:n idx:?X qu:minus]+ n9 type:anchor [cat:v idx:?E]![cat:p idx:?E]+ n11 type:subst [cat:n idx:?Z det:plus qu:minus]![cat:n idx:?Z]+ }+ }+}++ % kowey: for persuader instead of promettre+ % (sujidx is set differently)+vArity3controlObj:n0vsp2pinf1b(?E ?X ?Y ?Z) initial+n1[cat:p mode:FIXME]![]+{+ n2 type:subst [cat:n idx:?X det:plus qu:minus]![cat:n idx:?X]+ n3 type:anchor [cat:v idx:?E]![]+ n5[cat:sp idx:?Z det:plus]![]+ { n6[cat:prep]![]+ {+ n7 type:lex "a"+ }+ n8 type:subst [cat:n idx:?Z det:plus qu:minus]![cat:n idx:?Z] + }+ n9[cat:p]![]+ {n10[cat:prep]![]+ {+ n11 type:lex "de"+ } + n12 type:subst [cat:p idx:?Y mode:inf sujidx:?Z]![cat:p idx:?Y]+ }+}++ %15 declarative gn0 persuade gn2 pinf_de1 n0vn2pinf1 +vArity3controlObj:n0vn2pinf1b(?E ?X ?Y ?Z) initial+n1[cat:p mode:FIXME]![]+{+ n2 type:subst [cat:n idx:?X det:plus qu:minus]![cat:n idx:?X]+ n3 type:anchor [cat:v idx:?E]![]+ n5 type:subst [cat:n idx:?Z det:plus qu:minus]![cat:n idx:?Z]+ n6[cat:p]![]+ {n7[cat:prep]![]+ {+ n8 type:lex "de"+ } + n9 type:subst [cat:p idx:?Y mode:inf sujidx:?Z]![cat:p idx:?Y] + }+}++ %15 infinitive persuader gn pinf_de vinfn2pinf1+vArity3controlObj:vinfn2pinf1b(?E ?X ?Y ?Z) initial+n1[cat:p idx:?E mode:inf sujidx:?X]![cat:p] +{+ n2 type:anchor [cat:v idx:?E]![]+ n4 type:subst [cat:n idx:?Z det:plus qu:minus]![cat:n idx:?Z] + n5[cat:p]![]+ {n6[cat:prep]![]+ {+ n7 type:lex "de"+ } + n8 type:subst [cat:p idx:?Y mode:inf sujidx:?Z]![cat:p idx:?Y]+ }+}++ %18 rel-sjt n qui persuade gn pinf_de rel0vn2pinf1+vArity3controlObj:n0vn1sp2(?E ?X ?Y ?Z) initial+n1[cat:p mode:FIXME]![]+{+ n2 type:subst [cat:n idx:?X det:plus qu:minus]![cat:n idx:?X]+ n3 type:anchor [cat:v idx:?E]![]+ n5 type:subst [cat:n idx:?Y det:plus qu:minus]![cat:n idx:?Y]+ n6[cat:sp idx:?Z det:plus]![]+ { n8[cat:p]![]+ {+ n9 type:lex "a"+ }+ n10 type:subst [cat:n idx:?Z det:plus qu:minus]![cat:n idx:?Z+ ] + }+}++% vi: set cinoptions=0,p0:++
+ examples/promettre/morphinfo view
@@ -0,0 +1,33 @@+% associates predicates with feature structures which will be +% unified with the anchor node +%+% Note: if you want to specify features for certain lexical entries+% via the syntactic lexicon, you should do it through the +% interface. This means you'll have to modify your trees so+% that they export the relevant features through their +% interface +%+% john [gender:m]+% mary [gender:f]+%+% since we already have a mechanism for applying fs via the+% syntactic lexicon, i figured we could just keep things+% consistent+%+% the idea is that if you have plural(p)+% then this applies to any tree for which p is the first index++male [gen:m]+female [gen:f]++plural [num:pl] +singular [num:sg]++past [tense:past]+future [tense:future]++% -------------------------------------------------------------------- +% output features+% -------------------------------------------------------------------- +% +
+ examples/promettre/suite view
@@ -0,0 +1,113 @@+il_aime_marie+semantics:[h2:mary(m)+ h1:il(j)+ e1:love(j m)]+[ il aimer Marie ]++jean_aime_marie+semantics:[h1:john(j)+ h2:mary(m)+ e1:love(j m)]+[ Jean aimer Marie ]++jean_aime_le_livre+semantics:[h1:john(j)+ h2:book(l) h2:def(l)+ e1:love(j l)]+[ Jean aimer le livre ]++jean_s_aime+semantics:[h1:john(j)+ e1:love(j j)]+[ Jean se aimer ]++%jean_promet_marie_de_l'aimer+%semantics:[h1:john(j)+% h2:mary(m)+% e1:love(j m)+% e2:promise(j e1 m)]+%[ Jean promettre a Marie de le aimer ]+%[ Jean promettre Marie de le aimer ]++jean_donne_un_livre_a_marie+semantics:[john(j) + mary(m) + book(b) indef(b)+ give(j b m)]+%[ Jean donner a Marie un livre ]+[ Jean donner un livre a Marie ]++jean_promet_a_marie_de_donner_un_livre_a_Claire+semantics:[john(j) + mary(m) + book(b) indef(b)+ claire(c)+ e1:give(j b c) + promise(j e1 m) ]+[ Jean promettre a Marie de donner un livre a Claire ]++jean_promet_a_Marie_de_le_donner_un_livre+semantics:[john(j) + mary(m) + book(b) indef(b)+ e1:give(j b m) + promise(j e1 m) ]+[ Jean promettre a Marie de le donner un livre ]++%semantics:[h1:john(j) +% h2:mary(m) +% h3:book(b) h3:indef(b)+% h4:paul(p)+% e1:give(j b p) +% e2:promise(j e1 m) +% e3:convince(p e2 j) ]+%[ Paul persuader Jean de promettre a Marie de le donner un livre ]++promettre+semantics : [ h1:def(b)+ h1:present(b)+ e:promise(a b c)+ mary(a)+ john(c)+ ]++promettre_donner+semantics : [ e1:promise(a e2 b)+ john(a)+ mary(b)+ e2:give(a c d)+ h1:indef(c)+ h1:book(c)+ paul(d)+ ]++promettre_donner_persuader+semantics : [ e1:promise(a e2 b)+ john(a)+ mary(b)+ e2:convince(a e3 d)+ e3:give(d c e)+ h3:indef(c)+ h3:book(c)+ paul(d)+ claire(e)+ ]++% "Jean promet a Marie de persuader Paul de persuader Pierre de+% persuader Olivier de donner+% un livre a Claire" +pr_per_per_per_do+semantics : [ e1:promise(a e2 b)+ john(a)+ mary(b)+ e2:convince(a e3 d)+ paul(d)+ e3:convince(d e4 e)+ peter(e)+ e4:convince(e e5 o)+ oliver(o)+ e5:give(o l f)+ h1:indef(l)+ h1:book(l)+ claire(f)+ ]
+ examples/xmg-example/Makefile view
@@ -0,0 +1,138 @@+SHELL=/bin/sh+GENI:=geni++COMPILED_GRAMMAR_DIR:=compiled-grammar+GRAMMAR_DIR:=grammar+GRAMMAR:=Evaluations+GRAMMAR_RAW_MG:=$(GRAMMAR_DIR)/$(GRAMMAR).mg++LEXICON_DIR:=lexicon+FULL_LEXICON_PREFIX:=demo-lemma-latin1+MORPH_PREFIX:=demo-morph-latin1++MACROS_FILE:=macros.mac++SUITE_LEXICON:=$(LEXICON_DIR)/$(FULL_LEXICON_PREFIX).lex+SUITE_MORPH:=$(LEXICON_DIR)/$(MORPH_PREFIX).mph++GENI_GRAMMAR:=$(COMPILED_GRAMMAR_DIR)/$(GRAMMAR).genib+GENI_LEXICON:=$(LEXICON_DIR)/$(FULL_LEXICON_PREFIX).glex+GENI_MORPH:=$(LEXICON_DIR)/$(MORPH_PREFIX).gmorph+GENI_SUITE:=suite++GENI_OPTIMISATIONS:='pol f-sem f-root'++GENI_LEX_FLAGS:=-l $(GENI_LEXICON) -s $(GENI_SUITE)+ifdef ENABLE_MORPH+GENI_LEX_FLAGS+=--morphlexicon $(GENI_MORPH)+endif+GENI_FLAGS:=$(GENI_LEX_FLAGS) --opt=$(GENI_OPTIMISATIONS)++# --------------------------------------------------------------------+# main targets, etc+# --------------------------------------------------------------------++all: run++ECHO_STATUS:=@echo "[XMG/GenI demo]"+ifndef VERBOSE+SILENTLY:=@+endif++mg_files := $(wildcard $(GRAMMAR_DIR)/*.mg)++.PHONY: all macros run grammar morph clean++grammar: $(GENI_GRAMMAR)+lexicon: $(GENI_LEXICON)+morph: $(GENI_MORPH)+macros: $(LEXICON_DIR)/$(MACROS_FILE)+++# --------------------------------------------------------------------+# demo stuff+# --------------------------------------------------------------------++NEW_SUMMARY:=new-summary+OLD_DERIVATIONS:=old-derivations+NEW_DERIVATIONS_ORIG:=tmp-derivations+NEW_DERIVATIONS:=new-derivations++run: $(GENI_GRAMMAR) lexicon morph+ $(GENI) -m $< $(GENI_FLAGS) --nogui --batchdir=results++# --------------------------------------------------------------------+# running GenI+# --------------------------------------------------------------------++run-geni: $(GENI_GRAMMAR) lexicon morph+ $(GENI) -m $< $(GENI_FLAGS)++$(SUITE_DIR_GENI)/.geni-input:+ $(NO_SUITE_ERROR)++# --------------------------------------------------------------------+# cleaning+# --------------------------------------------------------------------++clean:+ rm -rf $(COMPILED_GRAMMAR_DIR)/*+ rm -rf $(LEXICON_DIR)/*.gmorph $(LEXICON_DIR)/*.glex $(LEXICON_DIR)/macros.mac $(LEXICON_DIR)/macros.lin $(GRAMMAR_DIR)/macros.mac $(GRAMMAR_DIR)/macros.lin+ rm -rf results/*++# --------------------------------------------------------------------+# grammar+# --------------------------------------------------------------------++# remove double entries+%.rec: %.tmprec+ $(ECHO_STATUS) removing double-entries to produce $@+ $(SILENTLY) CheckTAG $< --all -c $@ -o $(basename $@).xml++# compile the grammar+$(COMPILED_GRAMMAR_DIR)/%.tmprec: $(GRAMMAR_DIR)/%.mg+ $(ECHO_STATUS) compiling metagrammar $<+ $(SILENTLY) MetaTAG $< --chk -c $@++$(COMPILED_GRAMMAR_DIR)/$(GRAMMAR).rec: $(COMPILED_GRAMMAR_DIR)/$(GRAMMAR).tmprec+$(COMPILED_GRAMMAR_DIR)/$(GRAMMAR).tmprec: $(mg_files)++# --------------------------------------------------------------------+# grammar converted to geni format+# --------------------------------------------------------------------++%.xml : %.rec+ :++$(COMPILED_GRAMMAR_DIR):+ mkdir $@++%.geni : %.xml+ $(ECHO_STATUS) "converting to geni text format: $(basename $<)"+ $(SILENTLY) geniconvert -f tagml -t geni $< -o $@++%.genib : %.geni+ $(ECHO_STATUS) "converting to geni binary format: $<"+ $(SILENTLY) geniconvert -f geni -t genib $< -o $@++# --------------------------------------------------------------------+# lexicon+# --------------------------------------------------------------------+# compile semantic macros from SemVal.mg+# see the GNU make manual on implicit rules/automatic variables+# for $(<D), $(<F) and $(@F)+$(GRAMMAR_DIR)/$(MACROS_FILE): $(GRAMMAR_RAW_MG)+ $(ECHO_STATUS) compiling semantic macros only+ $(SILENTLY) MetaTAG $< --mac -s $(basename $@)++$(LEXICON_DIR)/$(MACROS_FILE): $(GRAMMAR_DIR)/$(MACROS_FILE)+ $(SILENTLY) cp -f $< $@++#convert lexicon to geni format using semantic macros+%.glex: %.lex $(LEXICON_DIR)/$(MACROS_FILE)+ $(ECHO_STATUS) lexically converting $<+ $(SILENTLY) lexConverter -L -g -i $< -o $@++#convert lexicon to geni format+%.gmorph: %.mph+ $(SILENTLY) sed -e 's/pos/cat/g' -e 's/=/:/g' -e 's/;/ /g' < $< > $@
+ examples/xmg-example/README view
@@ -0,0 +1,8 @@+This is a small demo using GenI with the rest of the SemTAG toolkit.+You'll need to install:+ XMG+ the various XMG-TOOLS (namely CHECKER)+ lexConverter++But otherwise, it should just be a matter of running:+ make
+ examples/xmg-example/grammar/Arguments.mg view
@@ -0,0 +1,146 @@+class VerbalArgument+export+ ?xS ?xV+declare+ ?xS ?xV+{+ <syn>{+ node ?xS(color=white)[cat = s]{+ node ?xV(color=white)[cat = v]+ }+ }+}++class SubjectAgreement+export+ ?xSubjAgr ?xVAgr+declare+ ?xSubjAgr ?xVAgr ?fX ?fY ?fZ+{+ <syn>{+ node ?xSubjAgr[top=[num = ?fX, gen = ?fY, pers = ?fZ]];+ node ?xVAgr[top=[num = ?fX, gen = ?fY, pers = ?fZ]]+ }+}++class CanSubject+import+ VerbalArgument[]+ SubjectAgreement[]+export+ ?xSubj+declare+ ?xSubj+{+ <syn>{+ node ?xS[cat = s]{+ node ?xSubj(color=red)[cat = @{cl,n}]+ node ?xV[cat = v]+ };+ ?xSubj = ?xSubjAgr;+ ?xV = ?xVAgr+ }+}++class CanonicalSubject+import+ CanSubject[]+ SubjectSem[]+{+ <syn>{+ node ?xSubj(color=red,mark=subst)[cat = n]+ };+ ?xSubj = ?xSem+}++class CliticSubject+import+ CanSubject[]+ SubjectSem[]+{+ <syn>{+ node ?xSubj(color=red,mark=subst)[cat = cl]+ };+ ?xSubj = ?xSem+}++class RelativeSubject+import+ VerbalArgument[]+ SubjectAgreement[]+ SubjectSem[]+declare+ ?xSubj ?xfoot ?xRel ?fU ?fY ?fZ ?xQui+{+ <syn>{+ node ?xRel(color=red)[cat = n,bot=[num = ?fY,gen = ?fZ,pers = ?fU]]{+ node ?xfoot(color=red,mark=foot)[cat=n,top=[num = ?fY,gen = ?fZ,pers = ?fU]]+ node ?xS(mark=nadj){+ node ?xSubj(color=red,extracted = +)[cat=c]{+ node ?xQui(color=red,mark=flex)[cat=qui]+ }+ node ?xV+ }+ }+ };+ ?xfoot = ?xSubjAgr;+ ?xV = ?xVAgr;+ ?xfoot = ?xSem+}++class CanonicalObject+import+ VerbalArgument[]+ ObjectSem[]+export+ ?xObj+declare+ ?xObj+{+ <syn>{+ node ?xS{+ node ?xV+ node ?xObj(mark=subst, color=red)[cat = n]+ }+ };+ ?xObj = ?xSem+}++class CanonicalCAgent+import+ VerbalArgument[]+ CAgentSem[]+export+ ?xtop ?xArg ?xX+declare+ ?xtop ?xArg ?xX ?xPrep+{+ <syn>{+ node ?xtop[cat = pp](color=red){+ node ?xX (color=red)[cat = p] {+ node ?xPrep(mark=flex,color=red)[cat=par]+ }+ node ?xArg(mark=subst,color=red)[cat = n]+ }+ } ;+ ?xArg = ?xSem ;+ <syn>{ ?xS -> ?xtop ; ?xV >> ?xtop }+}+++class Subject[I,L]+{+ CanonicalSubject[]*=[subjectI = I, subjectL = L]+ | RelativeSubject[]*=[subjectI = I, subjectL = L]+ | CliticSubject[]*=[subjectI = I, subjectL = L]+}++class Object[I,L]+{+ CanonicalObject[]*=[objectI = I, objectL = L]+}++class CAgent[I,L]+{+ CanonicalCAgent[]*=[cagentI = I, cagentL = L]+}
+ examples/xmg-example/grammar/Entete.mg view
@@ -0,0 +1,65 @@+% -------------------------------------------------------+% A FRENCH TOY METAGRAMMAR+% -------------------------------------------------------+%+% date = january 2007+%+%+% Demo MetaGrammar inspired by B. Crabbe and C. Gardent French TAG+%+%+% ASSOCIATED FILES (for SemConst):+% demo.lex : lemma database+% demo.mph : morphological database+% +%+% Contact: parmenti@loria.fr+% +% -------------------------------------------------------+++%% Principles instanciation++use color with () dims (syn)+use unicity with (extracted = +) dims (syn)++%% Type declarations++type CAT={n,np,v,vn,s,pp,c,p,cl,par,qui}+type PERSON=[1..3]+type GENDER={m,f}+type NUMBER={sg,pl}+type MODE={ind,subj}++type MARK={subst,foot,none,nadj,anchor,coanchor,flex}+type COLOR ={red,black,white}++type LABEL !+type IDX !++%% Property declarations++property color : COLOR+property mark : MARK+property extracted : bool++%% Feature declarations++feature cat : CAT+feature gen : GENDER+feature num : NUMBER+feature pers : PERSON+feature mode : MODE++feature idx : IDX+feature label: LABEL++%%%%%%%%%%%%%%%%%%%+% MUTUAL EXCLUSION+%%%%%%%%%%%%%%%%%%%+%+% (functionality not used in this toy metagrammar)+%+% mutex MUTEX-SET1 %% Mutex declaration+% mutex MUTEX-SET1 += Class1 %% Mutex filling+% mutex MUTEX-SET1 += Class2
+ examples/xmg-example/grammar/Evaluations.mg view
@@ -0,0 +1,21 @@+%%%%%%% Included files++include Entete.mg+extern rel theta1 theta2 theta3++include VerbMorph.mg+include Arguments.mg+include Misc.mg+include Sem.mg++semantics basicProperty unaryRel binaryRel SubjectSem ObjectSem CAgentSem nSem++%%%%%%% Valuations++value n0V+value n0Vn1++value Copule+value propername+value Clitic+
+ examples/xmg-example/grammar/Misc.mg view
@@ -0,0 +1,32 @@+class propername+import+ nSem[]+declare+ ?xN+{+ <syn>{+ node xN(color=red,mark=anchor)[cat = n,bot=[pers = 3]]+ };+ ?xN = ?xSem+}++class Copule+declare+ ?xV+{+ <syn>{+ node xV(color=red,mark=anchor)[cat = v]+ }+}++class Clitic+import+ nSem[]+declare+ ?xCl+{+ <syn>{+ node xCl(color=red,mark=anchor)[cat = cl]+ };+ ?xCl = ?xSem+}
+ examples/xmg-example/grammar/Sem.mg view
@@ -0,0 +1,80 @@+%% Semantic classes+class basicProperty+export ?E ?L0 ?Rel+declare ?E ?L0 ?Rel+ {+ <sem>{+ L0:Rel(E)+ }+ *=[label0 = L0, rel =Rel,arg0=E]+ }++% unary relation with event variable+class unaryRel+import basicProperty[]+declare ?X ?Theta1+ {+ <sem>{+ L0:Theta1(E,X)+ }+ *=[arg1=X,theta1 =Theta1]+ }++% binary relation with event variable+class binaryRel+import unaryRel[]+declare ?X ?Theta2+ {+ <sem>{+ L0:Theta2(E,X)+ }+ *=[arg2=X,theta2 =Theta2]+ }++% Verb arguments+class SubjectSem+export+ ?xSem+declare+ ?xSem ?X ?L+{+ <syn>{+ node xSem[top=[idx=X,label=L]]+ }*=[subjectI = X,subjectL = L]+}++class ObjectSem+export+ ?xSem+declare+ ?xSem ?X ?L+{+ <syn>{+ node xSem[top=[idx=X,label=L]]+ }*=[objectI = X,objectL = L]+}++class CAgentSem+export+ ?xSem+declare+ ?xSem ?X ?L+{+ <syn>{+ node xSem[top=[idx=X,label=L]]+ }*=[cagentI = X,cagentL = L]+}++%% Noun semantics+class nSem+export+ ?xSem+declare+ ?xSem ?X ?L+{+ <syn>{+ node ?xSem[cat=@{cl,n},top=[idx=X, label=L]]+ };+ basicProperty[]*=[arg0=X]+}+
+ examples/xmg-example/grammar/VerbMorph.mg view
@@ -0,0 +1,78 @@+class VerbalMorphology+export+ ?xS ?xVN ?xV+declare+ ?xS ?xVN ?xV ?I ?L+{+ <syn>{+ node ?xS(color=black)[cat = s, top = [mode=ind]]{+ node ?xVN(color=black)[cat = v]{+ node ?xV(mark=anchor,color=black)[cat = v,top = [idx=I,label=L]]+ }+ }+ }*=[vbI=I,vbL = L]+}++class activeVerbMorphology+import+ VerbalMorphology[]+declare+ ?fY ?fZ ?fW+{+ <syn>{+ node ?xVN[bot=[num = ?fY,gen = ?fZ,pers=?fW]]{+ node ?xV[top=[num = ?fY,gen = ?fZ,pers=?fW]]+ }+ }+}++class passiveVerbMorphology+import+ VerbalMorphology[]+export+ ?xInfl+declare+ ?xInfl ?fX ?fY ?fZ+{+ <syn>{+ node ?xVN[bot=[num = ?fX, gen = ?fY, pers = ?fZ]]{+ node ?xInfl(color=black,mark=subst)[cat = v,top=[num = ?+fX, gen = ?fY, pers = ?fZ]]+ node ?xV(color=black)[cat = v]+ }+ }+}++class dian0Vactive[L,E,X]+{+ Subject[X,L]+ ; activeVerbMorphology[]*=[vbI=E,vbL = L]+}++class dian0Vn1Active[L,E,X,Y]+{+ dian0Vactive[L,E,X]+ ; Object[Y,L]+}++class dian0Vn1Passive[L,E,X,Y]+{+ Subject[Y,L]+ ; CAgent[X,L]+ ; passiveVerbMorphology[]*=[vbI=E,vbL = L]+}++class n0V[L,E,X]+{+ unaryRel[]*=[label0=L,arg0=E,arg1=X];+ dian0Vactive[L,E,X]+}++class n0Vn1[L,E,X,Y]+{+ binaryRel[]*=[label0=L,arg0=E,arg1=X,arg2=Y] ;+ {+ dian0Vn1Active[L,E,X,Y]+ | dian0Vn1Passive[L,E,X,Y]+ }+}
+ examples/xmg-example/grammar/demo-corpus-latin1.txt view
@@ -0,0 +1,6 @@+Jean aime Marie.+Jean est appelé par Marie.+Marie aime Jean.+Marie appelle.+Jean qui appelle aime Marie.+ils appellent.
+ examples/xmg-example/grammar/parse-corpus.sh view
@@ -0,0 +1,16 @@+#!/bin/sh+# shell script containing the command line options+# to use the SemConst in interactive mode+# february 2007+# contact: parmenti@loria.fr++PWD=`pwd`+RESOURCE_DIR=`dirname $PWD`++cd $RESOURCE_DIR+./SemConst.exe --interactive\+ -g ${RESOURCE_DIR}/demo/Evaluations.mg\+ -l ${RESOURCE_DIR}/demo/demo-lemma-latin1.lex\+ -m ${RESOURCE_DIR}/demo/demo-morph-latin1.mph\+ -c ${RESOURCE_DIR}/demo/demo-corpus-latin1.txt\+ -w
+ examples/xmg-example/lexicon/demo-lemma-latin1.lex view
@@ -0,0 +1,113 @@+include macros.mac++*ENTRY: aimer+*CAT: v+*SEM: binaryRel[theta1=agent,rel=aimer,theta2=patient]+*ACC: 1+*FAM: n0Vn1+*FILTERS: []+*EX: {}+*EQUATIONS:+anc -> aux = avoir+anc -> aux-refl = -+*COANCHORS:++*ENTRY: aimer+*CAT: v+*SEM: binaryRel[theta1=agent,rel=aimer,theta2=patient]+*ACC: 1+*FAM: n0Vn1+*FILTERS: []+*EX: {}+*EQUATIONS:+anc -> aux = etre+anc -> aux-refl = ++*COANCHORS:++*ENTRY: appeler+*CAT: v+*SEM: unaryRel[theta1=agent,rel=appeler]+*ACC: 1+*FAM: n0V+*FILTERS: []+*EX: {}+*EQUATIONS:+anc -> aux = avoir+anc -> aux-refl = -+*COANCHORS:++*ENTRY: appeler+*CAT: v+*SEM: binaryRel[theta1=agent,rel=appeler,theta2=patient]+*ACC: 1+*FAM: n0Vn1+*FILTERS: []+*EX: {}+*EQUATIONS:+anc -> aux = avoir+anc -> aux-refl = -+*COANCHORS:++*ENTRY: etre+*CAT: v+*SEM:+*ACC: 1+*FAM: Copule+*FILTERS: []+*EX: {}+*EQUATIONS:+*COANCHORS:++*ENTRY: il+*CAT: cl+*SEM: basicProperty[rel=il]+*ACC: 1+*FAM: Clitic+*FILTERS: []+*EX: {}+*EQUATIONS:+*COANCHORS:++*ENTRY: jean+*CAT: n+*SEM: basicProperty[rel=jean]+*ACC: 1+*FAM: propername+*FILTERS: []+*EX: {}+*EQUATIONS:+anc -> gen = m+anc -> det = ++*COANCHORS:++*ENTRY: marie+*CAT: n+*SEM: basicProperty[rel=marie]+*ACC: 1+*FAM: propername+*FILTERS: []+*EX: {}+*EQUATIONS:+anc -> gen = f+anc -> det = ++*COANCHORS:++*ENTRY: par+*CAT: p+*SEM:+*ACC: 1+*FAM: void+*FILTERS: []+*EX: {}+*EQUATIONS:+*COANCHORS:++*ENTRY: qui+*CAT: c+*SEM:+*ACC: 1+*FAM: void+*FILTERS: []+*EX: {}+*EQUATIONS:+*COANCHORS:
+ examples/xmg-example/lexicon/demo-morph-latin1.mph view
@@ -0,0 +1,24 @@+aime aimer [pos = v; mode = ind; pers = 1; num = sg;]+aime aimer [pos = v; mode = ind; pers = 3; num = sg;]+aiment aimer [pos = v; mode = ind; pers = 3; num = pl;]+aimé aimer [pos = v; mode = ppart; pp-num = sg; pp-gen = m;]+aimée aimer [pos = v; mode = ppart; pp-num = sg; pp-gen = f;]+appelle appeler [pos = v; mode = ind; pers = 1; num = sg;]+appelle appeler [pos = v; mode = ind; pers = 3; num = sg;]+appellent appeler [pos = v; mode = ind; pers = 3; num = pl;]+appelé appeler [pos = v; mode = ppart; pp-num = sg; pp-gen = m;]+appelée appeler [pos = v; mode = ppart; pp-num = sg; pp-gen = f;]+elle il [pos = cl; refl = -; pers = 3; gen = f; num = sg;]+elle lui [pos = cl; refl = -; pers = 3; gen = f; num = sg;]+elle moi [pos = n; gen = f; num = sg;]+elles il [pos = cl; refl = -; pers = 3; gen = f; num = pl;]+elles lui [pos = cl; refl = -; pers = 3; gen = f; num = pl;]+elles moi [pos = n; pers = 3; gen = f; num = pl]+est etre [pos = v; mode = ind; pers = 3; num = sg;]+es etre [pos = v; mode = ind; pers = 2; num = sg;]+il il [pos = cl; refl = -; pers = 3; gen = m; num = sg;]+ils il [pos = cl; refl = -; pers = 3; gen = m; num = pl;]+jean jean [pos = n; det = +; gen = m; num = sg;]+marie marie [pos = n; pers = 3; gen = f; num = sg;]+par par [pos = p;]+qui qui [pos = c;]
+ examples/xmg-example/suite view
@@ -0,0 +1,39 @@+%% Generated by ../semconst2geni.pl at Thu May 31 18:00:44 2007+%% Input file : results/demo.geni++%% 1. jean aime marie+t10+semantics:[A:aimer(B) A:agent(B C) A:patient(B D) E:jean(C) F:marie(D)]+sentence:[jean aime marie]+++%% 2. jean est appelé par marie+t20+semantics:[A:appeler(B) A:agent(B C) A:patient(B D) E:marie(C) F:jean(D)]+sentence:[jean est appelé par marie]+++%% 3. marie aime jean+t30+semantics:[A:aimer(B) A:agent(B C) A:patient(B D) E:marie(C) F:jean(D)]+sentence:[marie aime jean]+++%% 4. marie appelle+t40+semantics:[A:appeler(B) A:agent(B C) D:marie(C)]+sentence:[marie appelle]+++%% 5. jean qui appelle aime marie+t50+semantics:[A:aimer(B) A:agent(B C) A:patient(B D) E:jean(C) F:appeler(G) F:agent(G C) H:marie(D)]+sentence:[jean qui appelle aime marie]+++%% 6. ils appellent+t60+semantics:[A:appeler(B) A:agent(B C) D:il(C)]+sentence:[ils appellent]++
− macstuff/macosx-app
@@ -1,114 +0,0 @@-#!/bin/sh-icnsfile=macstuff/wxmac.icns-infofile=macstuff/Info.plist-bundlename=GenI-rezcomp="/Developer/Tools/Rez -t APPL Carbon.r $rezfile -o"--#-------------------------------------------------------------------------# Helper script to create a MacOS X application from a binary.-# Hacked up with lots of GenI-specific stuff.-# Meant to be run from the bin directory directly.-#-# Daan Leijen and Arthur Baars.-#-# Copyright (c) 2003,2004 Daan Leijen, Arthur Baars-#--------------------------------------------------------------------------# $Id: macosx-app-template,v 1.4 2005/04/29 14:16:51 dleijen Exp $-arg=""--# variables-program=""-verbose="yes"---# Parse command-line arguments-while : ; do- # put optional argument in the $arg variable- case "$1" in- -*=*) arg=`echo "$1" | sed 's/[-_a-zA-Z0-9]*=//'` ;;- *) arg= ;;- esac-- # match on the arguments- case "$1" in- "") break;;- -\?|--help)- echo "usage:"- echo " macosx-app [options] <program (a.out)>"- echo ""- echo "options: [defaults in brackets]"- echo " --help | -? show this information"- echo " --verbose | -v be verbose"- echo ""- exit 1;;- -v|--verbose)- verbose="yes";;- -*) echo "error: Unknown option \"$1\". Use \"--help\" to show valid options." 1>&2- echo "" 1>&2- exit 2;;- *) if test "$program"; then- echo "error: [program] is specified twice. Use \"--help\" to show valid options." 1>&2- echo ""1>&2- exit 2- fi- program="$1";;- esac- shift-done--# default program-if test -z "$program"; then- echo "error: you need to specify a program. Use \"--help\" to show valid options." 1>&2- echo "" 1>&2- exit 2-fi--# show when verbose is true.-show()-{- if test "$verbose" = "yes"; then - echo "$1"- fi-}--# link with default resources-# this is neccesary only to run the GUI from the command line -if test "$rezcomp"; then- show "creating resource:" - show " > $rezcomp $program"- $rezcomp $program-fi--# create a bundle-bundle="$program.app/Contents"--# create bundle directories-show "creating app directories:"-show " - $program.app"-mkdir -p $program.app-show " - $bundle"-mkdir -p $bundle-show " - $bundle/MacOS"-mkdir -p $bundle/MacOS-show " - $bundle/Resources"-mkdir -p $bundle/Resources--cp -f $program $bundle/MacOS/--# copy the icon -cp -f ${icnsfile} $bundle/Resources--# package info-show "creating package info:"-show " - $bundle/PkgInfo"-echo -n "APPL????" > $bundle/PkgInfo--# create program information file-cp ${infofile} $bundle/Info.plist --# tell finder that there's an icon -/Developer/Tools/SetFile -a C $bundle--show "done."-show ""
+ src/EnableGUI.hs view
@@ -0,0 +1,38 @@+-- this is just to get the GUI running on my mac, no big deal+-- note: for Observe.lhs: -fglasgow-exts -cpp -package concurrent++module EnableGUI(enableGUI) where++import Data.Int+import Foreign+import qualified Main as Main2++{-+import Posix+import Concurrent+import Control.Exception+catchCtrlC = do+ main_thread <- myThreadId+ installHandler sigINT (Catch (hupHandler main_thread)) Nothing+ where+ hupHandler :: ThreadId -> IO ()+ hupHandler main_thread+ = throwTo main_thread (ErrorCall "Control-C")+-}++main = do (enableGUI >> Main2.main)++type ProcessSerialNumber = Int64++foreign import ccall "GetCurrentProcess" getCurrentProcess :: Ptr ProcessSerialNumber -> IO Int16+foreign import ccall "_CGSDefaultConnection" cgsDefaultConnection :: IO ()+foreign import ccall "CPSEnableForegroundOperation" cpsEnableForegroundOperation :: Ptr ProcessSerialNumber -> IO ()+foreign import ccall "CPSSignalAppReady" cpsSignalAppReady :: Ptr ProcessSerialNumber -> IO ()+foreign import ccall "CPSSetFrontProcess" cpsSetFrontProcess :: Ptr ProcessSerialNumber -> IO ()++enableGUI = alloca $ \psn -> do+ getCurrentProcess psn+ cgsDefaultConnection+ cpsEnableForegroundOperation psn+ cpsSignalAppReady psn+ cpsSetFrontProcess psn
+ src/MainGeni.lhs view
@@ -0,0 +1,88 @@+% GenI surface realiser+% Copyright (C) 2005 Carlos Areces and Eric Kow+%+% This program is free software; you can redistribute it and/or+% modify it under the terms of the GNU General Public License+% as published by the Free Software Foundation; either version 2+% of the License, or (at your option) any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; if not, write to the Free Software+% Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.++\chapter{Main}++Welcome to the GenI source code. The main module is where everything+starts from. If you're trying to figure out how GenI works, the main+action is in Geni and Tags +(chapters \ref{cha:Geni} and \ref{cha:Tags}). ++\begin{code}+module Main (main) where+\end{code}++\ignore{+\begin{code}+import Data.IORef(newIORef)+import System.Environment(getArgs)++import NLP.GenI.Geni(emptyProgState)+import NLP.GenI.Console(consoleGeni)+import NLP.GenI.Configuration (treatStandardArgs, processInstructions,+ hasFlagP, BatchDirFlg(..), DisableGuiFlg(..), FromStdinFlg(..),+ RegressionTestModeFlg(..), RunUnitTestFlg(..),+ )++#ifndef DISABLE_GUI+import NLP.GenI.Gui(guiGeni)+#else+guiGeni = consoleGeni+#endif+\end{code}+}++In figure \ref{fig:code-outline-main} we show what happens from main: First, we+hand control off to either the console or the graphical user interface. These+functions then do all the business stuff like loading files and figuring out+what to generate. From there, they invoke the the generation step+\fnref{runGeni} which does surface realisation from A-Z. Alternately, the+graphical interface could invoke a graphical debugger which also does surface+realisation from A-Z but allows you to intervene, inspect and stop at each+step.++\begin{figure}+\begin{center}+\includegraphics[scale=0.25]{images/code-outline-main}+\label{fig:code-outline-main}+\caption{How the GenI entry point is used}+\end{center}+\end{figure}++\begin{code}+main :: IO ()+main = do + args <- getArgs+ confArgs <- treatStandardArgs args >>= processInstructions+ let pst = emptyProgState confArgs+ pstRef <- newIORef pst+ let batch = hasFlagP BatchDirFlg confArgs+ console = hasFlagP DisableGuiFlg confArgs+ fromstdin = hasFlagP FromStdinFlg confArgs+ regression = hasFlagP RegressionTestModeFlg confArgs+ unit = hasFlagP RunUnitTestFlg confArgs+ if (fromstdin || console || batch || regression || unit)+ then consoleGeni pstRef+ else guiGeni pstRef+\end{code}++% TODO+% Define what is and what is not exported from the modules. +% In particular in BTypes take care to export the inspection function +% but not the types.+% Re-write functions in Main as needed.+% Change input in Lexicon and Grammar to allow more than one anchor.
+ src/NLP/GenI/Automaton.lhs view
@@ -0,0 +1,140 @@+% GenI surface realiser+% Copyright (C) 2005 Carlos Areces and Eric Kow+%+% This program is free software; you can redistribute it and/or+% modify it under the terms of the GNU General Public License+% as published by the Free Software Foundation; either version 2+% of the License, or (at your option) any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; if not, write to the Free Software+% Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.++\chapter{Automaton}+\label{cha:Automaton}++\begin{code}+module NLP.GenI.Automaton+ ( NFA(..), + finalSt,+ addTrans, lookupTrans,+ automatonPaths, automatonPathSets,+ numStates, numTransitions )+where++import qualified Data.Map as Map+import Data.Maybe (catMaybes)++import NLP.GenI.General (combinations)+\end{code}++This module provides a simple, naive implementation of nondeterministic+finite automata (NFA). The transition function consists of a Map, but +there are also accessor function which help you query the automaton +without worrying about how it's implemented.++\begin{enumerate}+\item The states are a list of lists, not just a simple flat list as + you might expect. This allows you to optionally group your + states into ``columns'' (which is something we use in the + GenI polarity automaton optimisation). If you don't want + columns, you can just make one big group out of all your states.+\item We model the empty an empty transition as the transition on+ Nothing. All other transitions are Just something.+\item I'd love to reuse some other library out there, but Leon P. Smith's+ Automata library requires us to know before-hand the size of our alphabet,+ which is highly unacceptable for this task. +\end{enumerate}++\begin{code}+-- | Note: there are two ways to define the final states.+-- 1. you may define them as a list of states in finalStList+-- 2. you may define them via the isFinalSt function+-- The state list is ignored if you define 'isFinalSt'+data NFA st ab = NFA + { startSt :: st+ , isFinalSt :: Maybe (st -> Bool)+ , finalStList :: [st]+ -- + , transitions :: Map.Map st (Map.Map st [Maybe ab])+ -- see chapter comments about list of list + , states :: [[st]] + }+\end{code}++% ----------------------------------------------------------------------+\section{Building automata}+% ----------------------------------------------------------------------++\fnlabel{finalSt} returns all the final states of an automaton++\begin{code}+finalSt :: NFA st ab -> [st]+finalSt aut =+ case isFinalSt aut of+ Nothing -> finalStList aut+ Just fn -> concatMap (filter fn) (states aut)+\end{code}++\fnlabel{lookupTrans} takes an automaton, a state \fnparam{st1} and an+element \fnparam{ab} of the alphabet; and returns the state that +\fnparam{st1} transitions to via \fnparam{a}, if possible. ++\begin{code}+lookupTrans :: (Ord ab, Ord st) => NFA st ab -> st -> (Maybe ab) -> [st]+lookupTrans aut st ab = Map.keys $ Map.filter (elem ab) subT+ where subT = Map.findWithDefault Map.empty st (transitions aut) +\end{code}++\begin{code}+addTrans :: (Ord ab, Ord st) => NFA st ab -> st -> Maybe ab -> st -> NFA st ab +addTrans aut st1 ab st2 = + aut { transitions = Map.insert st1 newSubT oldT }+ where oldT = transitions aut+ oldSubT = Map.findWithDefault Map.empty st1 oldT + newSubT = Map.insertWith (++) st2 [ab] oldSubT+\end{code}++% ----------------------------------------------------------------------+\section{Exploiting automata}+% ----------------------------------------------------------------------++\fnlabel{automatonPaths} returns all possible paths through an+automaton. Each path is represented as a list of labels.++We assume that the automaton does not have any loops+in it. Maybe it would still work if there were loops, with lazy+evaluation, but I haven't had time to think this through, so only+try it unless you're feeling adventurous.++FIXME: we should write some unit tests and quickchecks for this+\begin{code}+automatonPaths :: (Ord st, Ord ab) => (NFA st ab) -> [[ab]]+automatonPaths aut = concatMap combinations $ map (filter (not.null)) $ automatonPathSets aut++-- | Not quite the set of all paths, but the sets of all transitions+--- FIXME: explain later+automatonPathSets :: (Ord st, Ord ab) => (NFA st ab) -> [[ [ab] ]]+automatonPathSets aut = helper (startSt aut)+ where+ transFor st = Map.lookup st (transitions aut)+ helper st = case transFor st of+ Nothing -> []+ Just subT -> concat [ (next (catMaybes tr) st2) | (st2, tr) <- Map.toList subT ]+ next tr st2 = case helper st2 of+ [] -> [[tr]]+ res -> map (tr :) res+\end{code}++\begin{code}+numStates, numTransitions :: NFA st ab -> Int+numStates = sum . (map length) . states+numTransitions = sum . (map subTotal) . (Map.elems) . transitions+ where subTotal = sum . (map length) . (Map.elems)+\end{code}+
+ src/NLP/GenI/Btypes.lhs view
@@ -0,0 +1,987 @@+% GenI surface realiser+% Copyright (C) 2005 Carlos Areces and Eric Kow+%+% This program is free software; you can redistribute it and/or+% modify it under the terms of the GNU General Public License+% as published by the Free Software Foundation; either version 2+% of the License, or (at your option) any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; if not, write to the Free Software+% Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.++\chapter{Btypes}+\label{cha:Btypes}++This module provides basic datatypes like GNode, as well as operations+on trees, nodes and semantics. Things here are meant to be relatively+low-level and primitive (well, with the exception of feature structure+unification, that is).++\begin{code}+{-# LANGUAGE TypeSynonymInstances, FlexibleInstances, DeriveDataTypeable #-}+module NLP.GenI.Btypes(+ -- Datatypes+ GNode(..), GType(Subs, Foot, Lex, Other), NodeName,+ Ttree(..), MTtree, SemPols, TestCase(..),+ Ptype(Initial,Auxiliar,Unspecified),+ Pred, Flist, AvPair, GeniVal(..),+ Lexicon, ILexEntry(..), MorphLexEntry, Macros, Sem, LitConstr, SemInput, Subst,+ emptyLE, emptyGNode, emptyMacro,++ -- GNode stuff+ gCategory, showLexeme, lexemeAttributes, gnnameIs,++ -- Functions from Tree GNode+ plugTree, spliceTree,+ root, rootUpd, foot, setLexeme, setAnchor,++ -- Functions from Sem+ toKeys, subsumeSem, sortSem, showSem, showPred,+ emptyPred,++ -- Functions from Flist+ sortFlist, unify, unifyFeat, mergeSubst,+ showFlist, showPairs, showAv,++ -- Other functions+ Replacable(..), replaceOneAsMap,+ Collectable(..), Idable(..),+ alphaConvert, alphaConvertById,+ fromGConst, fromGVar,+ isConst, isVar, isAnon,++ -- Polarities++ -- Tests+ prop_unify_anon, prop_unify_self, prop_unify_sym+) where+\end{code}++\ignore{+\begin{code}+-- import Debug.Trace -- for test stuff+import Control.Monad (liftM)+import Data.List+import Data.Maybe (fromMaybe, isJust, mapMaybe)+import Data.Generics (Data)+import Data.Typeable (Typeable)+import qualified Data.Map as Map+import qualified Data.Set as Set+import Data.Tree+import Test.QuickCheck hiding (collect) -- needed for testing via ghci++import NLP.GenI.General(map', filterTree, listRepNode, snd3, geniBug, comparing)+--instance Show (IO()) where+-- show _ = ""+\end{code}+}++% ----------------------------------------------------------------------+\section{Grammar}+% ----------------------------------------------------------------------++A grammar is composed of some unanchored trees (macros) and individual+lexical entries. The trees are grouped into families. Every lexical+entry is associated with a single family. See section section+\ref{sec:combine_macros} for the process that combines lexical items+and trees into a set of anchored trees.++\begin{code}+type MTtree = Ttree GNode+type Macros = [MTtree]++data Ttree a = TT+ { params :: [GeniVal]+ , pfamily :: String+ , pidname :: String+ , pinterface :: Flist+ , ptype :: Ptype+ , psemantics :: Maybe Sem+ , ptrace :: [String]+ , tree :: Tree a }+ deriving (Show, Data, Typeable)++data Ptype = Initial | Auxiliar | Unspecified+ deriving (Show, Eq, Data, Typeable)++instance (Replacable a) => Replacable (Ttree a) where+ replaceMap s mt =+ mt { params = replaceMap s (params mt)+ , tree = replaceMap s (tree mt)+ , pinterface = replaceMap s (pinterface mt)+ , psemantics = replaceMap s (psemantics mt) }+ replaceOne = replaceOneAsMap++instance (Collectable a) => Collectable (Ttree a) where+ collect mt = (collect $ params mt) . (collect $ tree mt) .+ (collect $ psemantics mt) . (collect $ pinterface mt)++-- | A null tree which you can use for various debugging or display purposes.+emptyMacro :: MTtree+emptyMacro = TT { params = [],+ pidname = "",+ pfamily = "",+ pinterface = [],+ ptype = Unspecified,+ psemantics = Nothing,+ ptrace = [],+ tree = Node emptyGNode []+ }+\end{code}++\paragraph{Lexical entries}++\begin{code}+-- | A lexicon maps semantic predicates to lexical entries.+type Lexicon = Map.Map String [ILexEntry]+type SemPols = [Int]+data ILexEntry = ILE+ { -- normally just a singleton, useful for merging synonyms+ iword :: [String]+ , ifamname :: String+ , iparams :: [GeniVal]+ , iinterface :: Flist+ , ifilters :: Flist+ , iequations :: Flist+ , iptype :: Ptype+ , isemantics :: Sem+ , isempols :: [SemPols] }+ deriving (Show, Eq, Data, Typeable)++instance Replacable ILexEntry where+ replaceMap s i =+ i { iinterface = replaceMap s (iinterface i)+ , iequations = replaceMap s (iequations i)+ , isemantics = replaceMap s (isemantics i)+ , iparams = replaceMap s (iparams i) }+ replaceOne = replaceOneAsMap++instance Collectable ILexEntry where+ collect l = (collect $ iinterface l) . (collect $ iparams l) .+ (collect $ ifilters l) . (collect $ iequations l) .+ (collect $ isemantics l)++emptyLE :: ILexEntry+emptyLE = ILE { iword = [],+ ifamname = "",+ iparams = [],+ iinterface = [],+ ifilters = [],+ iptype = Unspecified,+ isemantics = [],+ iequations = [],+ isempols = [] }+\end{code}++\begin{code}+type MorphLexEntry = (String,String,Flist)+\end{code}++% ----------------------------------------------------------------------+\section{GNode}+% ----------------------------------------------------------------------++A GNode is a single node of a syntactic tree. It has a name (gnname),+top and bottom feature structures (gup, gdown), a lexeme+(ganchor, glexeme: False and empty string if n/a), and some flags+information (gtype, gaconstr).++\begin{code}+data GNode = GN{gnname :: NodeName,+ gup :: Flist,+ gdown :: Flist,+ ganchor :: Bool,+ glexeme :: [String],+ gtype :: GType,+ gaconstr :: Bool,+ gorigin :: String -- ^ for TAG, this would be the elementary tree+ -- that this node originally came from+ }+ deriving (Eq, Data, Typeable)++-- Node type used during parsing of the grammar+data GType = Subs | Foot | Lex | Other+ deriving (Show, Eq, Data, Typeable)++type NodeName = String++-- | A null 'GNode' which you can use for various debugging or display purposes.+emptyGNode :: GNode+emptyGNode = GN { gnname = "",+ gup = [], gdown = [],+ ganchor = False,+ glexeme = [],+ gtype = Other,+ gaconstr = False,+ gorigin = "" }++gnnameIs :: NodeName -> GNode -> Bool+gnnameIs n = (== n) . gnname+\end{code}++A TAG node may have a category. In the core GenI algorithm, there is nothing+which distinguishes the category from any other attributes. But for some+other uses, such as checking if it is a result or for display purposes, we+do treat this attribute differently. We take here the convention that the+category of a node is associated to the attribute ``cat''.+\begin{code}+-- | Return the value of the "cat" attribute, if available+gCategory :: Flist -> Maybe GeniVal+gCategory top =+ case [ v | (a,v) <- top, a == "cat" ] of+ [] -> Nothing+ [c] -> Just c+ _ -> geniBug $ "Impossible case: node with more than one category"+\end{code}++A TAG node might also have a lexeme. If we are lucky, this is explicitly+set in the glexeme field of the node. Otherwise, we try to guess it from+a list of distinguished attributes (in order of preference).+\begin{code}+-- | Attributes recognised as lexemes, in order of preference+lexemeAttributes :: [String]+lexemeAttributes = [ "lex", "phon", "cat" ]+\end{code}++\paragraph{show (GNode)} the default show for GNode tries to+be very compact; it only shows the value for cat attribute+and any flags which are marked on that node.++\begin{code}+instance Show GNode where+ show gn =+ let cat_ = case gCategory.gup $ gn of+ Nothing -> []+ Just c -> show c+ lex_ = showLexeme $ glexeme gn+ --+ stub = concat $ intersperse ":" $ filter (not.null) [ cat_, lex_ ]+ extra = case (gtype gn) of+ Subs -> " !"+ Foot -> " *"+ _ -> if (gaconstr gn) then " #" else ""+ in stub ++ extra++-- FIXME: will have to think of nicer way - one which involves+-- unpacking the trees :-(+showLexeme :: [String] -> String+showLexeme [] = ""+showLexeme [l] = l+showLexeme xs = concat $ intersperse "|" xs+\end{code}++A Replacement on a GNode consists of replacements on its top and bottom+feature structures++\begin{code}+instance Replacable GNode where+ replaceOne s gn =+ gn { gup = replaceOne s (gup gn)+ , gdown = replaceOne s (gdown gn) }+ replaceMap s gn =+ gn { gup = replaceMap s (gup gn)+ , gdown = replaceMap s (gdown gn) }+\end{code}++% ----------------------------------------------------------------------+\section{Tree manipulation}+% ----------------------------------------------------------------------++\begin{code}+instance (Replacable a) => Replacable (Tree a) where+ replaceOne s t = fmap (replaceOne s) t+ replaceMap s t = fmap (replaceMap s) t+\end{code}++Projector and Update function for Tree++\begin{code}+root :: Tree a -> a+root (Node a _) = a++rootUpd :: Tree a -> a -> Tree a+rootUpd (Node _ l) b = (Node b l)++foot :: Tree GNode -> GNode+foot t = case filterTree (\n -> gtype n == Foot) t of+ [x] -> x+ _ -> geniBug $ "foot returned weird result"+\end{code}++\begin{code}+-- | Given a lexical item @s@ and a Tree GNode t, returns the tree t'+-- where l has been assigned to the anchor node in t'+setAnchor :: [String] -> Tree GNode -> Tree GNode+setAnchor s t =+ let filt (Node a []) = (gtype a == Lex && ganchor a)+ filt _ = False+ in case listRepNode (setLexeme s) filt [t] of+ ([r],True) -> r+ _ -> geniBug $ "setLexeme " ++ show s ++ " returned weird result"++-- | Given a lexical item @l@ and a tree node @n@ (actually a subtree+-- with no children), return the same node with the lexical item as+-- its unique child. The idea is that it converts terminal lexeme nodes+-- into preterminal nodes where the actual terminal is the given lexical+-- item+setLexeme :: [String] -> Tree GNode -> Tree GNode+setLexeme l (Node a []) = Node a [ Node subanc [] ]+ where subanc = emptyGNode { gnname = '_' : ((gnname a) ++ ('.' : (concat l)))+ , gaconstr = True+ , glexeme = l}+setLexeme _ _ = geniBug "impossible case in setLexeme - subtree with kids"+\end{code}++\subsection{Substitution and Adjunction}++This module handles the strictly syntactic part of the TAG substitution and+adjunction. We do substitution with a very general \fnreflite{plugTree}+function, whose only job is to plug two trees together at a specified node.+Note that this function is also used to implement adjunction.++\begin{code}+-- | Plug the first tree into the second tree at the specified node.+-- Anything below the second node is silently discarded.+-- We assume the trees are pluggable; it is treated as a bug if+-- they are not!+plugTree :: Tree NodeName -> NodeName -> Tree NodeName -> Tree NodeName+plugTree male n female =+ case listRepNode (const male) (nmatch n) [female] of+ ([r], True) -> r+ _ -> geniBug $ "unexpected plug failure at node " ++ n++-- | Given two trees 'auxt' and 't', splice the tree 'auxt' into+-- 't' via the TAG adjunction rule.+spliceTree :: NodeName -- ^ foot node of the aux tree+ -> Tree NodeName -- ^ aux tree+ -> NodeName -- ^ place to adjoin in target tree+ -> Tree NodeName -- ^ target tree+ -> Tree NodeName+spliceTree f auxT n targetT =+ case findSubTree n targetT of -- excise the subtree at n+ Nothing -> geniBug $ "Unexpected adjunction failure. " +++ "Could not find node " ++ n ++ " of target tree."+ Just eT -> -- plug the excised bit into the aux+ let auxPlus = plugTree eT f auxT+ -- plug the augmented aux at n+ in plugTree auxPlus n targetT++nmatch :: NodeName -> Tree NodeName -> Bool+nmatch n (Node a _) = a == n++findSubTree :: NodeName -> Tree NodeName -> Maybe (Tree NodeName)+findSubTree n n2@(Node x ks)+ | x == n = Just n2+ | otherwise = case mapMaybe (findSubTree n) ks of+ [] -> Nothing+ (h:_) -> Just h+\end{code}++% ----------------------------------------------------------------------+\section{Features and variables}+% ----------------------------------------------------------------------++\begin{code}+type Flist = [AvPair]+type AvPair = (String,GeniVal)+\end{code}++\subsection{GeniVal}++\begin{code}+data GeniVal = GConst [String]+ | GVar !String+ | GAnon+ deriving (Eq,Ord, Data, Typeable)++instance Show GeniVal where+ show (GConst x) = concat $ intersperse "|" x+ show (GVar x) = '?':x+ show GAnon = "?_"++isConst :: GeniVal -> Bool+isConst (GConst _) = True+isConst _ = False++isVar :: GeniVal -> Bool+isVar (GVar _) = True+isVar _ = False++isAnon :: GeniVal -> Bool+isAnon GAnon = True+isAnon _ = False++-- | (assumes that it's a GConst!)+fromGConst :: GeniVal -> [String]+fromGConst (GConst x) = x+fromGConst x = error ("fromGConst on " ++ show x)++-- | (assumes that it's a GVar!)+fromGVar :: GeniVal -> String+fromGVar (GVar x) = x+fromGVar x = error ("fromGVar on " ++ show x)+\end{code}++\subsection{Collectable}++A Collectable is something which can return its variables as a set.+By variables, what I most had in mind was the GVar values in a+GeniVal. This notion is probably not very useful outside the context of+alpha-conversion task, but it seems general enough that I'll keep it+around for a good bit, until either some use for it creeps up, or I find+a more general notion that I can transform this into.++\begin{code}+class Collectable a where+ collect :: a -> Set.Set String -> Set.Set String++instance Collectable a => Collectable (Maybe a) where+ collect Nothing s = s+ collect (Just x) s = collect x s++instance (Collectable a => Collectable [a]) where+ collect l s = foldr collect s l++instance (Collectable a => Collectable (Tree a)) where+ collect = collect.flatten++-- Pred is what I had in mind here+instance ((Collectable a, Collectable b, Collectable c)+ => Collectable (a,b,c)) where+ collect (a,b,c) = collect a . collect b . collect c++instance Collectable GeniVal where+ collect (GVar v) s = Set.insert v s+ collect _ s = s++instance Collectable (String,GeniVal) where+ collect (_,b) = collect b++instance Collectable GNode where+ collect n = (collect $ gdown n) . (collect $ gup n)+\end{code}++\subsection{Replacable}+\label{sec:replacable}+\label{sec:replacements}++The idea of replacing one variable value with another is something that+appears all over the place in GenI. So we try to smooth out its use by+making a type class out of it.++\begin{code}+class Replacable a where+ replace :: Subst -> a -> a+ replace = replaceMap++ replaceMap :: Map.Map String GeniVal -> a -> a++ replaceOne :: (String,GeniVal) -> a -> a++ -- | Here it is safe to say (X -> Y; Y -> Z) because this would be crushed+ -- down into a final value of (X -> Z; Y -> Z)+ replaceList :: [(String,GeniVal)] -> a -> a+ replaceList = replaceMap . foldl update Map.empty+ where+ update m (s1,s2) = Map.insert s1 s2 $ Map.map (replaceOne (s1,s2)) m++-- | Default implementation for replaceOne but not a good idea for the+-- core stuff; which is why it is not a typeclass default+replaceOneAsMap :: Replacable a => (String, GeniVal) -> a -> a+replaceOneAsMap s = replaceMap (uncurry Map.singleton s)++instance (Replacable a => Replacable (Maybe a)) where+ replaceMap s = liftM (replaceMap s)+ replaceOne s = liftM (replaceOne s)+\end{code}++GeniVal is probably the simplest thing you would one to apply a+substitution on++\begin{code}+instance Replacable GeniVal where+ replaceMap m v@(GVar v_) = {-# SCC "replaceMap" #-} Map.findWithDefault v v_ m+ replaceMap _ v = {-# SCC "replaceMap" #-} v++ replaceOne (s1, s2) (GVar v_) | v_ == s1 = {-# SCC "replaceOne" #-} s2+ replaceOne _ v = {-# SCC "replaceOne" #-} v+\end{code}++Substitution on list consists of performing substitution on+each item. Each item, is independent of the other,+of course.++\begin{code}+instance (Replacable a => Replacable [a]) where+ replaceMap s = {-# SCC "replaceMap" #-} map' (replaceMap s)+ replaceOne s = {-# SCC "replaceOne" #-} map' (replaceOne s)+\end{code}++Substitution on an attribute/value pairs consists of ignoring+the attribute and performing substitution on the value.++\begin{code}+instance Replacable AvPair where+ replaceMap s (a,v) = {-# SCC "replaceMap" #-} (a, replaceMap s v)+ replaceOne s (a,v) = {-# SCC "replaceOne" #-} (a, replaceOne s v)++instance Replacable (String, ([String], Flist)) where+ replaceMap s (n,(a,v)) = {-# SCC "replaceMap" #-} (n,(a, replaceMap s v))+ replaceOne s (n,(a,v)) = {-# SCC "replaceOne" #-} (n,(a, replaceOne s v))+\end{code}++\subsection{Idable}++An Idable is something that can be mapped to a unique id.+You might consider using this to implement Ord, but I won't.+Note that the only use I have for this so far (20 dec 2005)+is in alpha-conversion.++\begin{code}+class Idable a where+ idOf :: a -> Integer+\end{code}++\subsection{Other feature and variable stuff}++Our approach to $\alpha$-conversion works by appending a unique suffix+to all variables in an object. See section \ref{sec:fs_unification} for+why we want this.++\begin{code}+alphaConvertById :: (Collectable a, Replacable a, Idable a) => a -> a+alphaConvertById x = {-# SCC "alphaConvertById" #-}+ alphaConvert ('-' : (show . idOf $ x)) x++alphaConvert :: (Collectable a, Replacable a) => String -> a -> a+alphaConvert suffix x = {-# SCC "alphaConvert" #-}+ let vars = Set.elems $ collect x Set.empty+ convert v = GVar (v ++ suffix)+ subst = Map.fromList $ map (\v -> (v, convert v)) vars+ in replaceMap subst x+\end{code}++\begin{code}+-- | Sort an Flist according with its attributes+sortFlist :: Flist -> Flist+sortFlist = sortBy (comparing fst)++showFlist :: Flist -> String+showFlist f = "[" ++ showPairs f ++ "]"++showPairs :: Flist -> String+showPairs = unwords . map showAv++showAv :: AvPair -> String+showAv (y,z) = y ++ ":" ++ show z+\end{code}++% ----------------------------------------------------------------------+\section{Semantics}+\label{btypes_semantics}+% ----------------------------------------------------------------------++\begin{code}+-- handle, predicate, parameters+type Pred = (GeniVal, GeniVal, [GeniVal])+type Sem = [Pred]+type LitConstr = (Pred, [String])+type SemInput = (Sem,Flist,[LitConstr])+type Subst = Map.Map String GeniVal++data TestCase = TestCase+ { tcName :: String+ , tcSemString :: String -- ^ for gui+ , tcSem :: SemInput+ , tcExpected :: [String] -- ^ expected results (for testing)+ , tcOutputs :: [(String, Map.Map (String,String) [String])]+ -- ^ results we actually got, and their traces (for testing)+ } deriving Show++emptyPred :: Pred+emptyPred = (GAnon,GAnon,[])+\end{code}++A replacement on a predicate is just a replacement on its parameters++\begin{code}+instance Replacable Pred where+ replaceMap s (h, n, lp) = (replaceMap s h, replaceMap s n, replaceMap s lp)+ replaceOne s (h, n, lp) = (replaceOne s h, replaceOne s n, replaceOne s lp)+\end{code}++\begin{code}+showSem :: Sem -> String+showSem l =+ "[" ++ (unwords $ map showPred l) ++ "]"++showPred :: Pred -> String+showPred (h, p, l) = showh ++ show p ++ "(" ++ unwords (map show l) ++ ")"+ where+ hideh (GConst [x]) = "genihandle" `isPrefixOf` x+ hideh _ = False+ --+ showh = if (hideh h) then "" else (show h) ++ ":"+\end{code}++\begin{code}+-- | Given a Semantics, return the string with the proper keys+-- (propsymbol+arity) to access the agenda+toKeys :: Sem -> [String]+toKeys l = map (\(_,prop,par) -> show prop ++ (show $ length par)) l+\end{code}++\subsection{Semantic subsumption}+\label{fn:subsumeSem}++FIXME: comment fix++Given tsem the input semantics, and lsem the semantics of a potential+lexical candidate, returns a list of possible ways that the lexical+semantics could subsume the input semantics. We return a pair with+the semantics that would result from unification\footnote{We need to+do this because there may be anonymous variables}, and the+substitutions that need to be propagated throughout the rest of the+lexical item later on.++Note: we return more than one possible substitution because s could be+different subsets of ts. Consider, for example, \semexpr{love(j,m),+ name(j,john), name(m,mary)} and the candidate \semexpr{name(X,Y)}.++TODO WE ASSUME BOTH SEMANTICS ARE ORDERED and that the input semantics is+non-empty.++\begin{code}+subsumeSem :: Sem -> Sem -> [(Sem,Subst)]+subsumeSem tsem lsem =+ subsumeSemHelper ([],Map.empty) (reverse tsem) (reverse lsem)+\end{code}++This is tricky because each substep returns multiple results. We solicit+the help of accumulators to keep things from getting confused.++\begin{code}+subsumeSemHelper :: (Sem,Subst) -> Sem -> Sem -> [(Sem,Subst)]+subsumeSemHelper _ [] _ =+ error "input semantics is non-empty in subsumeSemHelper"+subsumeSemHelper acc _ [] = [acc]+subsumeSemHelper acc tsem (hd:tl) =+ let (accSem,accSub) = acc+ -- does the literal hd subsume the input semantics?+ pRes = subsumePred tsem hd+ -- toPred reconstructs the literal hd with new parameters p.+ -- The head of the list is taken to be the handle.+ toPred p = (head p, snd3 hd, tail p)+ -- next adds a result from predication subsumption to+ -- the accumulators and goes to the next recursive step+ next (p,s) = subsumeSemHelper acc2 tsem2 tl2+ where tl2 = replace s tl+ tsem2 = replace s tsem+ acc2 = (toPred p : accSem, mergeSubst accSub s)+ in concatMap next pRes+\end{code}++\fnlabel{subsumePred}+The first Sem s1 and second Sem s2 are the same when we start we circle on s2+looking for a match for Pred, and meanwhile we apply the partical substitutions+to s1. Note: we treat the handle as if it were a parameter.++\begin{code}+subsumePred :: Sem -> Pred -> [([GeniVal],Subst)]+subsumePred [] _ = []+subsumePred ((h1, p1, la1):l) (pred2@(h2,p2,la2)) =+ -- if we found the proper predicate+ if ((p1 == p2) && (length la1 == length la2))+ then let mrs = unify (h1:la1) (h2:la2)+ next = subsumePred l pred2+ in maybe next (:next) mrs+ else if (p1 < p2) -- note that the semantics have to be reversed!+ then []+ else subsumePred l pred2+\end{code}++\subsection{Other semantic stuff}++\begin{code}+-- | Sort semantics first according to its predicate, and then to its handles.+sortSem :: Sem -> Sem+sortSem = sortBy (\(h1,p1,a1) (h2,p2,a2) -> compare (p1, h1:a1) (p2, h2:a2))+\end{code}++% --------------------------------------------------------------------+\subsection{Unification}+\label{sec:fs_unification}+% --------------------------------------------------------------------++Feature structure unification takes two feature lists as input. If it+fails, it returns Nothing. Otherwise, it returns a tuple with:++\begin{enumerate}+\item a unified feature structure list+\item a list of variable replacements that will need to be propagated+ across other feature structures with the same variables+\end{enumerate}++Unification fails if, at any point during the unification process, the+two lists have different constant values for the same attribute.+For example, unification fails on the following inputs because they have+different values for the \textit{number} attribute:++\begin{quotation}+\fs{\it cat:np\\ \it number:3\\}+\fs{\it cat:np\\ \it number:2\\}+\end{quotation}++Note that the following input should also fail as a result on the+coreference on \textit{?X}.++\begin{quotation}+\fs{\it cat:np\\ \it one: 1\\ \it two:2\\}+\fs{\it cat:np\\ \it one: ?X\\ \it two:?X\\}+\end{quotation}++On the other hand, any other pair of feature lists should unify+succesfully, even those that do not share the same attributes.+Below are some examples of successful unifications:++\begin{quotation}+\fs{\it cat:np\\ \it one: 1\\ \it two:2\\}+\fs{\it cat:np\\ \it one: ?X\\ \it two:?Y\\}+$\rightarrow$+\fs{\it cat:np\\ \it one: 1\\ \it two:2\\},+\end{quotation}++\begin{quotation}+\fs{\it cat:np\\ \it number:3\\}+\fs{\it cat:np\\ \it case:nom\\}+$\rightarrow$+\fs{\it cat:np\\ \it case:nom\\ \it number:3\\},+\end{quotation}++\fnlabel{unifyFeat} is an implementation of feature structure+unification. It makes the following assumptions:++\begin{itemize}+\item Features are ordered++\item The Flists do not share variables!!!++ More precisely, if the two Flists have the same variable, they+ will have the same value. Though this behaviour may not be+ desirable, we don't really care because we never encounter the+ situation (see page \pageref{par:lexSelection}).+\end{itemize}++\begin{code}+unifyFeat :: (Monad m) => Flist -> Flist -> m (Flist, Subst)+unifyFeat f1 f2 =+ {-# SCC "unification" #-}+ let (att, val1, val2) = alignFeat f1 f2+ in att `seq`+ do (res, subst) <- unify val1 val2+ return (zip att res, subst)+\end{code}++\fnlabel{alignFeat}++The less trivial case is when neither list is empty. If we are looking+at the same attribute, then we transfer control to the helper function.+Otherwise, we remove the (alphabetically) smaller att-val pair, add it+to the results, and move on. This only works if the lists are+alphabetically sorted beforehand!++\begin{code}+alignFeat :: Flist -> Flist -> ([String], [GeniVal], [GeniVal])+alignFeat [] [] = ([], [], [])++alignFeat [] ((f,v):x) =+ case alignFeat [] x of+ (att, left, right) -> (f:att, GAnon:left, v:right)++alignFeat x [] =+ case alignFeat [] x of+ (att, left, right) -> (att, right, left)++alignFeat fs1@((f1, v1):l1) fs2@((f2, v2):l2)+ | f1 == f2 = case alignFeat l1 l2 of+ (att, left, right) -> (f1:att, v1:left, v2:right)+ | f1 < f2 = case alignFeat l1 fs2 of+ (att, left, right) -> (f1:att, v1:left, GAnon:right)+ | f1 > f2 = case alignFeat fs1 l2 of+ (att, left, right) -> (f2:att, GAnon:left, v2:right)+ | otherwise = error "Feature structure unification is badly broken"+\end{code}++\subsection{Unification}++\fnlabel{unify} performs unification on two lists of GeniVal. If+unification succeeds, it returns \verb!Just (r,s)! where \verb!r! is+the result of unification and \verb!s! is a list of substitutions that this+unification results in.++Notes:+\begin{itemize}+\item there may be multiple results because of disjunction+\item we need to return \verb!r! because of anonymous variables+\item the lists need not be same length; we just assume you want+ the longer of the two+\end{itemize}++The core unification algorithm follows these rules in order:++\begin{enumerate}+\item if either h1 or h2 are anonymous, we add the other to the result,+ and we don't add any replacements.+\item if h1 is a variable then we replace it by h2,+ regardless of whether or not h2 is a variable+\item if h2 is a variable then we replace it by h1+\item if neither h1 and h2 are variables, but they match, we arbitarily+ add one of them to the result, but we don't add any replacements.+\item if neither are variables and they do \emph{not} match, we fail+\end{enumerate}++\begin{code}+unify :: (Monad m) => [GeniVal] -> [GeniVal] -> m ([GeniVal], Subst)+unify [] l2 = {-# SCC "unification" #-} return (l2, Map.empty)+unify l1 [] = {-# SCC "unification" #-} return (l1, Map.empty)+unify (h1:t1) (h2:t2) | h1 == h2 = {-# SCC "unification" #-} unifySansRep h1 t1 t2+unify (GAnon:t1) (h2:t2) = {-# SCC "unification" #-} unifySansRep h2 t1 t2+unify (h1:t1) (GAnon:t2) = {-# SCC "unification" #-} unifySansRep h1 t1 t2+unify (h1@(GVar _):t1) (h2:t2) = {-# SCC "unification" #-} unifyWithRep h1 h2 t1 t2+unify (h1:t1) (h2@(GVar _):t2) = {-# SCC "unification" #-} unifyWithRep h2 h1 t1 t2+-- special cases for efficiency only+unify ((GConst [_]):_) ((GConst [_]):_) = {-# SCC "unification" #-}+ fail "unification failure"+-- end special efficiency-only cases+unify ((GConst h1v):t1) ((GConst h2v):t2) = {-# SCC "unification" #-}+ case h1v `intersect` h2v of+ [] -> fail "unification failure"+ newH -> unifySansRep (GConst newH) t1 t2+{-# INLINE unifySansRep #-}+{-# INLINE unifyWithRep #-}+unifySansRep :: (Monad m) => GeniVal -> [GeniVal] -> [GeniVal] -> m ([GeniVal], Subst)+unifySansRep x2 t1 t2 = {-# SCC "unification" #-}+ do (res,subst) <- unify t1 t2+ return (x2:res, subst)++unifyWithRep :: (Monad m) => GeniVal -> GeniVal -> [GeniVal] -> [GeniVal] -> m ([GeniVal], Subst)+unifyWithRep (GVar h1) x2 t1 t2 = {-# SCC "unification" #-}+ let s = (h1,x2)+ t1_ = replaceOne s t1+ t2_ = replaceOne s t2+ ustep = unify t1_ t2_+ in s `seq` t1_ `seq` t2_ `seq` ustep `seq`+ (ustep >>= \(res,subst) -> return (x2:res, prependToSubst s subst))+unifyWithRep _ _ _ _ = geniBug "unification error"+\end{code}++\begin{code}+-- | Note that the first Subst is assumed to come chronologically+-- before the second one; so merging @{ X -> Y }@ and @{ Y -> 3 }@+-- should give us @{ X -> 3; Y -> 3 }@;+--+-- See 'prependToSubst' for a warning!+mergeSubst :: Subst -> Subst -> Subst+mergeSubst sm1 sm2 = Map.foldWithKey (curry prependToSubst) sm2 sm1++-- | Add to variable replacement to a 'Subst' that logical comes before+-- the other stuff in it. So for example, if we have @Y -> foo@+-- and we want to insert @X -> Y@, we notice that, in fact, @Y@ has+-- already been replaced by @foo@, so we add @X -> foo@ instead+--+-- Note that it is undefined if you try to append something like+-- @Y -> foo@ to @Y -> bar@, because that would mean that unification+-- is broken+prependToSubst :: (String,GeniVal) -> Subst -> Subst+prependToSubst (v, gr@(GVar r)) sm+ | isJust $ Map.lookup v sm = geniBug $ "prependToSubst: Eric broke unification. Prepending " ++ v ++ " twice."+ | otherwise = Map.insert v gr2 sm+ where gr2 = fromMaybe gr $ Map.lookup r sm+prependToSubst (v, gr) sm = Map.insert v gr sm+\end{code}++\subsubsection{Unification tests} The unification algorithm should satisfy+the following properties:++Unifying something with itself should always succeed++\begin{code}+prop_unify_self :: [GeniVal] -> Property+prop_unify_self x =+ (all qc_not_empty_GConst) x ==>+ case unify x x of+ Nothing -> False+ Just unf -> (fst unf == x)+\end{code}++Unifying something with only anonymous variables should succeed.++\begin{code}+prop_unify_anon :: [GeniVal] -> Bool+prop_unify_anon x =+ case (unify x y) of+ Nothing -> False+ Just unf -> (fst unf == x)+ where --+ y = take (length x) $ repeat GAnon+\end{code}++Unification should be symmetrical. We can't guarantee these if there+are cases where there are variables in the same place on both sides, so we+normalise the sides so that this doesn't happen.++\begin{code}+prop_unify_sym :: [GeniVal] -> [GeniVal] -> Property+prop_unify_sym x y =+ let u1 = (unify x y) :: Maybe ([GeniVal],Subst)+ u2 = unify y x+ --+ notOverlap (GVar _, GVar _) = False+ notOverlap _ = True+ in (all qc_not_empty_GConst) x &&+ (all qc_not_empty_GConst) y &&+ all (notOverlap) (zip x y) ==> u1 == u2+\end{code}++\ignore{+\begin{code}+-- Definition of Arbitrary GeniVal for QuickCheck+newtype GTestString = GTestString String+newtype GTestString2 = GTestString2 String++fromGTestString :: GTestString -> String+fromGTestString (GTestString s) = s++fromGTestString2 :: GTestString2 -> String+fromGTestString2 (GTestString2 s) = s++instance Arbitrary GTestString where+ arbitrary =+ oneof $ map (return . GTestString) $+ [ "a", "apple" , "b", "banana", "c", "carrot", "d", "durian"+ , "e", "eggplant", "f", "fennel" , "g", "grape" ]+ coarbitrary = error "no implementation of coarbitrary for GTestString"++instance Arbitrary GTestString2 where+ arbitrary =+ oneof $ map (return . GTestString2) $+ [ "X", "Y", "Z", "H", "I", "J", "P", "Q", "R", "S", "T", "U" ]+ coarbitrary = error "no implementation of coarbitrary for GTestString2"++instance Arbitrary GeniVal where+ arbitrary = oneof [ return $ GAnon,+ liftM (GVar . fromGTestString2) arbitrary,+ liftM (GConst . nub . sort . map fromGTestString) arbitrary ]+ coarbitrary = error "no implementation of coarbitrary for GeniVal"++qc_not_empty_GConst :: GeniVal -> Bool+qc_not_empty_GConst (GConst []) = False+qc_not_empty_GConst _ = True+\end{code}+}
+ src/NLP/GenI/BtypesBinary.hs view
@@ -0,0 +1,54 @@+{-# OPTIONS -fno-warn-orphans #-}+module NLP.GenI.BtypesBinary where++import Data.Binary+import NLP.GenI.Btypes++-- auto-generated by the Data.Binary BinaryDerive tool+instance Binary NLP.GenI.Btypes.Ptype where+ put Initial = putWord8 0+ put Auxiliar = putWord8 1+ put Unspecified = putWord8 2+ get = do+ tag_ <- getWord8+ case tag_ of+ 0 -> return Initial+ 1 -> return Auxiliar+ 2 -> return Unspecified+ _ -> fail "no parse"+instance Binary NLP.GenI.Btypes.GeniVal where+ put (GConst a) = putWord8 0 >> put a+ put (GVar a) = putWord8 1 >> put a+ put GAnon = putWord8 2+ get = do+ tag_ <- getWord8+ case tag_ of+ 0 -> get >>= \a -> return (GConst a)+ 1 -> get >>= \a -> return (GVar a)+ 2 -> return GAnon+ _ -> fail "no parse"+instance Binary NLP.GenI.Btypes.GNode where+ put (GN a b c d e f g h) = put a >> put b >> put c >> put d >> put e >> put f >> put g >> put h+ get = get >>= \a -> get >>= \b -> get >>= \c -> get >>= \d -> get >>= \e -> get >>= \f -> get >>= \g -> get >>= \h -> return (GN a b c d e f g h)++instance Binary NLP.GenI.Btypes.GType where+ put Subs = putWord8 0+ put Foot = putWord8 1+ put Lex = putWord8 2+ put Other = putWord8 3+ get = do+ tag_ <- getWord8+ case tag_ of+ 0 -> return Subs+ 1 -> return Foot+ 2 -> return Lex+ 3 -> return Other+ _ -> fail "no parse"+instance (Binary a) => Binary (NLP.GenI.Btypes.Ttree a) where+ put (TT a b c d e f g h) = put a >> put b >> put c >> put d >> put e >> put f >> put g >> put h+ get = get >>= \a -> get >>= \b -> get >>= \c -> get >>= \d -> get >>= \e -> get >>= \f -> get >>= \g -> get >>= \h -> return (TT a b c d e f g h)++instance Binary NLP.GenI.Btypes.ILexEntry where+ put (ILE a b c d e f g h i) = put a >> put b >> put c >> put d >> put e >> put f >> put g >> put h >> put i+ get = get >>= \a -> get >>= \b -> get >>= \c -> get >>= \d -> get >>= \e -> get >>= \f -> get >>= \g -> get >>= \h -> get >>= \i -> return (ILE a b c d e f g h i)+
+ src/NLP/GenI/Builder.lhs view
@@ -0,0 +1,480 @@+% GenI surface realiser+% Copyright (C) 2005 Carlos Areces and Eric Kow+%+% This program is free software; you can redistribute it and/or+% modify it under the terms of the GNU General Public License+% as published by the Free Software Foundation; either version 2+% of the License, or (at your option) any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; if not, write to the Free Software+% Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.++\chapter{Builder}+\label{cha:Builder}++The heavy lifting of GenI, the whole chart/agenda mechanism, can be+implemented in many ways. To make it easier to write different+algorithms for GenI and compare them, we provide a single interface+for what we call Builders.++This interface is then used called by the Geni module and by the+graphical interface. Note that each builder has its own graphical+interface and that we do a similar thing in the graphical interface+code to make it possible to use these GUIs. Maybe a little dose of+UML might help. See figure \ref{fig:builderUml}.++\begin{figure}+\begin{center}+\includegraphics[scale=0.5]{images/builderUml.pdf}+\label{fig:builderUml}+\caption{Essentially what the Builder interface provides}+\end{center}+\end{figure}++\ignore{+\begin{code}+module NLP.GenI.Builder+where++import Control.Monad.State+import Data.Bits ( (.&.), (.|.), bit, xor )+import Data.List ( (\\), maximum )+import qualified Data.Map as Map+import Data.Maybe ( mapMaybe, fromMaybe )+import qualified Data.Set as Set+import Data.Tree ( flatten )+import Prelude hiding ( init )++import NLP.GenI.Automaton (NFA, automatonPaths, automatonPathSets, numStates, numTransitions)+import NLP.GenI.Configuration+ ( getListFlagP, getFlagP, hasFlagP, Params,+ ExtraPolaritiesFlg(..), MetricsFlg(..),+ IgnoreSemanticsFlg(..), RootFeatureFlg(..),+ polarised )+import NLP.GenI.General (geniBug, BitVector, multiGroupByFM, fst3, snd3, thd3)+import NLP.GenI.Btypes+ ( ILexEntry, SemInput, Sem, Pred, showPred, showSem,+ Flist, gtype, GType(Subs, Foot),+ Collectable(collect), alphaConvertById,+ GeniVal(GConst)+ )+import NLP.GenI.Polarity (PolResult, buildAutomaton, detectPolPaths)+import NLP.GenI.Statistics (Statistics, incrIntMetric,+ Metric(IntMetric), updateMetrics,+ mergeMetrics, addIntMetrics,+ queryMetrics, queryIntMetric,+ addMetric, emptyStats,+ )+import NLP.GenI.Tags ( TagElem(idname,tsemantics,ttree), setTidnums, TagDerivation )+\end{code}+}++\section{The interface}++All backends provide the same essential functionality:+\begin{description}+\item [run] calls init and stepAll and potentially wraps it with some+ other functionality. +\item [init] initialise the machine from the semantics and lexical selection +\item [step] run a realisation step+\item [stepAll] run all realisations steps until completion+\item [finished] determine if realisation is finished+\item [stats] extract various statistics from it+\item [setStats] set the statistical information +\item [unpack] unpack chart results into a list of sentences+\end{description}++FIXME: need to update this comment++\begin{code}+data Builder st it pa = Builder+ { init :: Input -> pa -> (st, Statistics)+ --+ , step :: BuilderState st ()+ , stepAll :: BuilderState st ()+ --+ , finished :: st -> Bool+ , unpack :: st -> [Output]+ , partial :: st -> [Output] }++type Output = (UninflectedSentence, Derivation)+type Derivation = TagDerivation+\end{code}++To simplify interaction with the backend, we provide a single data+structure which represents all the inputs a backend could take.++\begin{code}+data Input = + Input { inSemInput :: SemInput+ , inLex :: [ILexEntry] -- ^ for the debugger+ , inCands :: [(TagElem, BitVector)] -- ^ tag tree+ }+\end{code}++\section{Uninflected words and sentences}++Each word of an uninflected sentence consists of a lemma and some+feature structures.++\paragraph +A SentenceAut represents a set of sentences in the form of an automaton.+The labels of the automaton are the words of the sentence. But note! +``word'' in the sentence is in fact a tuple (lemma, inflectional feature+structures). Normally, the states are defined as integers, with the+only requirement being that each one, naturally enough, is unique.++\begin{code}+type UninflectedWord = (String, Flist)+type UninflectedSentence = [ UninflectedWord ] +type UninflectedDisjunction = ([String], Flist)+type SentenceAut = NFA Int UninflectedWord +\end{code}++\section{BuilderState}++To cleanly seperate the tracking of statistics from the core functionality of a+builder, we use a State transformer to thread a Statistics state monad inside of+our main monad.++\begin{code}+type BuilderState s a = StateT s (State Statistics) a+\end{code}++\section{Helper functions for Builders}++\subsection{Initialisation}+\label{fn:Builder:preInit}++There's a few things that need to be run before even initialising the builder.+One of these is running some of the optimisations (namely the polarity stuff),+which is made complicated by the fact that they are optional. Another of these+to assign each of the trees with a unique ID. Note that this has to be done+after the polarity optimisation because this optimisation may introduce new+items into the lexical selection. Finally, we must also make sure we perform+alpha conversion so that unification does not do the wrong thing when two trees+have the same variables.++\begin{code}+preInit :: Input -> Params -> (Input, (Int,Int,Int), PolResult)+preInit input config =+ let (cand,_) = unzip $ inCands input+ seminput = inSemInput input+ --+ extraPol = fromMaybe (Map.empty) $ getFlagP ExtraPolaritiesFlg config+ rootFeat = getListFlagP RootFeatureFlg config+ -- do any optimisations+ isPol = polarised config+ -- polarity optimisation (if enabled)+ autstuff = buildAutomaton seminput cand rootFeat extraPol+ (_, seedAut, aut, sem2) = autstuff+ autpaths = map concat $ automatonPathSets aut+ combosPol = if isPol then autpaths else [cand]+ -- chart sharing optimisation+ (cands2, pathIds) = unzip $ detectPolPaths combosPol+ -- the number of paths explored vs possible+ polcount = (length autpaths, length $ automatonPaths aut, length $ automatonPaths seedAut)+ --+ fixate ts ps = zip (map alphaConvertById $ setTidnums ts) ps+ input2 = input { inCands = fixate cands2 pathIds+ , inSemInput = (sem2, snd3 seminput, thd3 seminput) }+ -- note: autstuff is only useful for the graphical debugger+ in (input2, polcount, autstuff)+\end{code}++\begin{code}+-- | Equivalent to 'id' unless the input contains an empty or uninstatiated+-- semantics+unlessEmptySem :: Input -> Params -> a -> a+unlessEmptySem input config =+ let (cands,_) = unzip $ inCands input+ nullSemCands = [ idname t | t <- cands, (null.tsemantics) t ]+ unInstSemCands = [ idname t | t <- cands, not $ Set.null $ collect (tsemantics t) Set.empty ]+ nullSemErr = "The following trees have a null semantics: " ++ (unwords nullSemCands)+ unInstSemErr = "The following trees have an uninstantiated semantics: " ++ (unwords unInstSemCands)+ semanticsErr = (if null nullSemCands then "" else nullSemErr ++ "\n") +++ (if null unInstSemCands then "" else unInstSemErr)+ in if (null semanticsErr || hasFlagP IgnoreSemanticsFlg config)+ then id+ else error semanticsErr+\end{code}++\subsection{Running a surface realiser}++\begin{code}+-- | Performs surface realisation from an input semantics and a lexical selection.+run :: Builder st it Params -> Input -> Params -> (st, Statistics)+run builder input config =+ let -- 1 run the setup stuff+ (input2, polcount, autstuff) = preInit input config+ auts = (\(x,_,_,_) -> map snd3 x) autstuff+ -- 2 call the init stuff+ (iSt, iStats) = init builder input2 config+ -- 3 step through the whole thing+ stepAll_ = do incrCounter "pol_used_bundles" $ fst3 polcount+ incrCounter "pol_used_paths" $ snd3 polcount+ incrCounter "pol_seed_paths" $ thd3 polcount+ incrCounter "pol_total_states" $ sum $ map numStates auts+ incrCounter "pol_total_trans" $ sum $ map numTransitions auts+ incrCounter "pol_max_states" $ maximum $ map numStates auts+ incrCounter "pol_max_trans" $ maximum $ map numTransitions auts+ stepAll builder+ in runState (execStateT stepAll_ iSt) iStats+\end{code}++\subsection{Semantics and bit vectors}++\begin{code}+type SemBitMap = Map.Map Pred BitVector++-- | assign a bit vector value to each literal in the semantics+-- the resulting map can then be used to construct a bit vector+-- representation of the semantics+defineSemanticBits :: Sem -> SemBitMap+defineSemanticBits sem = Map.fromList $ zip sem bits+ where+ bits = map bit [0..] -- 0001, 0010, 0100...++semToBitVector :: SemBitMap -> Sem -> BitVector+semToBitVector bmap sem = foldr (.|.) 0 $ map doLookup sem+ where doLookup p =+ case Map.lookup p bmap of+ Nothing -> geniBug $ "predicate " ++ showPred p ++ " not found in semanticBit map"+ Just b -> b++bitVectorToSem :: SemBitMap -> BitVector -> Sem+bitVectorToSem bmap vector =+ mapMaybe tryKey $ Map.toList bmap+ where tryKey (p,k) = if (k .&. vector == k) then Just p else Nothing+\end{code}++\subsection{Index accesibility filtering}+\label{sec:iaf}++Index accesibility filtering was described in \cite{carroll05her}. This+is my attempt to adapt it to TAG. This filter works as a form of delayed+substitution, basically the exact opposite of delayed adjunction.++This might be wrong, but we say that an index is originally accesible if+it is the root node's idx attribute (no atomic disjunction; atomic+disjunction is as good as a variable as far I'm concerned)++FIXME: more about this later.+FIXME: are we sure we got the atomic disjunctions right?++\begin{code}+type IafMap = Map.Map String Sem++-- | Return the literals of the semantics (in bit vector form)+-- whose accesibility depends on the given index+dependentSem :: IafMap -> String -> Sem+dependentSem iafMap x = Map.findWithDefault [] x iafMap++-- | Return the handle and arguments of a literal+literalArgs :: Pred -> [GeniVal]+literalArgs (h,_,args) = h:args++semToIafMap :: Sem -> IafMap+semToIafMap sem =+ multiGroupByFM (concatMap fromUniConst . literalArgs) sem++-- | Like 'fromGConst' but only for the non-disjoint ones: meant to be used as Maybe or List+fromUniConst :: (Monad m) => GeniVal -> m String+fromUniConst (GConst [x]) = return x+fromUniConst _ = fail "not a unique constant" -- we don't actually expect this failure msg to be used++getIdx :: Flist -> [GeniVal]+getIdx fs = [ v | (a,v) <- fs, a == "idx" ]++ts_iafFailure :: [String] -> [Pred] -> String+ts_iafFailure is sem = "index accesibility failure -" ++ (unwords is) ++ "- blocked: " ++ showSem sem++-- | Calculate the new set of accessibility/inaccesible indices, returning a+-- a tuple of accesible / inaccesible indices+recalculateAccesibility :: (IafAble a) => a -> a+recalculateAccesibility i =+ let oldAcc = iafAcc i+ newAcc = iafNewAcc i+ oldInacc = iafInacc i+ newInacc = oldInacc ++ (oldAcc \\ newAcc)+ in iafSetInacc newInacc $ iafSetAcc newAcc i++-- | Return, in bitvector form, the portion of a semantics that is inaccesible+-- from an item+iafBadSem :: (IafAble a) => IafMap -> SemBitMap+ -> BitVector -- ^ the input semantics+ -> (a -> BitVector) -- ^ the semantics of the item+ -> a -> BitVector+iafBadSem iafMap bmap sem semfn i =+ let -- the semantics we can't reach+ inaccessible = foldr (.|.) 0 $ map (semToBitVector bmap . dependentSem iafMap) $ iafInacc i+ -- the semantics we still _need_ to be able to reach+ remaining = sem `xor` (semfn i)+ -- where we're in trouble+ in inaccessible .&. remaining++class IafAble a where+ iafAcc :: a -> [String]+ iafInacc :: a -> [String]+ iafSetAcc :: [String] -> a -> a+ iafSetInacc :: [String] -> a -> a+ iafNewAcc :: a -> [String]+\end{code}++\subsection{Generate step}++\begin{code}+-- | Default implementation for the 'stepAll' function in 'Builder'+defaultStepAll :: Builder st it pa -> BuilderState st ()+defaultStepAll b =+ do s <- get+ unless (finished b s) $+ do step b+ defaultStepAll b+\end{code}++\subsection{Dispatching new chart items}+\label{sec:dispatching}++Dispatching consists of assigning a chart item to the right part of the+chart (agenda, trash, results list, etc). This is implemented as a+series of filters which can either fail or succeed.++Counter-intuitively, success is defined as returning \verb!Nothing!.+Failure is defined as return \verb!Just!, because if a filter fails, it+has the right to modify the item for the next filter. For example, the+top and bottom unification filter succeeds if it \emph{cannot} unify+the top and bottom features of a node. It suceeds by putting the item+into the trash and returning Nothing. If it \emph{can} perform top and+bottom unification, we want to return the item where the top and bottom+nodes are unified. Failure is success, war is peace, freedom is+slavery, erase is backspace.++\begin{code}+type DispatchFilter s a = a -> s (Maybe a)++-- | Sequence two dispatch filters.+(>-->) :: (Monad s) => DispatchFilter s a -> DispatchFilter s a -> DispatchFilter s a+f >--> f2 = \x -> f x >>= maybe (return Nothing) f2++-- | A filter that always fails (i.e. no filtering)+nullFilter :: (Monad s) => DispatchFilter s a+nullFilter = return.Just++-- | If the item meets some condition, use the first filter, otherwise+-- use the second one.+condFilter :: (Monad s) => (a -> Bool)+ -> DispatchFilter s a -> DispatchFilter s a+ -> DispatchFilter s a+condFilter cond f1 f2 = \x -> if cond x then f1 x else f2 x+\end{code}++\subsection{Statistics}++\begin{code}+addCounters :: Statistics -> Statistics -> Statistics+addCounters = mergeMetrics addIntMetrics++modifyStats :: (Metric -> Metric) -> BuilderState st ()+modifyStats fn = lift $ modify $ updateMetrics fn++incrCounter :: String -> Int -> BuilderState st ()+incrCounter key n = modifyStats (incrIntMetric key n)++queryCounter :: String -> Statistics -> Maybe Int+queryCounter key s =+ case queryMetrics (queryIntMetric key) s of+ [] -> Nothing+ [c] -> Just c+ _ -> geniBug $ "More than one instance of the metric: " ++ key+\end{code}++\subsection{Command line configuration}++\begin{code}+initStats :: Params -> Statistics+initStats pa =+ let identifyMs :: [String] -> [Metric]+ identifyMs ["default"] = identifyMs defaultMetricNames+ identifyMs ms = map namedMetric ms+ metrics = identifyMs $ fromMaybe [] $ getFlagP MetricsFlg pa+ in execState (mapM addMetric metrics) emptyStats++namedMetric :: String -> Metric+-- the default case is that it's an int metric+namedMetric n = IntMetric n 0++-- Note that the strings here are command-line strings, not metric names!+defaultMetricNames :: [ String ]+defaultMetricNames = [ num_iterations, chart_size, num_comparisons ]+\end{code}++\subsection{Common counters}++These numbers allow us to keep track of how efficient our generator is+and where we are in the process (how many steps we've taken, etc)++\begin{code}+num_iterations, chart_size, num_comparisons :: String++num_iterations = "iterations"+chart_size = "chart_size"+num_comparisons = "comparisons"+\end{code}++\section{The null builder}++For the purposes of tracking certain statistics without interfering with the+lazy evaluation of the real builders. For example, one we would like to be+able to do is count the number of substitution and foot nodes in the lexical+selection. Doing so would in a real builder might cause it to walk entire+trees for ptoentially no good reason.++\begin{code}+nullBuilder :: Builder () (NullState ()) Params+nullBuilder = Builder+ { NLP.GenI.Builder.init = initNullBuilder+ , step = return ()+ , stepAll = return ()+ , finished = const True+ , unpack = return []+ , partial = return []+ }++type NullState a = BuilderState () a++initNullBuilder :: Input -> Params -> ((), Statistics)+initNullBuilder input config =+ let countsFor ts = (length ts, length nodes, length sn, length an)+ where nodes = concatMap (flatten.ttree) ts+ sn = [ n | n <- nodes, gtype n == Subs ]+ an = [ n | n <- nodes, gtype n == Foot ]+ --+ (tsem,_,_) = inSemInput input+ cands = map fst $ inCands input+ (_,_,(_,_,aut,_)) = preInit input config+ cands2 = concatMap concat $ automatonPathSets aut+ --+ countUp = do incrCounter "sem_literals" $ length tsem+ --+ incrCounter "lex_subst_nodes" snl+ incrCounter "lex_foot_nodes" anl+ incrCounter "lex_nodes" nl+ incrCounter "lex_trees" tl+ -- node count after polarities are taken into account+ incrCounter "plex_subst_nodes" snl2+ incrCounter "plex_foot_nodes" anl2+ incrCounter "plex_nodes" nl2+ incrCounter "plex_trees" tl2+ where (tl , nl , snl , anl ) = countsFor cands+ (tl2, nl2, snl2, anl2) = countsFor cands2+ in runState (execStateT countUp ()) (initStats config)+\end{code}++
+ src/NLP/GenI/BuilderGui.lhs view
@@ -0,0 +1,34 @@+% GenI surface realiser+% Copyright (C) 2005 Carlos Areces and Eric Kow+%+% This program is free software; you can redistribute it and/or+% modify it under the terms of the GNU General Public License+% as published by the Free Software Foundation; either version 2+% of the License, or (at your option) any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; if not, write to the Free Software+% Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.++\begin{code}+module NLP.GenI.BuilderGui+where++import Graphics.UI.WX++import qualified NLP.GenI.Builder as B+import NLP.GenI.Geni (ProgStateRef, GeniResult)+import NLP.GenI.Configuration (Params)+import NLP.GenI.Statistics (Statistics)+\end{code}++\begin{code}+data BuilderGui = BuilderGui+ { resultsPnl :: forall a . ProgStateRef -> (Window a) -> IO ([GeniResult],Statistics,Layout)+ , debuggerPnl :: forall a . (Window a) -> Params -> B.Input -> String -> IO Layout }+\end{code}
+ src/NLP/GenI/CkyEarley/CkyBuilder.lhs view
@@ -0,0 +1,1243 @@+% GenI surface realiser+% Copyright (C) 2005 Carlos Areces and Eric Kow+%+% This program is free software; you can redistribute it and/or+% modify it under the terms of the GNU General Public License+% as published by the Free Software Foundation; either version 2+% of the License, or (at your option) any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; if not, write to the Free Software+% Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.++\chapter{Cky builder}+\label{cha:CkyBuilder}++GenI currently has three backends, SimpleBuilder (chapter+\ref{cha:SimpleBuilder}) the CKY and Earley which are both in this+module. This backend does not attempt to build derived trees at all.+We construct packed derivation trees using the CKY/Earley algorithm for+TAGs, and at the very end, we unpack the results directly into an+automaton. No derived trees here!++\begin{code}+{-# LANGUAGE LiberalTypeSynonyms #-}+module NLP.GenI.CkyEarley.CkyBuilder+ ( -- builder+ ckyBuilder, earleyBuilder,+ CkyStatus(..),+ -- chart item+ CkyItem(..), ChartId,+ ciAdjDone, ciRoot,+ extractDerivations,+ -- automaton stuff (for the graphical debugger)+ mJoinAutomata, mAutomatonPaths, emptySentenceAut, unpackItemToAuts,+ --+ bitVectorToSem, findId,+ )+where+\end{code}++\ignore{+\begin{code}++import Control.Monad+ (unless, foldM)++import Control.Monad.State+ (State, gets, get, put, modify, runState, execStateT )+import Data.Bits ( (.&.), (.|.) )+import Data.List ( delete, find, span, (\\), intersect, union )+import qualified Data.Map as Map+import qualified Data.Set as Set+import Data.Maybe (catMaybes, mapMaybe, maybeToList)+import Data.Tree++import NLP.GenI.Btypes+ ( unify, collect+ , Flist+ , Replacable(..), Subst+ , GNode(..), GType(Subs, Foot, Other)+ , GeniVal(GVar), fromGVar+ , Ptype(Auxiliar)+ , root, foot+ , unifyFeat, mergeSubst )++import NLP.GenI.Automaton+ ( NFA(NFA, transitions, states), isFinalSt, finalSt, finalStList, startSt, addTrans, automatonPaths )+import qualified NLP.GenI.Builder as B+import NLP.GenI.Builder+ ( SentenceAut, incrCounter, num_iterations, chart_size,+ SemBitMap, semToBitVector, bitVectorToSem, defineSemanticBits,+ (>-->), DispatchFilter,+ semToIafMap, IafAble(..), IafMap, fromUniConst, getIdx,+ recalculateAccesibility, iafBadSem, ts_iafFailure+ )+import NLP.GenI.Configuration ( Params, isIaf )+import NLP.GenI.General+ ( fst3, combinations, treeLeaves, BitVector, geniBug )+import NLP.GenI.Tags+ ( TagElem, tidnum, ttree, tsemantics, ttype,+ ts_tbUnificationFailure, TagSite(TagSite), detectSites+ )+import NLP.GenI.Statistics ( Statistics )++-- -- Debugging stuff+-- import Data.List ( intersperse )+-- import Debug.Trace+-- import General ( showBitVector )+-- import Tags ( idname )+--+-- ckyShow name item chart =+-- let showChart = show $ length chart+-- pad s n = s ++ (take (n - length s) $ repeat ' ')+-- in concat $ intersperse "\t" $+-- [ pad name 10, showChart+-- , pad (idname $ ciSourceTree item) 10+-- , pad (showItemSem item) 5+-- , show $ ciNode item ]+--+-- showItems = unlines . (map showItem)+-- showItem i = (idname.ciSourceTree) i ++ " " ++ show (ciNode i) ++ " " ++ (showItemSem i)+-- showItemSem = (showBitVector 5) . ciSemantics+\end{code}+}++\section{Implementing the Builder interface}++\begin{code}+type CkyBuilder = B.Builder CkyStatus CkyItem Params++ckyBuilder, earleyBuilder :: CkyBuilder+ckyBuilder = ckyOrEarleyBuilder False+earleyBuilder = ckyOrEarleyBuilder True++ckyOrEarleyBuilder :: Bool -> CkyBuilder+ckyOrEarleyBuilder isEarley = B.Builder+ { B.init = initBuilder isEarley+ , B.step = generateStep isEarley+ , B.stepAll = B.defaultStepAll (ckyOrEarleyBuilder isEarley)+ , B.finished = null.theAgenda+ , B.unpack = \s -> concatMap (unpackItem s) $ theResults s+ , B.partial = const [] -- FIXME: not implemented+ }+\end{code}++The rest of the builder interface is implemented below. I just+wanted to put the front-end functions up on top.++% --------------------------------------------------------------------+\section{Key types}+% --------------------------------------------------------------------++\subsection{CkyState and CkyStatus}++This terminology might be a bit confusing: \verb!CkyState! is just a+\verb!BuilderState! monad parameterised over \verb!CkyStatus!. So,+status contains the actual data and state handles all the monadic stuff.++\begin{code}+type CkyState a = B.BuilderState CkyStatus a++data CkyStatus = S+ { theAgenda :: Agenda+ , theChart :: Chart+ , theTrash :: Trash+ , tsemVector :: BitVector -- the semantics in bit vector form+ , theIafMap :: IafMap -- for index accessibility filtering+ , gencounter :: Integer+ , genconfig :: Params+ , theRules :: [CKY_InferenceRule]+ , theDispatcher :: CkyItem -> CkyState (Maybe CkyItem)+ , theResults :: [CkyItem]+ , genAutCounter :: Integer -- allocation of node numbers+ }++type Agenda = [CkyItem]+type Chart = [CkyItem]+type Trash = [CkyItem]+\end{code}++Note the theTrash is not actually essential to the operation of the+generator; it is for pratical debugging of grammars. Instead of+trees dissapearing off the face of the debugger; they go into the+trash where the user can inspect them and try to figure out why they+went wrong.++\subsubsection{CkyState getters and setters}++\begin{code}+addToAgenda :: CkyItem -> CkyState ()+addToAgenda te = do+ modify $ \s -> s{ theAgenda = te : (theAgenda s) }++addToResults :: CkyItem -> CkyState ()+addToResults te = do+ modify $ \s -> s{ theResults = te : (theResults s) }++updateAgenda :: Agenda -> CkyState ()+updateAgenda a = do+ modify $ \s -> s{ theAgenda = a }++addToChart :: CkyItem -> CkyState ()+addToChart te = do+ modify $ \s -> s { theChart = te : (theChart s) }+ incrCounter chart_size 1++addToTrash :: CkyItem -> String -> CkyState ()+addToTrash item err = do+ let item2 = item { ciDiagnostic = err:(ciDiagnostic item) }+ modify $ \s -> s { theTrash = item2 : (theTrash s) }+\end{code}++\subsection{Chart items}++-- TODO: decide if we want this to be an instant of Replacable+\begin{code}+data CkyItem = CkyItem+ { ciNode :: GNode+ -- things which should never change+ , ciSourceTree :: TagElem+ , ciOrigVariables :: [GeniVal]+ --+ , ciPolpaths :: BitVector+ , ciSemantics :: BitVector+ , ciAdjPoint :: Maybe ChartId+ -- | the semantics of the item when it was first initialised+ , ciInitialSem :: BitVector+ -- | unique identifier for this item+ , ciId :: ChartId+ -- names of the sisters of this node in its tree+ , ciRouting :: RoutingMap+ -- used by the next leaf rule (if active)+ , ciPayload :: [CkyItem]+ -- a list of genivals which were variables when the node was+ -- first initialised+ , ciVariables :: [GeniVal]+ -- we keep a SemBitMap strictly to help display the semantics+ , ciSemBitMap :: SemBitMap+ -- what side of the spine are we on? (left if initial tree: no spine)+ , ciTreeSide :: TreeSide+ -- if there are things wrong with this item, what?+ , ciDiagnostic :: [String]+ -- what is the set of the ways you can produce this item?+ , ciDerivation :: [ ChartOperation ]+ -- what indices are accesible/inaccesible from this item?+ , ciAccesible :: [ String ] -- it's acc/inacc/undetermined+ , ciInaccessible :: [ String ] -- that's why you want both+ , ciSubstnodes :: [ TagSite ] -- only used for iaf+ }++type ChartId = Integer++-- | note that the order is always active item, followed by passive item+data ChartOperation = SubstOp ChartId ChartId+ | AdjOp ChartId ChartId+ | NullAdjOp ChartId+ | KidsToParentOp [ChartId]+ | InitOp+ deriving Show -- for debugging++type ChartOperationConstructor = ChartId -> ChartId -> ChartOperation++ciRoot, ciFoot, ciSubs, ciAdjDone, ciAux, ciInit, ciComplete :: CkyItem -> Bool+ciRoot i = (gnname.ciNode) i == (gnname.root.ttree.ciSourceTree) i+ciFoot i = (gtype.ciNode) i == Foot+ciSubs i = (gtype.ciNode) i == Subs+ciAdjDone = gaconstr.ciNode+ciComplete i = (not.ciSubs $ i) && ciAdjDone i+ciAux i = (ttype.ciSourceTree) i == Auxiliar+ciInit = not.ciAux++data TreeSide = LeftSide | RightSide | OnTheSpine+ deriving (Eq)++ciLeftSide, ciRightSide, ciOnTheSpine :: CkyItem -> Bool+ciLeftSide i = ciTreeSide i == LeftSide+ciRightSide i = ciTreeSide i == RightSide+ciOnTheSpine i = ciTreeSide i == OnTheSpine+++-- basically, an inverted tree+-- from node name to a list of its sisters on the left,+-- a list of its sisters on the right, its parent+type RoutingMap = Map.Map String ([String], [String], GNode)+\end{code}++% --------------------------------------------------------------------+\section{Initialisation}+% --------------------------------------------------------------------++\begin{code}+initBuilder :: Bool -> B.Input -> Params -> (CkyStatus, Statistics)+initBuilder isEarley input config =+ let (sem, _, _) = B.inSemInput input+ bmap = defineSemanticBits sem+ cands = concatMap (initTree isEarley bmap) $ B.inCands input+ dispatchFn = ckyDispatch (isIaf config)+ initS = S+ { theAgenda = []+ , theChart = []+ , theTrash = []+ , theResults = []+ , theRules = map fst ckyRules+ , tsemVector = semToBitVector bmap sem+ , theIafMap = semToIafMap sem+ , theDispatcher = dispatchFn+ , gencounter = 0+ , genAutCounter = 0+ , genconfig = config }+ in B.unlessEmptySem input config $+ runState (execStateT (mapM dispatchFn cands) initS) (B.initStats config)+\end{code}++\subsection{Initialising a chart item}+\label{fn:cky:initTree}++\begin{code}+initTree :: Bool -> SemBitMap -> (TagElem,BitVector) -> [CkyItem]+initTree ordered bmap tepp@(te,_) =+ let semVector = semToBitVector bmap (tsemantics te)+ createItem l n = item+ { ciSemantics = semVector+ , ciInitialSem = semVector+ , ciSemBitMap = bmap+ , ciRouting = decompose te+ , ciVariables = map GVar $ Set.toList $ collect te Set.empty+ , ciAccesible = iafNewAcc item+ } where item = leafToItem l tepp n+ --+ (left,right) = span (\n -> gtype n /= Foot) $ treeLeaves $ ttree te+ items = map (createItem True) left ++ map (createItem False) right+ in if ordered+ then foldr (\i p -> [i { ciPayload = p }]) [] items+ else items++leafToItem :: Bool+ -- ^ is it on the left of the foot node? (yes if there is none)+ -> (TagElem, BitVector)+ -- ^ what tree does it belong to+ -> GNode+ -- ^ the leaf to convert+ -> CkyItem+leafToItem left (te,pp) node = CkyItem+ { ciNode = node+ , ciSourceTree = te+ , ciPolpaths = pp+ , ciSemantics = 0 -- to be set+ , ciInitialSem = 0 -- to be set+ , ciId = -1 -- to be set+ , ciRouting = Map.empty -- to be set+ , ciOrigVariables = [] -- to be set+ , ciVariables = [] -- to be set+ , ciPayload = [] -- to be set+ , ciAdjPoint = Nothing+ , ciSemBitMap = Map.empty+ , ciTreeSide = spineSide+ , ciDiagnostic = []+ , ciAccesible = [] -- to be set+ , ciInaccessible = []+ , ciSubstnodes = (fst3.detectSites.ttree) te+ , ciDerivation = [ InitOp ] }+ where+ spineSide | left = LeftSide+ | gtype node == Foot = OnTheSpine+ | otherwise = RightSide++-- | explode a TagElem tree into a bottom-up routing map+decompose :: TagElem -> RoutingMap+decompose te = helper (ttree te) Map.empty+ where+ helper :: Tree GNode -> RoutingMap -> RoutingMap+ helper (Node _ []) smap = smap+ helper (Node p kidNodes) smap =+ let kids = [ gnname x | (Node x _) <- kidNodes ]+ addKid k = Map.insert k (left, right, p)+ where (left, right') = span (/= k) kids+ right = if null right' then [] else tail right'+ smap2 = foldr addKid smap kids+ in -- recurse to add routing info for child nodes+ foldr helper smap2 kidNodes+\end{code}++% --------------------------------------------------------------------+\section{Generate}+% --------------------------------------------------------------------++Each iteration of the surface realisation step involves picking an item+off the agenda, applying all the relevant inference rules to it, and+dispatching the results. Lather, rinse, repeat. At some point we just+run out of things on the agenda and stop.++Well, ok, there are ways that this thing could loop infinitely: for+example, having null semantic lexical items would be a very bad thing.++\begin{code}+generateStep :: Bool -> CkyState ()+generateStep isEarley =+ do -- this check may seem redundant with generate, but it's needed+ -- to protect against a user who calls generateStep on a finished+ -- state+ isFinished <- gets finished+ unless (isFinished) (generateStep2 isEarley)++generateStep2 :: Bool -> CkyState ()+generateStep2 isEarley =+ do st <- get+ -- incrGeniter 1+ agendaItem <- selectAgendaItem+ -- try the inference rules+ let chart = theChart st+ apply rule = rule agendaItem chart+ results = map apply (theRules st)+ -- see comments below about ordered substitution+ releasePayload = not (null results) || ciComplete agendaItem+ payload = if releasePayload && isEarley+ then ciPayload agendaItem else []+ -- put all newly generated items into the right pigeon-holes+ -- trace (concat $ zipWith showRes ckyRules results) $+ let dispatcher = theDispatcher st+ mapM dispatcher $ payload ++ (concat results)+ addToChart agendaItem+ incrCounter num_iterations 1+ return ()++selectAgendaItem :: CkyState CkyItem+selectAgendaItem = do+ a <- gets theAgenda+ updateAgenda (tail a)+ return (head a)++finished :: CkyStatus -> Bool+finished = null.theAgenda+\end{code}++% --------------------------------------------------------------------+\section{CKY Rules}+% --------------------------------------------------------------------++Our surface realiser is defined by a set of inference rules. Since we are+using an agenda-based algorithm, we define our inference rules to take two+arguments: the agenda item and the entire chart. It is up to the inference+rule to filter the chart for the items which can combine with the agenda item.+If a rule is not applicable, it should simply return the empty list.++\begin{code}+type InferenceRule a = a -> [a] -> [a]+type CKY_InferenceRule = InferenceRule CkyItem++instance Show CKY_InferenceRule where+ show _ = "cky inference rule"+\end{code}++% FIXME: diagram and comment++\begin{code}+ckyRules :: [ (CKY_InferenceRule, String) ]+ckyRules =+ [ (parentRule, "parent")+ , (substRule , "subst")+ , (nonAdjunctionRule, "nonAdj")+ , (activeAdjunctionRule, "actAdjRule")+ , (passiveAdjunctionRule, "psvAdjRule") ]++parentRule, substRule, nonAdjunctionRule, activeAdjunctionRule, passiveAdjunctionRule :: CKY_InferenceRule++-- | CKY non adjunction rule - creates items in which+-- we do not apply any adjunction+-- this rule also doubles as top+nonAdjunctionRule item _ =+ let node = ciNode item+ node2 = node { gaconstr = True }+ in if gtype node /= Other || ciAdjDone item then []+ else [ item { ciNode = node2+ , ciPayload = []+ , ciDerivation = [ NullAdjOp $ ciId item ] } ]+\end{code}++\subsection{Parent rule}++WARNING: unproven code below! There is a piece of code floating around+here which attempts to make the parent rule go a little bit faster and+could eventually be used to replace \verb!ciSubsts! altogether. But+somebody needs to sit down and prove that this is correct first.++The basic problem is that you've got some child nodes from a tree and+you want to know if you can use them to climb up to the parent node.+Consider for instance the tree $(P:?X L:?X R:?X)$, that is a+simple tree with two child nodes with a shared variable $?X$ on all+nodes. Your two jobs are to+\begin{enumerate}+\item Make sure that the assignments of $?X$ do not conflict, for+example, if in your instance of $L$, you have $?X \leftarrow a$ and in+$R$, you have $?X \leftarrow b$, that would be bad and you should rule+it out.+\item Propagate any assignments of $?X$ up to the parent node.+\end{enumerate}++A naïve ``safe'' solution then seems to be that you have to unify+together all instances of the child nodes: that is, in the example+above, you need to unify $L$ with $R$'s idea of what $L$ is and vice+versa, and then somehow propaagate everything up. Keep in mind that+this is not the same thing as unifying $L$ with $R$ (why on earth would+you want to do something like that?). I don't like this solution,+because I get the impression that it makes us do a lot of unification+for nothing.++Ok, so how do we go about making this cheaper to perform? Here is what+I ended up implementing: in the initialisation phase, you collect a set+of open variables for each tree. This is the initial value of+\verb!ciVariables!. Now, whenever you do anything with a chart item,+for example, unifying some feature structure because of adjunction, you+take care to also apply the variable replacements to the+\verb!ciVariables! list. This way, it always contains the+latest values for what were the open variables of the original tree.+When you apply the parent rule, so goes the unproven idea, all you have+to do is unify \verb!ciVariables! for all the child nodes. In order+to propagate this to the parent node, you have to remember what the+original values for \verb!ciVariables! was and use that to create a+new replacements list. Let's work this out with a concrete example:++\begin{enumerate}+\item You've got the source tree in figure+\ref{fig:variableCollection-01-04} with two open variables, $?X$ and+$?Y$.+\item Substitution into one of the nodes gives you the replacement+$?Y \leftarrow b$+\item Our first application of the parent rule: we climb up to the next+node, rather trivially here since there is only one child+\item This parent node $L$ receives adjunction, which sets the variable+$?X \leftarrow a$+\item (figure \ref{fig:variableCollection-05-06}) Independently of all+this, we substitute something into the other side of the tree. This+sets $?X \leftarrow c$. We don't know yet that this is a conflict with+the previous step because we haven't tried applying the parent rule yet.+\item But when we try to apply the parent rule here between the child+$L$ and this version of the child $R$, we get a failure because their+two instances of \verb!ciVariables! fail to unify ($a \neq c$).+\item (figure \ref{fig:variableCollection-07-09}) We've seen what failure+looks like, so let's try for success. Say we had substituted something+different into $R$ and as a result, we get the assignement $?X+\leftarrow b$.+\item This time, unification between the \verb!ciVariables! from the+children $L$ and $R$ actually succeeds, so we allow the parent rule+to apply.+\item Notice that the same \verb!ciVariables! unification mechanism+also propagates up the assignemnt $?Y \leftarrow a$+\end{enumerate}++\begin{figure}+\begin{center}+\includegraphics[scale=0.5]{images/variableCollection-01-04}+\caption{Variable collections example (part 1/3)}+\label{fig:variableCollection-01-04}+\end{center}+\end{figure}+\begin{figure}+\begin{center}+\includegraphics[scale=0.5]{images/variableCollection-05-06}+\caption{Variable collections example (part 2/3)}+\label{fig:variableCollection-05-06}+\end{center}+\end{figure}+\begin{figure}+\begin{center}+\includegraphics[scale=0.5]{images/variableCollection-07-09}+\caption{Variable collections example (part 3/3)}+\label{fig:variableCollection-07-09}+\end{center}+\end{figure}++\begin{code}+-- | CKY parent rule+parentRule item chart | ciComplete item =+ do (leftS,rightS,p) <- maybeToList $ Map.lookup (gnname node) (ciRouting item)+ let mergePoints kids =+ case mapMaybe ciAdjPoint (item:kids) of+ [] -> Nothing+ [x] -> Just x+ _ -> error "multiple adjunction points in parentRule?!"+ combine par kids = do+ let unifyOnly (x, _) y = maybeToList $ unify x y+ -- IMPORTANT! This blocks the parent rule from applying+ -- if the child variables don't unify.+ (newVars, _) <- foldM unifyOnly (ciVariables item, Map.empty) $+ map ciVariables kids+ let newSubsts = Map.fromList $ zip (map fromGVar $ ciOrigVariables item) newVars+ newSide | all ciLeftSide kids = LeftSide+ | all ciRightSide kids = RightSide+ | any ciOnTheSpine kids = OnTheSpine+ | otherwise = geniBug $ "parentRule: Weird situtation involving tree sides"+ newItem = item+ { ciNode = replace newSubsts par+ , ciAdjPoint = mergePoints kids+ , ciVariables = newVars+ , ciTreeSide = newSide+ , ciDerivation = [ KidsToParentOp $ map ciId kids ]+ , ciPayload = []+ , ciSubstnodes = foldr intersect (ciSubstnodes item) $ map ciSubstnodes kids+ -- does union make sense?+ , ciAccesible = foldr union (ciAccesible item) $ map ciAccesible kids+ , ciInaccessible = foldr union (ciInaccessible item) $ map ciInaccessible kids+ }+ return $ foldr combineVectors newItem kids+ let leftMatches = map matches leftS+ rightMatches = map matches rightS+ allMatches = leftMatches ++ ([item] : rightMatches)+ -- trace (" relevant chart: (" ++ (show $ length relChart) ++ ") " ++ showItems relChart) $+ -- trace (" routing info: " ++ show (s,p,r)) $+ -- trace (" matches: (" ++ (show $ length allMatches) ++ ") " ++ (concat $ intersperse "-\n" $ map showItems allMatches)) $+ combinations allMatches >>= combine p+ where+ node = ciNode item+ sourceOf = tidnum.ciSourceTree+ --+ relevant c = (sourceOf c == sourceOf item) && ciComplete c+ -- make sure the semantics only overlap in the initial parts+ && (ciSemantics c) .&. (ciSemantics item) == (ciInitialSem item)+ relChart = filter relevant chart+ --+ matches :: String -> [CkyItem]+ matches sis = [ c | c <- relChart, (gnname.ciNode) c == sis ]+parentRule _ _ = [] -- if this rule is not applicable to the item at hand+\end{code}++\subsection{Substitution}++The substitution rule has two variants: either the agenda item is active,+meaning it is a root node and is trying to subsitute into something; or it+is passive, meaning that is a substitution node waiting to receive+substitution on something.++\begin{code}+-- | CKY subst rules+substRule item chart = catMaybes $+ if ciSubs item+ then [ attemptSubst item r | r <- chart, compatibleForSubstitution r item ]+ else [ attemptSubst s item | s <- chart, compatibleForSubstitution item s ]++-- | unification for substitution+attemptSubst :: CkyItem -> CkyItem -> Maybe CkyItem+attemptSubst sItem rItem | ciSubs sItem =+ do let rNode = ciNode rItem+ sNode = ciNode sItem+ (up, down, subst) <- unifyGNodes sNode (ciNode rItem)+ let newNode = rNode { gnname = gnname sNode+ , gup = up, gdown = down }+ newItem = combineWithSubst newNode subst rItem sItem+ return $ newItem+attemptSubst _ _ = error "attemptSubst called on non-subst node"++-- | return True if the first item may be substituted into the second+-- as long as unification and all the nasty details work out+compatibleForSubstitution :: CkyItem -- ^ active item+ -> CkyItem -- ^ passive item+ -> Bool+compatibleForSubstitution a p =+ ciRoot a && ciComplete a && ciInit a+ && ciSubs p+ && compatible a p+\end{code}++\subsection{Adjunction}++As with substitution, the adjunction rule has two variants: either the agenda+item is active, meaning it is the root node of an auxliary tree is trying+to adjoin into something; or it is passive, meaning it is a node which is+waiting to receive adjunction.++Note that unlike the substitution rule, we have to split these two variants+into two actual rules. This is because we also want auxiliary tree nodes+to be able to receive adjunction and not just perform it!++\begin{code}+-- | CKY adjunction rule: note - we need this split into two rules because+-- both variants could fire at the same time, for example, the passive variant+-- to adjoin into the root of an auxiliary tree, and the active variant because+-- it is an aux tree itself and it wants to adjoin somewhere+activeAdjunctionRule item chart | ciRoot item && ciAux item =+ mapMaybe (\p -> attemptAdjunction p item)+ [ p | p <- chart, compatibleForAdjunction item p ]+activeAdjunctionRule _ _ = [] -- if not applicable++-- | CKY adjunction rule: we're just a regular node, minding our own business+-- looking for an auxiliary tree to adjoin into us+passiveAdjunctionRule item chart =+ mapMaybe (attemptAdjunction item)+ [ a | a <- chart, compatibleForAdjunction a item ]++attemptAdjunction :: CkyItem -> CkyItem -> Maybe CkyItem+attemptAdjunction pItem aItem | ciRoot aItem && ciAux aItem =+ -- trace ("try adjoining " ++ (showItem aItem) ++ " into " ++ (showItem pItem)) $+ do let aRoot = ciNode aItem+ aFoot = (foot.ttree.ciSourceTree) aItem-- could be pre-computed?+ pNode = ciNode pItem+ (newTop, _ , subst) <- unifyPair (gup pNode, gdown pNode)+ (gup aRoot, gdown aFoot)+ let newNode = pNode { gaconstr = False, gup = newTop, gdown = [] }+ newItem = combineWith AdjOp newNode subst aItem pItem+ return newItem+attemptAdjunction _ _ = error "attemptAdjunction called on non-aux or non-root node"++-- | return True if the first item may be adjoined into the second+-- as long as unification and all the nasty details work out+compatibleForAdjunction :: CkyItem -- ^ active item+ -> CkyItem -- ^ passive item+ -> Bool+compatibleForAdjunction a p =+ ciAux a && ciRoot a && ciAdjDone a+ && (gtype.ciNode) p == Other && (not.ciAdjDone) p+ && compatible a p+\end{code}++\subsection{Helpers for inference rules}++\begin{code}+isLexeme :: GNode -> Bool+isLexeme = not.null.glexeme++-- | return True if the chart items may be combined with each other; for now, this+-- consists of a semantic check+compatible :: CkyItem -> CkyItem -> Bool+compatible a b = ( (ciSemantics a) .&. (ciSemantics b) ) == 0+ && ( (ciPolpaths a) .|. (ciPolpaths b) ) /= 0+\end{code}++To factorise the construction of new items, we provide two functions for combining+two chart items. \fnreflite{combineVectors} merely combines the easy stuff (the+semantic bit maps and the polarity paths). \fnreflite{combineWith} does the+heavier stuff like the list of open variables and the derivation for the new item.+The reason we expose \fnreflite{combineVectors} as a separate function is because+the \fnreflite{kidsToParentsRule} needs it.++\begin{code}+combineVectors :: CkyItem -> CkyItem -> CkyItem+combineVectors a b =+ b { ciSemantics = (ciSemantics a) .|. (ciSemantics b)+ , ciPolpaths = (ciPolpaths a) .&. (ciPolpaths b)+ , ciSemBitMap = ciSemBitMap a }++combineWithSubst :: GNode -> Subst -> CkyItem -> CkyItem -> CkyItem+combineWithSubst node subst a p =+ newPassive { ciAccesible = (ciAccesible a) `union` (ciAccesible p)+ , ciInaccessible = (ciInaccessible a) `union` (ciInaccessible p)+ , ciSubstnodes = newCiSubstnodes }+ where newCiSubstnodes = [ t | t@(TagSite x _ _ _) <- ciSubstnodes p, x /= gnname node ]+ newPassive = combineWith SubstOp node subst a p++combineWith :: ChartOperationConstructor -- ^ how did we get the new item?+ -> GNode -> Subst -> CkyItem -> CkyItem -> CkyItem+combineWith operation node subst active passive =+ combineVectors active $+ passive { ciNode = node+ , ciPayload = []+ , ciVariables = replace subst (ciVariables passive)+ , ciDerivation = [ operation (ciId active) (ciId passive) ] }+\end{code}++\paragraph{unifyTagNodes} performs feature structure unification+on TAG nodes. First we try unification on the top node. We+propagate any results from that unification and proceed to trying+unification on the bottom nodes. If succesful, we return the+results of both unifications and a list of substitutions to+propagate. Otherwise we return Nothing.++\begin{code}+unifyGNodes :: GNode -> GNode -> Maybe (Flist, Flist, Subst)+unifyGNodes g1 g2 =+ unifyPair (gupdown g1) (gupdown g2)+ where gupdown n = (gup n, gdown n)++unifyPair :: (Flist, Flist) -> (Flist, Flist) -> Maybe (Flist, Flist, Subst)+unifyPair (t1, b1) (t2, b2) =+ do (newTop, subst1) <- unifyFeat t1 t2+ (newBot, subst2) <- unifyFeat (replace subst1 b1) (replace subst1 b2)+ return (newTop, newBot, mergeSubst subst1 subst2)+\end{code}++% --------------------------------------------------------------------+\section{Dispatching new chart items}+% --------------------------------------------------------------------++We use the generic dispatch mechanism described in section \ref{sec:dispatch}.++\begin{code}+type CKY_DispatchFilter = DispatchFilter CkyState CkyItem++ckyDispatch :: Bool -- ^ index accessibility filtering+ -> CKY_DispatchFilter+ckyDispatch iaf =+ dispatchTbFailure >--> dispatchRedundant >--> dispatchResults >-->+ (if iaf then dispatchIafFailure >--> dispatchToAgenda+ else dispatchToAgenda)++dispatchToAgenda, dispatchRedundant, dispatchResults, dispatchTbFailure :: CKY_DispatchFilter++-- | Trivial dispatch filter: always put the item on the agenda and return+-- Nothing+dispatchToAgenda item =+ do addToAgenda item+ return Nothing++-- | If the item can merge with another, merge it with the equivalent+-- item and return Nothing.+-- If the item is indeed unique, return (Just $ setId item)+dispatchRedundant item =+ do st <- get+ let chart = theChart st+ mergeEquivItems o =+ let equiv = canMerge o item+ in (equiv, if equiv then mergeItems o item else o)+ (isEq, newChart) = unzip $ map mergeEquivItems chart+ --+ if or isEq+ then -- trace (ckyShow "-> merge" item []) $+ do put ( st {theChart = newChart} )+ return Nothing+ else do s <- get+ let counter = gencounter s+ put $ s { gencounter = counter + 1 }+ return $ Just $ item { ciId = counter }++-- | If it is a result, put the item in the results list.+-- Otherwise, return (Just unmodified)+dispatchResults item =+ do st <- get+ let synComplete = ciInit item && ciRoot item && ciAdjDone item+ semComplete = tsemVector st == ciSemantics item+ --+ if (synComplete && semComplete )+ then -- trace ("isResult " ++ showItem item) $+ addToResults item >> return Nothing+ else return $ Just item++-- | This filter requires another inversion in thinking. It suceeds+-- if tb unification fails by dispatching to the trash and returning+-- Nothing. If tb unification suceeds, it returns (Just newItem),+-- where newItem has its top and bottom nodes unified.+dispatchTbFailure itemRaw =+ case tbUnify itemRaw of+ Nothing ->+ do addToTrash itemRaw ts_tbUnificationFailure+ return Nothing+ Just item -> return $ Just item++tbUnify :: CkyItem -> Maybe CkyItem+-- things for which tb unification is not relevant+tbUnify item | ciFoot item = return item+tbUnify item | (not.ciAdjDone) item = return item+-- ok, here, we should do tb unification+tbUnify item =+ do let node = ciNode item+ (newTop, sub1) <- unifyFeat (gup node) (gdown node)+ -- it's not enough if t/b unification succeeds by itself+ -- we also have to check that these unifications propagate alright+ let origVars = ciOrigVariables item+ treeVars = ciVariables item+ nodeVars = replace sub1 origVars+ (newVars, _) <- unify treeVars nodeVars+ return $ item+ { ciNode = node { gup = newTop, gdown = [] }+ , ciVariables = newVars }+\end{code}++% --------------------------------------------------------------------+\subsection{Equivalence classes}+\label{sec:cky:merging}+% --------------------------------------------------------------------++\fnlabel{canMerge} returns true if two chart items are allowed to merge.+We do not allow items to merge when they are not "complete", because that+would complicate things like the right sister rule.++\begin{code}+canMerge :: CkyItem -> CkyItem -> Bool+canMerge c1 c2 = ciComplete c1 && ciComplete c2 && stuff c1 == stuff c2+ where stuff x = ( ciNode x, ciSourceTree x, ciSemantics x, ciPolpaths x )+\end{code}++\fnlabel{mergeItems} combines two chart items into one, with the+assumption being that you have already determined that they can be+merged. Information from the second ``slave'' item is merged+into information from the first ``master'' item.++\begin{code}+mergeItems :: CkyItem -> CkyItem -> CkyItem+mergeItems master slave =+ master { ciDerivation = ciDerivation master ++ (ciDerivation slave) }+\end{code}++Note that we do not perform index accesibility filtering on auxiliary+trees. What we're after here is delayed substitution, meaning that we+don't do any substitution until the adjunctions are done. If an+auxiliary tree has substitution nodes, this puts us in the paradoxical+situation where we're trying to delay a substitution which we need in+order to perform an adjunction.++Consider for example, the semantics \texttt{john(j) ask(e1 j e2) go(e2+j w) where(w)} which we intend to realise as \natlang{John asks where to+go}. Depending on your grammar, one conceivable way to realise this is+as an initial tree for ``to go'', and an auxiliary tree for ``asks'' (a+sentential modifier). You plug ``where'' into ``to go'' to get ``where+to go'' and ``John'' into ``asks''. This gives you an auxiliary tree+``John asks'' which adjoins into another tree ``where to go''. Now the+problem is that if you enable iaf on auxiliary trees, you're not going+to be able to construct the ``John asks'' tree because it thinks that+by doing so, you have sealed off access to the \texttt{j} index in+\texttt{go(e2 j w)}. Conclusion: iaf on auxiliary trees is a no-no.++\begin{code}+instance IafAble CkyItem where+ iafAcc = ciAccesible+ iafInacc = ciInaccessible+ iafSetAcc a i = i { ciAccesible = a }+ iafSetInacc a i = i { ciInaccessible = a }+ iafNewAcc i =+ concatMap fromUniConst $ replaceList r $+ concat [ getIdx u | (TagSite _ u _ _) <- ciSubstnodes i ]+ where r = zip (map fromGVar $ ciOrigVariables i)+ (ciVariables i)++dispatchIafFailure :: CkyItem -> CkyState (Maybe CkyItem)+dispatchIafFailure item | ciAux item = return $ Just item+dispatchIafFailure itemRaw =+ do s <- get+ let bmap = ciSemBitMap item+ item = recalculateAccesibility itemRaw+ badSem = iafBadSem (theIafMap s) bmap (tsemVector s) ciSemantics item+ inAcc = iafInacc item+ if badSem == 0+ then -- can't dispatch, but that's good!+ -- (note that we return the item with its iaf criteria updated)+ return $ Just item+ else do addToTrash item (ts_iafFailure inAcc $ bitVectorToSem bmap badSem)+ return Nothing+\end{code}++% --------------------------------------------------------------------+\section{Unpacking the chart}+% --------------------------------------------------------------------++\begin{code}+unpackItem :: CkyStatus -> CkyItem -> [B.Output]+unpackItem st it =+ zip (mAutomatonPaths $ uncurry mJoinAutomata $ unpackItemToAuts st it)+ (repeat [])++type SentenceAutPairMaybe = (Maybe SentenceAut, Maybe SentenceAut)++unpackItemToAuts :: CkyStatus -> CkyItem+ -- left and right automata+ -> SentenceAutPairMaybe+unpackItemToAuts st item =+ case map aut derivations of+ [] -> (Nothing, Nothing)+ (a:as) -> foldr pairUnion a as+ where+ pairUnion (l1,r1) (l2,r2) = (mUnionAutomata l1 l2, mUnionAutomata r1 r2)+ derivations = ciDerivation item+ retrieve = findIdOrBug st+ -- these are fleshed out in the paragraphs below+ aut (KidsToParentOp k) = unpackKidsToParentOp st $ map retrieve k+ aut (NullAdjOp p) = unpackNullAdjOp st $ retrieve p+ aut (SubstOp a p) = unpackSubstOp st (retrieve a) (retrieve p)+ aut (AdjOp a p) = unpackAdjOp st (retrieve a) (retrieve p)+ aut InitOp = unpackInitOp st item+\end{code}++\paragraph{Leaf nodes}++\begin{code}+unpackInitOp :: CkyStatus -> CkyItem -> SentenceAutPairMaybe+unpackInitOp _ item =+ let node = ciNode item+ -- we have to add a transition for each choice in the lexical+ -- atomic disjunction+ lexAut = foldr (\l a -> addTrans a 0 (via l) 1) iAut (glexeme node)+ via l = Just (l, gup node)+ iAut = emptySentenceAut { startSt = 0+ , finalStList = [1]+ , states = [[0,1]]}+ in if isLexeme node+ then case ciTreeSide item of+ LeftSide -> (Just lexAut, Nothing)+ RightSide -> (Nothing, Just lexAut)+ OnTheSpine -> (Nothing, Nothing)+ else (Nothing, Nothing)++emptySentenceAut :: SentenceAut+emptySentenceAut =+ NFA { startSt = (-1)+ , isFinalSt = Nothing+ , finalStList = []+ , transitions = Map.empty+ , states = [[]] }+\end{code}++\paragraph{Null adjunction} is a trivial case; we just propagate the automaton upwards.++\begin{code}+unpackNullAdjOp :: CkyStatus -> CkyItem -> SentenceAutPairMaybe+unpackNullAdjOp st psv = unpackItemToAuts st psv+\end{code}++\paragraph{Substitution} would be as simple as null adjunction, were it+not for auxiliary trees. When dealing with an auxiliary tree, we need+to be careful which side of the spine we substitute into. For those of+you not so familiar with TAG, the spine is the path from root node to+the foot node of an auxiliary tree.++If we're on the left side of the spine, we propagate into the left+automaton. Likewise, we propagate into the right autamaton if we're on+the right side of the spine. If we're trying to substitute \emph{into}+the spine, we're in trouble.++\begin{code}+unpackSubstOp :: CkyStatus -> CkyItem -> CkyItem -> SentenceAutPairMaybe+unpackSubstOp st act psv =+ case ciTreeSide psv of+ LeftSide -> (actAut, Nothing)+ RightSide -> (Nothing, actAut)+ OnTheSpine -> geniBug $ "Tried to substitute on the spine!"+ where actAut = fst $ unpackItemToAuts st act+\end{code}++\paragraph{Adjunction} involves joining the left sides of both items+together as well as the right side. This is probably best explained+with a picture:++FIXME: insert figure++\begin{code}+unpackAdjOp :: CkyStatus -> CkyItem -> CkyItem -> SentenceAutPairMaybe+unpackAdjOp st act psv =+ let (actL, actR) = unpackItemToAuts st act+ (psvL, psvR) = unpackItemToAuts st psv+ newAutL = mJoinAutomata actL psvL+ newAutR = mJoinAutomata psvR actR+ newAut = mJoinAutomata newAutL newAutR+ in case ciTreeSide psv of+ LeftSide -> (newAut, Nothing)+ RightSide -> (Nothing, newAut)+ OnTheSpine -> (newAutL, newAutR)+\end{code}++\paragraph{The kids to parents rule} is complicated because of auxiliary+trees. As usual, there are three cases:++\begin{itemize}+\item On the left of the spine: we concatenate all the left+ automata of the kids+\item On the right of the spine: we concatenate all the right+ automata of the kids+\item On the spine itself: we concatenate all the left automata+ of the stuff on the left of the spine and propagate that+ as our left side. Similarly, we concatenate all the right+ automata of the stuff on the right of the spine and send+ that up the right side.+\end{itemize}++\begin{code}+unpackKidsToParentOp :: CkyStatus -> [CkyItem] -> SentenceAutPairMaybe+unpackKidsToParentOp st kids =+ let (bef, aft) = span (not.ciOnTheSpine) kids+ (befL, befR) = unzip $ map (unpackItemToAuts st) bef+ concatAut_ theLast auts = foldr mJoinAutomata theLast auts+ concatAut = concatAut_ Nothing+ in case aft of+ -- two cases in one! (we expect one of these to be Nothing)+ -- we're either on the left or the right of the spine+ [] -> ( concatAut befL, concatAut befR )+ -- we are on the spine: we attach the left automaton of the+ -- spinal child to the other left automata and likewise,+ -- its right automaton to the rest of the right automata+ (spi:aft2) ->+ let (spiL, spiR) = unpackItemToAuts st spi+ (_ , aftR) = unzip $ map (unpackItemToAuts st) aft2+ in ( concatAut_ spiL befL, concatAut (spiR:aftR) )+\end{code}++\subsection{Core automaton stuff}++Note: you might be tempted to move this code to the generic Automaton library.+In order to do this, you will have to introduce a geniric notion of+state-renaming to the library. I didn't want to bother with any of that.++\begin{code}+mUnionAutomata :: Maybe SentenceAut -> Maybe SentenceAut -> Maybe SentenceAut+mUnionAutomata Nothing mAut2 = mAut2+mUnionAutomata mAut1 Nothing = mAut1+mUnionAutomata (Just aut1) (Just aut2) = Just $ unionAutomata aut1 aut2++-- | Merge two sentence automata together. This essentially calculates the+-- union of the two automata and "pinches" their final states together.+-- FIXME: could be much more sophisticated and produce smaller automata!+unionAutomata :: SentenceAut -> SentenceAut -> SentenceAut+unionAutomata aut1 rawAut2 =+ let -- rename all the states in aut2 so that they don't overlap+ aut1Max = foldr max (-1) $ concat $ states aut1+ aut2 = incrStates (1 + aut1Max) rawAut2+ -- make the start state of the new automaton also transition+ -- everything that the from the start state of aut2 transitions to+ t1 = transitions aut1+ t2 = transitions aut2+ aut2Start = startSt aut2+ addAut2Trans = Map.unionWith (++) $ Map.findWithDefault Map.empty aut2Start t2+ newT1 = Map.adjust addAut2Trans (startSt aut1) t1+ newT2 = Map.delete aut2Start t2+ in aut1 { states = [ delete aut2Start $ concat $ states aut1 ++ states aut2 ]+ , transitions = Map.union newT1 newT2+ , isFinalSt = do -- in the Maybe Monad+ f1 <- isFinalSt aut1+ f2 <- isFinalSt aut2+ return $ \s -> f1 s || f2 s+ , finalStList = finalStList aut1 ++ finalStList aut2 }+\end{code}++It's important not to confuse \fnreflite{joinAutomata} with+\fnreflite{unionAutomata}. Joining automata is basically concatenation,+putting the second automaton after the first one.+Interestingly, their implementations have a lot in common.+FIXME: it might be worth refactoring the two.++\begin{code}+mJoinAutomata :: Maybe SentenceAut -> Maybe SentenceAut -> Maybe SentenceAut+mJoinAutomata Nothing mAut2 = mAut2+mJoinAutomata mAut1 Nothing = mAut1+mJoinAutomata (Just aut1) (Just aut2) = Just $ joinAutomata aut1 aut2++-- | Concatenate two sentence automata. This merges the final state of the+-- first automaton into the initial state of the second automaton.+joinAutomata :: SentenceAut -> SentenceAut -> SentenceAut+joinAutomata aut1 rawAut2 =+ let -- rename all the states in aut2 so that they don't overlap+ aut1Max = (maximum.concat.states) aut1+ aut2 = incrStates (1 + aut1Max) rawAut2+ -- replace all transitions to aut1's final st by+ -- transitions to aut2's start state+ aut1Final = finalSt aut1+ aut2Start = startSt aut2+ t1 = transitions aut1+ t2 = transitions aut2+ updateKey k m = case Map.lookup k m of+ Nothing -> m+ Just v -> Map.insert aut2Start v (Map.delete k m)+ replaceFinal (f,t) = (f, foldr updateKey t aut1Final)+ newT1 = Map.fromList $ map replaceFinal $ Map.toList t1+ newStates1 = map (\\ aut1Final) $ states aut1+ --+ in aut1 { states = [ concat $ newStates1 ++ states aut2 ]+ , transitions = Map.union newT1 t2+ , isFinalSt = isFinalSt aut2+ , finalStList = finalStList aut2 }++incrStates :: Int -> SentenceAut -> SentenceAut+incrStates prefix aut =+ let -- increment a state+ addP_s = (prefix +)+ -- increment all the states involved in a transition+ addP_t (st,l) = (addP_s st, Map.mapKeys addP_s l)+ in aut { startSt = addP_s (startSt aut)+ , states = map (map addP_s) $ states aut+ , transitions = Map.fromList $ map addP_t $+ Map.toList $ transitions aut+ , finalStList = map addP_s $ finalStList aut }++mAutomatonPaths :: (Ord st, Ord ab) => Maybe (NFA st ab) -> [[ab]]+mAutomatonPaths Nothing = []+mAutomatonPaths (Just x) = automatonPaths x+\end{code}++\subsection{Item history}++We don't ever really need to calculate the derivation tree for an item.+Don't get me wrong, we certainly calculate something which looks a lot+like a derivation tree and contains more or less the same stuff, but not+a derivation tree per se.++On the otherhand, debugging the generator is \emph{much} easier if you+can get a graphical representation for an item. This is like a+derivation tree with way too much detail. We calculate a tree-like+representation of the history of inference rule applications for this+item.++Note that because of equivalence classes, an item can be seen as having+more than one derivation. We abstract around this fact simply by+implementing the function with a \verb!List! monad.++\begin{code}+-- | Returns all the derivations trees for this item: note that this is+-- not a TAG derivation tree but a history of inference rule applications+-- in tree form+extractDerivations :: CkyStatus -> CkyItem -> [ Tree (ChartId, String) ]+extractDerivations st item =+ do chartOp <- ciDerivation item+ case chartOp of+ KidsToParentOp kids ->+ do kidTrees <- mapM treeFor kids+ createNode "kids" kidTrees+ SubstOp act psv ->+ do actTree <- treeFor act+ let psvTree = Node (psv, "subst") [ actTree ]+ createNode "subst-finish" [psvTree]+ AdjOp act psv ->+ do actTree <- treeFor act+ let psvTree = Node (psv, "adj") [ actTree ]+ createNode "adj-finish" [psvTree]+ NullAdjOp psv ->+ do psvTree <- treeFor psv+ createNode "no-adj" [psvTree]+ InitOp -> createNode "init" []+ where+ createNode op kids =+ return $ Node (ciId item, op) kids+ treeFor i =+ case findId st i of+ Nothing -> geniBug $ "derivation for item " ++ (show $ ciId item)+ ++ "points to non-existent item " ++ (show i)+ Just x -> extractDerivations st x+\end{code}++\subsection{Helpers for unpacking}++\begin{code}+findId :: CkyStatus -> ChartId -> Maybe CkyItem+findId st i = find (\x -> ciId x == i) $ theChart st ++ (theAgenda st) ++ (theResults st) ++ (theTrash st)++-- | The same as 'findId' but calls 'geniBug' if not found+findIdOrBug :: CkyStatus -> ChartId -> CkyItem+findIdOrBug st i =+ case findId st i of+ Nothing -> geniBug $ "Cannot find item in chart with id " ++ (show i)+ Just x -> x+\end{code}++\section{Optimisations}++\paragraph{Earley-style derivation}++The idea is that we to perform substitutions in a fixed order so that we avoid+generating a lot of useless chart items that aren't going to be used in a final+result anyway.++We implement this in two places. In the initialisation phase, (page+\pageref{fn:cky:initTree}), we avoid placing all the leaf items onto the+agenda. Instead, we make each leaf node point to the next leaf, as+with a singly linked list, and put the head of that list on the agenda.+The second part of this is implemented below as an inference rule which+takes only complete items (i.e. items for which there is no need to+perform substitution) and releases their payload.++Note that in order for this to work, we also had to introduce a+restriction into chart item merging (page \pageref{sec:cky:merging})+that no two items may merge if they are not complete in the same sense+as this inference rule. Otherwise, we'd have to think find a way to+make sure that payloads get released correctly (which might not be as+hard as I first thought).++
+ src/NLP/GenI/CkyEarley/CkyGui.lhs view
@@ -0,0 +1,456 @@+% GenI surface realiser+% Copyright (C) 2005 Carlos Areces and Eric Kow+%+% This program is free software; you can redistribute it and/or+% modify it under the terms of the GNU General Public License+% as published by the Free Software Foundation; either version 2+% of the License, or (at your option) any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; if not, write to the Free Software+% Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.++\chapter{CKY Gui}++\begin{code}+{-# LANGUAGE FlexibleInstances, TypeSynonymInstances #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+module NLP.GenI.CkyEarley.CkyGui where+\end{code}++\ignore{+\begin{code}+import Graphics.UI.WX hiding (when)++import qualified Control.Monad as Monad +import Control.Monad (liftM)++import Data.IORef+import Data.List (intersperse, findIndex, sort)+import qualified Data.Map as Map +import Data.Maybe (listToMaybe, catMaybes)+import Data.Tree ++import NLP.GenI.Statistics (Statistics)++import NLP.GenI.Automaton+ ( NFA(states, transitions, startSt, finalStList)+ , addTrans )+import qualified NLP.GenI.Builder as B+import qualified NLP.GenI.BuilderGui as BG+import NLP.GenI.Btypes ( GNode, gnname )++import NLP.GenI.CkyEarley.CkyBuilder+ ( ckyBuilder, earleyBuilder, CkyStatus, CkyItem(..), ChartId+ , ciRoot, ciAdjDone+ , bitVectorToSem, findId+ , extractDerivations+ , theResults, theAgenda, theChart, theTrash+ , emptySentenceAut, mJoinAutomata, mAutomatonPaths+ , unpackItemToAuts,+ )+import NLP.GenI.Configuration ( Params(..) )++import NLP.GenI.Geni+ ( ProgStateRef, runGeni, GeniResult )+import NLP.GenI.General ( boundsCheck, geniBug )+import NLP.GenI.GuiHelper+ ( messageGui, toSentence+ , debuggerPanel, DebuggerItemBar+ , addGvHandler, modifyGvParams+ , GraphvizGuiSt(gvitems, gvsel, gvparams), GvIO, setGvSel+ , graphvizGui, newGvRef, setGvDrawables,+ )++import NLP.GenI.Tags ( idname, tsemantics, ttree, TagElem )++import NLP.GenI.Graphviz+ ( GraphvizShow(..), gvNode, gvEdge, gvSubgraph, gvUnlines, gvShowTree+ , gvNewline+ , GraphvizShowNode(..) )+\end{code}+}++% --------------------------------------------------------------------+\section{Interface}+% --------------------------------------------------------------------++\begin{code}+ckyGui, earleyGui :: BG.BuilderGui+ckyGui = ckyOrEarleyGui False+earleyGui = ckyOrEarleyGui True++ckyOrEarleyGui :: Bool -> BG.BuilderGui+ckyOrEarleyGui isEarley = BG.BuilderGui {+ BG.resultsPnl = resultsPnl builder+ , BG.debuggerPnl = ckyDebuggerTab builder }+ where builder = if isEarley then earleyBuilder else ckyBuilder++resultsPnl :: B.Builder CkyStatus CkyItem Params -> ProgStateRef -> Window a -> IO ([GeniResult], Statistics, Layout)+resultsPnl builder pstRef f =+ do (sentences, stats, st) <- runGeni pstRef builder+ (lay, _, _) <- realisationsGui pstRef f (theResults st)+ return (sentences, stats, lay)+\end{code}++% --------------------------------------------------------------------+\section{Results}+\label{sec:cky_results_gui}+% --------------------------------------------------------------------++\begin{code}+-- | Browser for the results (if there are any)+realisationsGui :: ProgStateRef -> (Window a) -> [CkyItem]+ -> GvIO CkyDebugParams (Maybe CkyItem)+realisationsGui _ f [] =+ do m <- messageGui f "No results found"+ gvRef <- newGvRef initCkyDebugParams [] ""+ return (m, gvRef, return ())+realisationsGui _ f resultsRaw =+ do let tip = "result"+ results = map Just resultsRaw+ labels = map (toSentence.ciSourceTree) resultsRaw+ gvRef <- newGvRef initCkyDebugParams labels tip+ setGvDrawables gvRef results+ graphvizGui f "cky-results" gvRef+\end{code}++\begin{code}+data CkyDebugParams = + CkyDebugParams { debugShowFeats :: Bool + , debugShowFullDerv :: Bool+ , debugShowSourceTree :: Bool+ , debugWhichDerivation :: Int+ , debugNodeChoice :: [ChartId] }++initCkyDebugParams :: CkyDebugParams+initCkyDebugParams = + CkyDebugParams { debugShowFeats = False+ , debugShowFullDerv = False+ , debugShowSourceTree = False+ , debugWhichDerivation = 0+ , debugNodeChoice = [] }++-- would be nice if Haskell sugared this kind of stuff for us+setDebugShowFeats, setDebugShowFullDerv, setDebugShowSourceTree :: Bool -> CkyDebugParams -> CkyDebugParams+setDebugShowFeats b x = x { debugShowFeats = b }+setDebugShowFullDerv b x = x { debugShowFullDerv = b }+setDebugShowSourceTree b x = x { debugShowSourceTree = b }++setDebugWhichDerivation :: Int -> CkyDebugParams -> CkyDebugParams+setDebugWhichDerivation w x = x { debugWhichDerivation = w }++clearDebugNodeChoice :: CkyDebugParams -> CkyDebugParams+clearDebugNodeChoice x = x { debugNodeChoice = [] }++pushDebugNodeChoice :: ChartId -> CkyDebugParams -> CkyDebugParams+pushDebugNodeChoice w x = x { debugNodeChoice = w:(debugNodeChoice x) }++popDebugNodeChoice :: CkyDebugParams -> Maybe (ChartId, CkyDebugParams)+popDebugNodeChoice x =+ case debugNodeChoice x of+ [] -> Nothing+ (h:t) -> Just (h, x { debugNodeChoice = t })++ckyDebuggerTab :: B.Builder CkyStatus CkyItem Params+ -> (Window a) -> Params -> B.Input -> String -> IO Layout+ckyDebuggerTab builder = debuggerPanel builder initCkyDebugParams stateToGv ckyItemBar+ where + stateToGv :: CkyStatus -> [(Maybe (CkyStatus,CkyItem), String)]+ stateToGv st = + let agenda = section "AGENDA" $ theAgenda st+ trash = section "TRASH" $ theTrash st+ chart = section "CHART" $ theChart st+ results = section "RESULTS" $ theResults st+ --+ section n i = hd : (map tlFn i)+ where hd = (Nothing, "___" ++ n ++ "___")+ tlFn x = (Just (st,x), labelFn x)+ showPaths = const ""+ {- if (polarised $ genconfig st)+ then (\t -> " (" ++ showPolPaths t ++ ")")+ else const "" -}+ gorn i = case gornAddressStr (ttree $ ciSourceTree i) (ciNode i) of+ Nothing -> geniBug "A chart item claims to have a node which is not in its tree"+ Just x -> x+ isComplete i = ciRoot i && ciAdjDone i+ -- try displaying as an automaton, or if all else fails, the tree sentence+ fancyToSentence ci =+ let mergedAut = uncurry mJoinAutomataUsingHole $ unpackItemToAuts st ci+ boringSentence = toSentence $ ciSourceTree ci+ in case mAutomatonPaths mergedAut of+ [] -> boringSentence+ (h:_) -> unwords $ map fst $ h+ labelFn i = unwords [ completeStr ++ idStr ++ gornStr+ , fancyToSentence i+ , "/" ++ (idname $ ciSourceTree i)+ , showPaths i+ ]+ where idStr = show $ ciId i+ completeStr = if isComplete i then ">" else ""+ gornStr = if isComplete i then "" else " g" ++ (gorn i)+ in agenda ++ chart ++ results ++ trash++ckyItemBar :: DebuggerItemBar CkyDebugParams (CkyStatus, CkyItem)+ckyItemBar f gvRef updaterFn =+ do ib <- panel f []+ -- select derivation+ derTxt <- staticText ib []+ derChoice <- choice ib [ tooltip := "Select a derivation" ]+ jumpBtn <- button ib [ text := "Go to node" ]+ unjumpBtn <- button ib [ text := "Pop back" ]+ jumpChoice <- choice ib [ tooltip := "Jump to item." ]+ let onDerChoice =+ do sel <- get derChoice selection+ modifyGvParams gvRef (setDebugWhichDerivation sel)+ gvSt <- readIORef gvRef+ -- update the list of jump choices+ case Map.lookup (gvsel gvSt) (gvitems gvSt) of+ Just (Just (s,c)) -> do+ let t = selectedDerivation (gvparams gvSt) s c+ nodes = map show $ sort $ derivationNodes t+ set jumpChoice [ items := nodes, selection := 0 ]+ updaterFn+ _ -> return ()+ set derChoice [ on select := onDerChoice ]+ -- show features+ detailsChk <- checkBox ib [ text := "features"+ , enabled := False, checked := False ]+ fullDervChk <- checkBox ib [ text := "full derivation"+ , checked := False ]+ srcTreeChk <- checkBox ib [ text := "src tree"+ , checked := False ]+ let setChkBoxUpdater box setter =+ set box [ on command := do isChecked <- get box checked+ modifyGvParams gvRef $ setter isChecked+ updaterFn ]+ setChkBoxUpdater detailsChk setDebugShowFeats+ setChkBoxUpdater fullDervChk setDebugShowFullDerv+ setChkBoxUpdater srcTreeChk setDebugShowSourceTree+ -- make detailsChk conditioned on srcTreeChk+ set srcTreeChk [ on command :~ \x -> x >> do+ isChecked <- get srcTreeChk checked+ set detailsChk [ enabled := isChecked ]+ ]+ -- add a handler for when an item is selected: + -- update the list of derivations to choose from+ let updateDerTxt t = set derTxt [ text := "Deriviations (" ++ t ++ ")" ]+ handler gvSt = + do case Map.lookup (gvsel gvSt) (gvitems gvSt) of+ Just (Just (s,c)) ->+ do let derivations = extractDerivations s c + dervLabels = zipWith (\n _ -> show n) ([1..]::[Int]) derivations+ set derChoice [ enabled := True, items := dervLabels, selection := 0 ]+ onDerChoice+ updateDerTxt $ show $ length derivations+ _ ->+ do set derChoice [ enabled := False, items := [] ]+ updateDerTxt "n/a"+ addGvHandler gvRef handler+ -- call the handler to react to the first selection+ handler `liftM` readIORef gvRef+ -- pushing and popping between nodes+ let jumpToNode jmpTo =+ do gvSt <- readIORef gvRef+ let chartItems = Map.elems $ gvitems gvSt+ case findIndex isJmpTo chartItems of+ Nothing -> geniBug $ "Was asked to see node " ++ (show jmpTo) ++ ", which is not in the list"+ Just x ->+ do setGvSel gvRef x+ modifyGvParams gvRef (setDebugWhichDerivation 0)+ readIORef gvRef >>= handler+ updaterFn+ where isJmpTo Nothing = False+ isJmpTo (Just (_,x)) = ciId x == jmpTo+ set jumpBtn [ on command := do+ gvSt <- readIORef gvRef+ case Map.lookup (gvsel gvSt) (gvitems gvSt) of+ Just (Just x) -> modifyGvParams gvRef (pushDebugNodeChoice $ (ciId.snd) x)+ _ -> return ()+ jmpSel <- get jumpChoice selection+ jmpItms <- get jumpChoice items+ let jmpTo = (read $ jmpItms !! jmpSel)+ jumpToNode jmpTo ]++ set unjumpBtn [ on command := do+ gvSt <- readIORef gvRef+ case popDebugNodeChoice (gvparams gvSt) of+ Nothing -> return ()+ Just (x,gvParam) -> do modifyGvParams gvRef (const gvParam)+ jumpToNode x ]+ --+ return $ hfloatCentre $ container ib $ column 0 $+ [ row 5+ [ label "Show...", widget fullDervChk, widget srcTreeChk, widget detailsChk ]+ , row 5+ [ widget derTxt, widget derChoice+ , hspace 5, label "Node", widget jumpChoice, widget jumpBtn, widget unjumpBtn ] ]+\end{code}++\section{Helper code}++\begin{code}++gornAddressStr :: Tree GNode -> GNode -> Maybe String+gornAddressStr t target =+ (concat . intersperse "." . map show) `liftM` gornAddress t target++gornAddress :: Tree GNode -> GNode -> Maybe [Int]+gornAddress tr target = reverse `liftM` helper [] tr+ where+ helper current (Node x _) | (gnname x == gnname target) = Just current+ helper current (Node _ l) = listToMaybe $ catMaybes $+ zipWith (\c t -> helper (c:current) t) [1..] l+++selectedDerivation :: CkyDebugParams -> CkyStatus -> CkyItem -> Tree (ChartId, String)+selectedDerivation f s c =+ let derivations = extractDerivations s c+ whichDer = debugWhichDerivation f+ in if boundsCheck whichDer derivations+ then derivations !! whichDer+ else geniBug $ "Bounds check failed on derivations selector:\n"+ ++ "Selected derivation: " ++ (show whichDer) ++ "\n"+ ++ "Bounds: 0 to " ++ (show $ length derivations - 1)++derivationNodes :: Tree (ChartId, String) -> [ChartId]+derivationNodes = (map fst).flatten++-- | Remove na and subst or adj completion links+thinDerivationTree :: Tree (ChartId, String) -> Tree (ChartId, String)+thinDerivationTree =+ let thinlst = ["no-adj", "subst", "adj" ]+ helper n@(Node _ []) = n+ -- this is made complicated for fancy highlighting to work+ helper (Node (i,op) [k]) | op `elem` thinlst = (Node (i,op2) k2)+ where (Node (_,op2) k2) = helper k+ helper (Node x kids) = (Node x $ map helper kids)+ in helper++instance GraphvizShow CkyDebugParams (CkyStatus, CkyItem) where+ graphvizLabel f (_,c) = graphvizLabel f c+ graphvizParams f (_,c) = graphvizParams f c+ graphvizShowAsSubgraph f p (s,c) = + let color_ x = ("color", x)+ label_ x = ("label", x)+ style_ x = ("style", x)+ arrowtail_ x = ("arrowtail", x)+ --+ substColor = color_ "blue"+ adjColor = color_ "red"+ --+ edgeParams (_ ,"no-adj") = [ label_ "na" ]+ edgeParams (_, "kids" ) = []+ edgeParams (_, "init" ) = [ label_ "i" ]+ edgeParams (_, "subst" ) = [ substColor ]+ edgeParams (_, "adj" ) = [ adjColor ]+ edgeParams (_, "subst-finish") = [ substColor, style_ "bold" , arrowtail_ "normal" ]+ edgeParams (_, "adj-finish") = [ adjColor , style_ "dashed, bold", arrowtail_ "normal" ]+ edgeParams (_, k) = [ ("label", "UNKNOWN: " ++ k) ]+ --+ whichDer = debugWhichDerivation f+ showFullDer = debugShowFullDerv f+ showSrcTree = debugShowSourceTree f+ showTree i t = gvSubgraph $ gvShowTree edgeParams (s,showFullDer, [ciId c]) prfx t+ where prfx = p ++ "t" ++ (show i)+ gvDerv = showTree whichDer $ if showFullDer then t else thinDerivationTree t+ where t = selectedDerivation f s c+ --+ joinedAut = uncurry mJoinAutomataUsingHole $ unpackItemToAuts s c+ gvAut = graphvizShowAsSubgraph () (p ++ "aut") joinedAut+ --+ showFeats = debugShowFeats f+ treeParams = unlines $ graphvizParams showFeats $ ciSourceTree c+ -- FIXME: will have to make this configurable, maybe, show aut, show tree? radio button?+ in "\n// ------------------- derivations --------------------------\n"+ ++ treeParams ++ "node [ shape = plaintext, peripheries = 0 ]\n"+ ++ gvDerv+ ++ "\n// ------------------- automata (joined) ------------------------\n"+ ++ gvSubgraph gvAut+ ++ if showSrcTree+ then ("\n// ------------------- elementary tree --------------------------\n"+ ++ treeParams ++ graphvizShowAsSubgraph f p c)+ else ""++instance GraphvizShowNode (CkyStatus,Bool,[ChartId]) (ChartId, String) where+ graphvizShowNode (st,showFullDerv,highlight) prefix (theId,_) =+ let idStr = show theId+ treename i = " (" ++ ((idname.ciSourceTree) i) ++ ")"+ txt = case findId st theId of+ Nothing -> ("???" ++ idStr)+ Just i -> idStr ++ " " ++ (show.ciNode) i+ ++ (if showFullDerv then treename i else "")+ custom = if theId `elem` highlight then [ ("fontcolor","red") ] else []+ in gvNode prefix txt custom++instance GraphvizShow CkyDebugParams CkyItem where+ graphvizLabel f ci =+ graphvizLabel (debugShowFeats f, nullHlter) (toTagElem ci) +++ gvNewline ++ (gvUnlines $ ciDiagnostic ci)++ graphvizShowAsSubgraph f prefix ci = + let showFeats = debugShowFeats f+ hlter n = (n, if (gnname n) == (gnname $ ciNode ci)+ then Just "red" else Nothing)+ in graphvizShowAsSubgraph (showFeats,hlter) (prefix ++ "tree") $ toTagElem ci++nullHlter :: GNode -> (GNode, Maybe String)+nullHlter a = (a,Nothing)++toTagElem :: CkyItem -> TagElem+toTagElem ci =+ te { ttree = ttree te+ , tsemantics = bitVectorToSem (ciSemBitMap ci) (ciSemantics ci) }+ where te = ciSourceTree ci++-- FIXME: this is largely copy-and-pasted from Polarity.lhs +-- it should be refactored later+instance GraphvizShow () B.SentenceAut where+ graphvizShowAsSubgraph _ prefix aut =+ let st = (concat.states) aut+ ids = map (\x -> prefix ++ show x) ([0..]::[Int])+ -- map which permits us to assign an id to a state+ stmap = Map.fromList $ zip st ids+ lookupFinal x = Map.findWithDefault "error_final" x stmap+ in -- final states should be a double-edged ellispse+ "node [ shape = ellipse, peripheries = 2 ]; "+ ++ (unlines $ map lookupFinal $ finalStList aut)+ -- any other state should be an ellipse+ ++ "node [ shape = ellipse, peripheries = 1 ]\n"+ -- draw the states and transitions + ++ (concat $ zipWith gvShowState ids st) + ++ (concat $ zipWith (gvShowTrans aut stmap) ids st )++type SentenceAutState = Int ++gvShowState :: String -> SentenceAutState -> String+gvShowState stId st = gvNode stId (show st) []++gvShowTrans :: B.SentenceAut -> Map.Map SentenceAutState String+ -> String -> SentenceAutState -> String +gvShowTrans aut stmap idFrom st = + let -- outgoing transition labels from st+ trans = Map.findWithDefault Map.empty st $ transitions aut+ -- returns the graphviz dot command to draw a labeled transition+ drawTrans (stTo,x) = case Map.lookup stTo stmap of+ Nothing -> drawTrans' ("id_error_" ++ (show stTo)) x + Just idTo -> drawTrans' idTo x+ drawTrans' idTo x = gvEdge idFrom idTo (drawLabel x) []+ drawLabel labels = gvUnlines $ map fst $ catMaybes labels + in unlines $ map drawTrans $ Map.toList trans+\end{code}++\begin{code}+-- | join two automata, inserting a ".." transition between them+mJoinAutomataUsingHole :: Maybe B.SentenceAut -> Maybe B.SentenceAut -> Maybe B.SentenceAut+mJoinAutomataUsingHole aut1 Nothing = aut1+mJoinAutomataUsingHole aut1 aut2 =+ mJoinAutomata aut1 $ mJoinAutomata (Just holeAut) aut2+ where holeAut = addTrans emptyA 0 (Just ("..",[])) 1+ emptyA = emptySentenceAut { startSt = 0, finalStList = [1], states = [[0,1]] }+\end{code}
+ src/NLP/GenI/Configuration.lhs view
@@ -0,0 +1,873 @@+% GenI surface realiser+% Copyright (C) 2005 Carlos Areces and Eric Kow+%+% This program is free software; you can redistribute it and/or+% modify it under the terms of the GNU General Public License+% as published by the Free Software Foundation; either version 2+% of the License, or (at your option) any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; if not, write to the Free Software+% Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.++\chapter{Command line arguments}++\begin{code}+{-# LANGUAGE ExistentialQuantification #-}+module NLP.GenI.Configuration+ ( Params(..), GrammarType(..), BuilderType(..), Instruction, Flag+ -- flags+ , BatchDirFlg(..)+ , DisableGuiFlg(..)+ , EarlyDeathFlg(..)+ , ExtraPolaritiesFlg(..)+ , FromStdinFlg(..)+ , HelpFlg(..)+ , IgnoreSemanticsFlg(..)+ , InstructionsFileFlg(..)+ , LexiconFlg(..)+ , MacrosFlg(..)+ , MaxTreesFlg(..)+ , MetricsFlg(..)+ , MorphCmdFlg(..)+ , MorphInfoFlg(..)+ , MorphLexiconFlg(..)+ , NoLoadTestSuiteFlg(..)+ , OptimisationsFlg(..)+ , OutputFileFlg(..)+ , PartialFlg(..)+ , RegressionTestModeFlg(..)+ , RootFeatureFlg(..)+ , RunUnitTestFlg(..)+ , StatsFileFlg(..)+ , TestCaseFlg(..)+ , TestInstructionsFlg(..)+ , TestSuiteFlg(..)+ , TimeoutFlg(..)+ , TracesFlg(..)+ , VerboseModeFlg(..)+ , ViewCmdFlg(..)+ --+ , mainBuilderTypes+ , getFlagP, getListFlagP, setFlagP, hasFlagP, deleteFlagP, hasOpt, polarised+ , getFlag, setFlag, hasFlag+ , Optimisation(..)+ , rootcatfiltered, semfiltered+ , isIaf+ , emptyParams, defineParams+ , treatArgs, treatStandardArgs, treatArgsWithParams, treatStandardArgsWithParams+ , processInstructions+ , optionsForStandardGenI+ , optionsForBasicStuff, optionsForOptimisation, optionsForMorphology, optionsForInputFiles+ , optionsForBuilder, optionsForTesting+ , nubBySwitches+ , noArg, reqArg, optArg+ , parseFlagWithParsec+ -- re-exports+ , module System.Console.GetOpt+ , Typeable+ )+where+\end{code}++\ignore{+\begin{code}+import qualified Data.Map as Map++import Control.Monad ( liftM )+import Data.Char ( toLower )+import Data.Maybe ( listToMaybe, mapMaybe )+import Data.Typeable ( Typeable, typeOf, cast )+import System.Console.GetOpt+import System.Exit ( exitFailure, exitWith, ExitCode(..) )+import Data.List ( find, intersperse, nubBy )+import Data.Maybe ( catMaybes, fromMaybe, isNothing, fromJust )+import Text.ParserCombinators.Parsec ( runParser, CharParser )++import NLP.GenI.Btypes ( GeniVal(GConst), Flist, showFlist, )+import NLP.GenI.General ( geniBug, fst3, snd3, Interval )+import NLP.GenI.GeniParsers ( geniFeats, geniPolarities )+\end{code}+}++% --------------------------------------------------------------------+% Code for debugging. (should be latex-commented+% when not in use)+% --------------------------------------------------------------------++%\begin{code}+%import Debug.Trace+%\end{code}++% --------------------------------------------------------------------+% Params+% --------------------------------------------------------------------++\begin{code}+-- | Holds the specification for how Geni should be run, its input+-- files, etc. This is the stuff that would normally be found in+-- the configuration file.+data Params = Prms{+ grammarType :: GrammarType,+ builderType :: BuilderType,+ geniFlags :: [Flag]+} deriving (Show)++hasOpt :: Optimisation -> Params -> Bool+hasOpt o p = maybe False (elem o) $ getFlagP OptimisationsFlg p++polarised, isIaf :: Params -> Bool+rootcatfiltered, semfiltered :: Params -> Bool+polarised = hasOpt Polarised+isIaf = hasOpt Iaf+semfiltered = hasOpt SemFiltered+rootcatfiltered = hasOpt RootCatFiltered++hasFlagP :: (Typeable f, Typeable x) => (x -> f) -> Params -> Bool+deleteFlagP :: (Typeable f, Typeable x) => (x -> f) -> Params -> Params+setFlagP :: (Eq f, Show f, Show x, Typeable f, Typeable x) => (x -> f) -> x -> Params -> Params+getFlagP :: (Show f, Show x, Typeable f, Typeable x) => (x -> f) -> Params -> Maybe x+getListFlagP :: (Show f, Show x, Typeable f, Typeable x) => ([x] -> f) -> Params -> [x]++hasFlagP f = hasFlag f . geniFlags+deleteFlagP f p = p { geniFlags = deleteFlag f (geniFlags p) }+setFlagP f v p = p { geniFlags = setFlag f v (geniFlags p) }+getFlagP f = getFlag f . geniFlags+getListFlagP f = fromMaybe [] . getFlagP f+-- | The default parameters configuration+emptyParams :: Params+emptyParams = Prms {+ builderType = SimpleBuilder,+ grammarType = GeniHand,+ geniFlags = [ Flag ViewCmdFlg "ViewTAG"+ , Flag RootFeatureFlg defaultRootFeat ]+}+\end{code}++% --------------------------------------------------------------------+\section{Command line arguments}+% --------------------------------------------------------------------++Command line arguments can be specified in the GNU style, for example+\texttt{--foo=bar} or \texttt{--foo bar}, or \texttt{-f bar} when a+short switch is available. For more information, type \texttt{geni+--help}.++\begin{code}+-- | Uses the GetOpt library to process the command line arguments.+-- Note that we divide them into basic and advanced usage.+optionsForStandardGenI :: [OptDescr Flag]+optionsForStandardGenI =+ nubBySwitches $ concatMap snd3 optionsSections+ ++ -- FIXME: weird mac stuff+ [ Option ['p'] [] (reqArg WeirdFlg id "CMD") "" ]++optionsSections :: [(String,[OptDescr Flag],[String])]+optionsSections =+ [ ("Core options", optionsForBasicStuff, example)+ , ("Input", optionsForInputFiles, [])+ , ("Output", optionsForOutput, [])+ , ("Algorithm",+ (nubBySwitches $ optionsForBuilder ++ optionsForOptimisation),+ usageForOptimisations)+ , ("Morphology", optionsForMorphology, [])+ , ("User interface", optionsForUserInterface, [])+ , ("Batch processing", optionsForTesting, [])+ , ("Miscellaneous", nubBySwitches $ optionsForIgnoreSem, [])+ ]+ where+ example = [ "Example:"+ , " geni -m examples/ej/mac -l examples/ej/lexicon -s examples/ej/suite"+ ]++getSwitches :: OptDescr a -> ([Char],[String])+getSwitches (Option s l _ _) = (s,l)++nubBySwitches :: [OptDescr a] -> [OptDescr a]+nubBySwitches = nubBy (\x y -> getSwitches x == getSwitches y)+\end{code}++\subsection{Essential arguments}++See also section \ref{sec:optimisations} for more details on+optimisations.++% FIXME: what would be great is some special processing of the+% code below so that the documentation writes itself++\begin{code}+-- GetOpt wrappers+noArg :: forall f . (Eq f, Show f, Typeable f)+ => (() -> f) -> ArgDescr Flag+noArg s = NoArg (Flag s ())++reqArg :: forall f x . (Eq f, Show f, Typeable f, Eq x, Show x, Typeable x)+ => (x -> f) -- ^ flag+ -> (String -> x) -- ^ string reader for flag (probably |id| if already a String)+ -> String -- ^ description+ -> ArgDescr Flag+reqArg s fn desc = ReqArg (\x -> Flag s (fn x)) desc++optArg :: forall f x . (Eq f, Show f, Typeable f, Eq x, Show x, Typeable x)+ => (x -> f) -- ^ flag+ -> x -- ^ default value+ -> (String -> x) -- ^ string reader (as in @reqArg@)+ -> String -- ^ description+ -> ArgDescr Flag+optArg s def fn desc = OptArg (\x -> Flag s (maybe def fn x)) desc+\end{code}++\begin{code}+-- -------------------------------------------------------------------+-- Parsing command line arguments+-- -------------------------------------------------------------------++usage :: Bool -- ^ advanced+ -> String+usage adv =+ let header = "Usage: geni [OPTION...]\n"+ tweakBasic (x,y,z) = (x,y,z ++ ["See geni --help for more details"])+ sections = if adv+ then optionsSections+ else map tweakBasic $ take 1 optionsSections+ body = unlines $ map usageSection sections+ in header ++ body++usageSection :: (String, [OptDescr Flag],[String]) -> String+usageSection (name, opts, comments) =+ usageInfo (unlines $ [bar,name, bar]) opts ++ mcomments+ where+ bar = replicate 72 '='+ mcomments = if null comments then [] else "\n" ++ unlines comments++treatStandardArgs :: [String] -> IO Params+treatStandardArgs argv = treatStandardArgsWithParams argv emptyParams++treatStandardArgsWithParams :: [String] -> Params -> IO Params+treatStandardArgsWithParams = treatArgsWithParams optionsForStandardGenI++treatArgs :: [OptDescr Flag] -> [String] -> IO Params+treatArgs options argv = treatArgsWithParams options argv emptyParams++treatArgsWithParams :: [OptDescr Flag] -> [String] -> Params -> IO Params+treatArgsWithParams options argv initParams =+ case getOpt Permute options argv of+ (os,_,[] )+ | hasFlag HelpFlg os ->+ do putStrLn $ usage True+ exitWith ExitSuccess+ | hasFlag DisableGuiFlg os+ && notHasFlag TestCaseFlg os+ && notHasFlag RegressionTestModeFlg os+ && notHasFlag BatchDirFlg os+ && notHasFlag FromStdinFlg os ->+ do putStrLn $ "GenI must either be run in graphical mode, "+ ++ "in regression mode, with a test case specified, with --from-stdin,"+ ++ "or with a batch directory specified"+ exitFailure+ | otherwise ->+ return $ defineParams os initParams+ (_,_,errs) -> ioError (userError $ concat errs ++ usage False)+ where notHasFlag f l = not $ hasFlag f l++defineParams :: [Flag] -> Params -> Params+defineParams flgs prms =+ (\p -> foldr setDefault p $ geniFlags prms)+ . maybeSetMaxTrees+ . (mergeFlagsP OptimisationsFlg)+ . (mergeFlagsP MetricsFlg)+ $ prms+ { geniFlags = flgs+ , builderType = fromFlags builderType BuilderFlg flgs+ , grammarType = fromFlags grammarType GrammarTypeFlg flgs+ }+ where+ setDefault (Flag f v) p =+ if hasFlagP f p then p else setFlagP f v p+ mergeFlagsP f p =+ if hasFlagP f p+ then setFlagP f (concat $ getAllFlags f flgs) p+ else p+ fromFlags default_ t fs =+ fromMaybe (default_ prms) (getFlag t fs)+ maybeSetMaxTrees p =+ if hasFlagP IgnoreSemanticsFlg p && (not $ hasFlagP MaxTreesFlg p)+ then setFlagP MaxTreesFlg 5 p else p+\end{code}++\section{Options by theme}+\label{sec:fancy_parameters}++Note that you might see an option described in more than one place+because it falls into multiple categories.++% --------------------------------------------------------------------+\subsection{Basic options}+% --------------------------------------------------------------------++\begin{code}+optionsForBasicStuff :: [OptDescr Flag]+optionsForBasicStuff =+ [ helpOption, verboseOption, noguiOption+ , macrosOption , lexiconOption, testSuiteOption+ , outputOption+ ]+\end{code}++% --------------------------------------------------------------------+\subsection{Input files}+% --------------------------------------------------------------------++\begin{code}+optionsForInputFiles :: [OptDescr Flag]+optionsForInputFiles =+ [ macrosOption+ , lexiconOption+ , tracesOption+ , testSuiteOption+ , fromStdinOption+ , morphInfoOption+ , instructionsOption+ , outputOption+ , Option [] ["preselected"] (NoArg (Flag GrammarTypeFlg PreAnchored))+ "do NOT perform lexical selection - treat the grammar as the selection"+ ]++instructionsOption, macrosOption, lexiconOption, tracesOption, outputOption :: OptDescr Flag++instructionsOption =+ Option [] ["instructions"] (reqArg InstructionsFileFlg id "FILE")+ "instructions file FILE"++macrosOption =+ Option ['m'] ["macros"] (reqArg MacrosFlg id "FILE")+ "macros file FILE (unanchored trees)"++lexiconOption =+ Option ['l'] ["lexicon"] (reqArg LexiconFlg id "FILE")+ "lexicon file FILE"++tracesOption =+ Option [] ["traces"] (reqArg TracesFlg id "FILE")+ "traces file FILE (list of traces to display)"++outputOption =+ Option ['o'] ["output"] (reqArg OutputFileFlg id "FILE")+ "output file FILE (stdout if unset)"+\end{code}++% --------------------------------------------------------------------+\subsection{Output}+% --------------------------------------------------------------------++\begin{code}+optionsForOutput :: [OptDescr Flag]+optionsForOutput =+ [ outputOption+ , Option [] ["partial"] (noArg PartialFlg)+ "return partial result(s) if no complete solution is found"+ ]+\end{code}++% --------------------------------------------------------------------+\subsection{User interface}+% --------------------------------------------------------------------++\begin{code}+optionsForUserInterface :: [OptDescr Flag]+optionsForUserInterface =+ [ noguiOption, helpOption+ , Option [] ["regression"] (noArg RegressionTestModeFlg)+ "Run in regression testing mode (needs grammar, etc)"+ , Option [] ["unit-tests"] (noArg RunUnitTestFlg)+ "Run in unit testing mode (no arguments needed)"+ , Option [] ["viewcmd"] (reqArg ViewCmdFlg id "CMD")+ "XMG tree-view command"+ ]++verboseOption, noguiOption, helpOption :: OptDescr Flag+noguiOption = Option [] ["nogui"] (noArg DisableGuiFlg)+ "disable graphical user interface"+helpOption = Option [] ["help"] (noArg HelpFlg)+ "show full list of command line switches"+verboseOption = Option ['v'] ["verbose"] (noArg VerboseModeFlg)+ "verbose mode"+\end{code}++% --------------------------------------------------------------------+\subsection{Optimisations}+% --------------------------------------------------------------------++\begin{description}+\item[opt]+ The opt switch lets you specify a list of optimisations+ that GenI should use, for example, \texttt{--opt='pol S i'}.+ We associate each optimisation with a short code like 'i' for+ ``index accessibility filtering''. This code is what the+ user passes in, and is sometimes used by GenI to tell the+ user which optimisations it's using. See \texttt{geni+ --help} for more detail on the codes.++ Optimisations can be accumulated. For example, if you say something+ like \texttt{--opt='foo bar' --opt='quux'} it is the same as saying+ \texttt{--opt='foo bar quux'}.++ Note that we also have two special thematic codes ``pol'' and+ ``adj'' which tell GenI that it should enable all the+ polarity-related, and all the adjunction-related+ optimisations respectively.++\item[rootfeat]+ No results? Make sure your rootfeat are set correctly. GenI+ will reject all sentences whose root category does not unify+ with the rootfeat, the default of which is:+\begin{includecodeinmanual}+\begin{code}+defaultRootFeat :: Flist+defaultRootFeat =+ [ ("cat" , GConst ["s"])+ , ("inv" , GConst ["-"])+ , ("mode", GConst ["ind","subj"])+ , ("wh" , GConst ["-"])+ ]+\end{code}+\end{includecodeinmanual}++ You can set rootfeat to be empty (\verb![]!) if you want, in+ which case the realiser proper will return all results; but+ note that if you want to use polarity filtering, you must at+ least specify a value for the \verb!cat! feature.++\item[extrapols]+ Allows to to preset some polarities. There's not very much use for+ this, in my opinion. Most likely, what you really want is rootfeat.+\end{description}++\begin{code}+optionsForOptimisation :: [OptDescr Flag]+optionsForOptimisation =+ [ Option [] ["opts"]+ (reqArg OptimisationsFlg readOptimisations "LIST")+ "optimisations 'LIST' (--help for details)"+ , Option [] ["rootfeat"]+ (reqArg RootFeatureFlg readRF "FEATURE")+ ("root features 'FEATURE' (for polarities, default:"+ ++ showFlist defaultRF ++ ")")+ , Option [] ["extrapols"]+ (reqArg ExtraPolaritiesFlg readPolarities "STRING")+ "preset polarities (normally, you should use rootfeat instead)"+ ]+ where+ defaultRF = getListFlagP RootFeatureFlg emptyParams+ readRF = parseFlagWithParsec "root feature" geniFeats+ readPolarities = parseFlagWithParsec "polarity string" geniPolarities++data Optimisation =+ PolOpts | AdjOpts | Polarised | NoConstraints |+ RootCatFiltered | SemFiltered | Iaf {- one phase only! -}+ deriving (Show,Eq,Typeable)++coreOptimisationCodes :: [(Optimisation,String,String)]+coreOptimisationCodes =+ [ (Polarised , "p", "polarity filtering")+ , (SemFiltered , "f-sem", "semantic filtering (two-phase only)")+ , (RootCatFiltered , "f-root", "filtering on root node (two-phase only)")+ , (Iaf , "i", "index accesibility filtering (one-phase only)")+ , (NoConstraints , "nc", "disable semantic constraints (anti-optimisation!)")+ ]++optimisationCodes :: [(Optimisation,String,String)]+optimisationCodes =+ coreOptimisationCodes +++ [ (SemFiltered , "S", "semantic filtering (same as f-sem)")+ , (PolOpts , "pol", equivalentTo polOpts)+ , (AdjOpts , "adj", equivalentTo adjOpts)+ ]+ where equivalentTo os = "equivalent to '" ++ (unwords $ map showOptCode os) ++ "'"++polOpts, adjOpts :: [Optimisation]+polOpts = [Polarised]+adjOpts = [RootCatFiltered, SemFiltered]+\end{code}++\begin{code}+-- ---------------------------------------------------------------------+-- Optimisation usage info+-- ---------------------------------------------------------------------++lookupOpt:: Optimisation -> (String, String)+lookupOpt k =+ case find (\x -> k == fst3 x) optimisationCodes of+ Just (_, c, d) -> (c, d)+ Nothing -> geniBug $ "optimisation " ++ show k ++ " unknown"++showOptCode :: Optimisation -> String+showOptCode = fst.lookupOpt++describeOpt :: (Optimisation, String, String) -> String+describeOpt (_,k,d) = k ++ " - " ++ d++-- | Displays the usage text for optimisations.+-- It shows a table of optimisation codes and their meaning.+usageForOptimisations :: [String]+usageForOptimisations =+ [ "Optimisations must be passed in as a space-delimited list"+ , "(ex: --opt='p f-sem' for polarities and semantic filtering)"+ , ""+ , "Optimisations:"+ , " " ++ unlinesTab (map describeOpt coreOptimisationCodes)+ ]+ where unlinesTab l = concat (intersperse "\n " l)+\end{code}++\begin{code}+-- ---------------------------------------------------------------------+-- Parsing optimisation stuff+-- ---------------------------------------------------------------------++-- | If we do not recognise a code, we output an error message. We+-- also take the liberty of expanding thematic codes like 'pol'+-- into the respective list of optimisations.+readOptimisations :: String -> [Optimisation]+readOptimisations str =+ case parseOptimisations str of+ Left ick -> error $ "Unknown optimisations: " ++ (unwords ick)+ Right os -> (addif PolOpts polOpts) . (addif AdjOpts adjOpts) $ os+ where addif t x o = if (t `elem` o) then x ++ o else o++-- | Returns |Left| for any codes we don't recognise, or+-- |Right| if everything is ok.+parseOptimisations :: String -> Either [String] [Optimisation]+parseOptimisations str =+ let codes = words str+ mopts = map lookupOptimisation codes+ in if any isNothing mopts+ then Left [ c | (c,o) <- zip codes mopts, isNothing o ]+ else Right $ map fromJust mopts++lookupOptimisation :: String -> Maybe Optimisation+lookupOptimisation code =+ liftM fst3 $ find (\x -> snd3 x == code) optimisationCodes++parseFlagWithParsec :: String -> CharParser () b -> String -> b+parseFlagWithParsec description p str =+ case runParser p () "" str of+ Left err -> error $ "Couldn't parse " ++ description ++ " because " ++ show err+ Right res -> res+\end{code}++% --------------------------------------------------------------------+\subsection{Builders}+% --------------------------------------------------------------------++\begin{description}+\item[builder]+ A builder is basically a surface realisation algorithm. Some+ builders do not differ by very much. For example, the Earley and CKY builders+ are more or less the same from GenI's point of view, except with one little+ parameter to tweak.+\end{description}++\begin{code}+data BuilderType = NullBuilder |+ SimpleBuilder | SimpleOnePhaseBuilder |+ CkyBuilder | EarleyBuilder+ deriving (Eq, Typeable)++instance Show BuilderType where+ show NullBuilder = "null"+ show SimpleBuilder = "simple-2p"+ show SimpleOnePhaseBuilder = "simple-1p"+ show CkyBuilder = "CKY"+ show EarleyBuilder = "Earley"++optionsForBuilder :: [OptDescr Flag]+optionsForBuilder =+ [ Option ['b'] ["builder"] (reqArg BuilderFlg readBuilderType "BUILDER")+ ("use as realisation engine one of: " ++ (unwords $ map show mainBuilderTypes))+ ]++mainBuilderTypes :: [BuilderType]+mainBuilderTypes =+ [ SimpleBuilder, SimpleOnePhaseBuilder+ , CkyBuilder, EarleyBuilder]++-- | Hint: compose with (map toLower) to make it case-insensitive+mReadBuilderType :: String -> Maybe BuilderType+mReadBuilderType "null" = Just NullBuilder+mReadBuilderType "cky" = Just CkyBuilder+mReadBuilderType "earley" = Just EarleyBuilder+mReadBuilderType "simple" = Just SimpleBuilder+mReadBuilderType "simple-2p" = Just SimpleBuilder+mReadBuilderType "simple-1p" = Just SimpleOnePhaseBuilder+mReadBuilderType _ = Nothing++-- | Is case-insensitive, error if unknown type+readBuilderType :: String -> BuilderType+readBuilderType b =+ case mReadBuilderType $ map toLower b of+ Just x -> x+ Nothing -> error $ "Unknown builder type " ++ b++\end{code}++% --------------------------------------------------------------------+\subsection{Testing and profiling}+% --------------------------------------------------------------------++\begin{code}+fromStdinOption :: OptDescr Flag+fromStdinOption =+ Option [] ["from-stdin"] (noArg FromStdinFlg) "get testcase from stdin"++testSuiteOption :: OptDescr Flag+testSuiteOption =+ Option ['s'] ["testsuite"] (reqArg TestSuiteFlg id "FILE") "test suite FILE"++optionsForTesting :: [OptDescr Flag]+optionsForTesting =+ [ testSuiteOption+ , fromStdinOption+ , Option [] ["testcase"] (reqArg TestCaseFlg id "STRING")+ "run test case STRING"+ , Option [] ["timeout"] (reqArg TimeoutFlg read "SECONDS")+ "time out after SECONDS seconds"+ , Option [] ["metrics"] (optArg MetricsFlg ["default"] words "LIST")+ "keep track of performance metrics: (default: iterations comparisons chart_size)"+ , Option [] ["statsfile"] (reqArg StatsFileFlg id "FILE")+ "write performance data to file FILE (stdout if unset)"+ , Option [] ["batchdir"] (reqArg BatchDirFlg id "DIR")+ "batch process the test suite and save results to DIR"+ , Option [] ["earlydeath"] (noArg EarlyDeathFlg)+ "exit on first case with no results (batch processing) "+ ]+\end{code}++% --------------------------------------------------------------------+\subsection{Morphology}+% --------------------------------------------------------------------++GenI provides two options for morphology: either you use an external+inflection program (morphcmd), or you pass in a morphological lexicon+(morphlexicon) and in doing so, use GenI's built in inflecter. The+GenI internal morphology mechanism is a simple and stupid lookup-and-+unify table, so you probably don't want to use it if you have a huge+lexicon.++\begin{description}+\item[morphcmd] specifies the program used for morphology. Literate+GenI \cite{literateGeni} has a chapter describing how that program must work.+It will mostly likely be a script you wrote to wrap around some off-the-shelf+software.+\item[morphlexicon] specifies a morphological lexicon for use by+GenI's internal morphological generator. Specifying this option will+cause the morphcmd flag to be ignored.+\item[morphinfo] tells GenI which literals in the input semantics are+to be used by the morphological \emph{pre-}processor. The pre-processor+strips these features from the input and fiddles with the elementary+trees used by GenI so that the right features get attached to the leaf+nodes. An example of a ``morphological'' literal is something like+\texttt{past(p)}.+\end{description}++\begin{code}+optionsForMorphology :: [OptDescr Flag]+optionsForMorphology =+ [ morphInfoOption+ , Option [] ["morphcmd"] (reqArg MorphCmdFlg id "CMD")+ "morphological post-processor CMD (default: unset)"+ , Option [] ["morphlexicon"] (reqArg MorphLexiconFlg id "FILE")+ "morphological lexicon FILE (default: unset) - overrides morphcmd!"+ ]++morphInfoOption :: OptDescr Flag+morphInfoOption = Option [] ["morphinfo"] (reqArg MorphInfoFlg id "FILE")+ "morphological lexicon FILE (default: unset)"+\end{code}++% --------------------------------------------------------------------+\subsection{Ignore semantics mode}+% --------------------------------------------------------------------++\begin{description}+\item[ignoresem] is a special generation mode for systematically+churning out any sentences that the grammar can produce, without+using an input semantics. \textbf{Note}: This was implemented by Jackie+Lai (see patches around 2005-06-16), but has been horribly broken by+Eric sometime before 2006-08. Please let us know if you actually use+this thing, so that we can fix it.+\item[maxtrees] limits ignoresem mode by restricting the size of its+derivation trees (in number of elementary trees). Otherwise, GenI+would just spin around exploring an infinite number of sentences.+If you don't specify a maxtrees under ignoresem mode, we'll use a+default of 5. Note that maxtrees also works in normal generation+mode. It could be a useful way of saying ``give me only really+small sentences''.+\end{description}++\begin{code}+optionsForIgnoreSem :: [OptDescr Flag]+optionsForIgnoreSem =+ [ Option [] ["ignoresem"] (noArg IgnoreSemanticsFlg)+ "ignore all semantic information"+ , Option [] ["maxtrees"] (reqArg MaxTreesFlg read "INT")+ "max tree size INT by number of elementary trees"+ ]+\end{code}++% --------------------------------------------------------------------+\subsection{Other options}+% --------------------------------------------------------------------++\begin{code}+data GrammarType = GeniHand -- ^ geni's text format+ | PreCompiled -- ^ built into geni, no parsing needed+ | PreAnchored -- ^ lexical selection already done+ deriving (Show, Eq, Typeable)+\end{code}++% ====================================================================+\section{Scripting GenI}+% ====================================================================++Any input that you give to GenI will be interpreted as a list of test+suites (and test cases that you want to run). Each line has the format+\texttt{path/to/test-suite case1 case2 .. caseN}. You can omit the+test cases, which is interpreted as you wanting to run the entire test+suite. Also, the \verb!%! character and anything after is treated as+a comment.++\begin{code}+type Instruction = (FilePath, Maybe [String])++processInstructions :: Params -> IO Params+processInstructions config =+ do let is0 = case getFlagP TestSuiteFlg config of+ Just ts -> case getFlagP TestCaseFlg config of+ Just c -> [ (ts, Just [c]) ]+ Nothing -> [ (ts, Nothing) ]+ Nothing -> []+ is <- case getFlagP InstructionsFileFlg config of+ Nothing -> return []+ Just f -> instructionsFile `fmap` readFile f+ -- basically set the test suite/case flag to the first instruction+ -- note that with the above code (which sets the first instruction+ -- to the test suite/case flag), this should work out to identity+ -- when those flags are provided.+ let instructions = is0 ++ is+ updateInstructions =+ setFlagP TestInstructionsFlg instructions+ updateTestCase =+ case (listToMaybe instructions >>= snd >>= listToMaybe) of+ Just c -> setFlagP TestCaseFlg c+ Nothing -> id+ updateTestSuite =+ case (fst `fmap` listToMaybe instructions) of+ Just s -> setFlagP TestSuiteFlg s+ Nothing -> id+ updateFlags = updateInstructions . updateTestSuite . updateTestCase+ return $ updateFlags config++instructionsFile :: String -> [Instruction]+instructionsFile = mapMaybe inst . lines+ where+ inst l = case words (takeWhile (/= '%') l) of+ [] -> Nothing+ [f] -> Just (f, Nothing)+ (f:cs) -> Just (f, Just cs)+\end{code}++% ====================================================================+% Flags+% ====================================================================++\begin{code}+{-+Flags are GenI's internal representation of command line arguments. We+use phantom existential types (?) for representing GenI flags. This+makes it simpler to do things such as ``get the value of the MacrosFlg''+whilst preserving type safety (we always know that MacrosFlg is+associated with String). The alternative would be writing getters and+setters for each flag, and that gets really boring after a while.+-}++data Flag = forall f x . (Eq f, Show f, Show x, Typeable f, Typeable x) =>+ Flag (x -> f) x deriving (Typeable)++instance Show Flag where+ show (Flag f x) = "Flag " ++ show (f x)++instance Eq Flag where+ (Flag f1 x1) == (Flag f2 x2)+ | (typeOf f1 == typeOf f2) && (typeOf x1 == typeOf x2) =+ (fromJust . cast . f1 $ x1) == (f2 x2)+ | otherwise = False++isFlag :: (Typeable f, Typeable x) => (x -> f) -> Flag -> Bool+hasFlag :: (Typeable f, Typeable x) => (x -> f) -> [Flag] -> Bool+deleteFlag :: (Typeable f, Typeable x) => (x -> f) -> [Flag] -> [Flag]+setFlag :: (Eq f, Show f, Show x, Typeable f, Typeable x) => (x -> f) -> x -> [Flag] -> [Flag]+getFlag :: (Show f, Show x, Typeable f, Typeable x) => (x -> f) -> [Flag] -> Maybe x+getAllFlags :: (Show f, Show x, Typeable f, Typeable x) => (x -> f) -> [Flag] -> [x]++isFlag f1 (Flag f2 _) = typeOf f1 == typeOf f2+hasFlag f = any (isFlag f)+deleteFlag f = filter (not.(isFlag f))+setFlag f v fs = (Flag f v) : tl where tl = deleteFlag f fs+getFlag f fs = do (Flag _ v) <- find (isFlag f) fs ; cast v+getAllFlags f fs = catMaybes [ cast v | flg@(Flag _ v) <- fs, isFlag f flg ]+++{-+Below are just the individual flags, which unfortunately have to be+defined as separate data types because of our fancy existential+data type code.+-}+-- input files+#define FLAG(x,y) data x = x y deriving (Eq, Show, Typeable)++FLAG (BatchDirFlg, FilePath)+FLAG (DisableGuiFlg, ())+FLAG (EarlyDeathFlg, ())+FLAG (ExtraPolaritiesFlg, (Map.Map String Interval))+FLAG (FromStdinFlg, ())+FLAG (HelpFlg, ())+FLAG (IgnoreSemanticsFlg, ())+FLAG (InstructionsFileFlg, FilePath)+FLAG (LexiconFlg, FilePath)+FLAG (MacrosFlg, FilePath)+FLAG (TracesFlg, FilePath)+FLAG (MaxTreesFlg, Int)+FLAG (MetricsFlg, [String])+FLAG (MorphCmdFlg, String)+FLAG (MorphInfoFlg, FilePath)+FLAG (MorphLexiconFlg, FilePath)+FLAG (OptimisationsFlg, [Optimisation])+FLAG (OutputFileFlg, String)+FLAG (PartialFlg, ())+FLAG (RegressionTestModeFlg, ())+FLAG (RootFeatureFlg, Flist)+FLAG (RunUnitTestFlg, ())+FLAG (NoLoadTestSuiteFlg, ())+FLAG (StatsFileFlg, FilePath)+FLAG (TestCaseFlg, String)+FLAG (TestInstructionsFlg, [Instruction])+FLAG (TestSuiteFlg, FilePath)+FLAG (TimeoutFlg, Integer)+FLAG (VerboseModeFlg, ())+FLAG (ViewCmdFlg, String)+-- not to be exported (defaults)+-- the WeirdFlg exists strictly to please OS X when you launch+-- GenI in an application bundle (double-click)... for some+-- reason it wants to pass an argument to -p+FLAG (BuilderFlg, BuilderType)+FLAG (GrammarTypeFlg, GrammarType)+FLAG (WeirdFlg, String)+\end{code}++
+ src/NLP/GenI/Console.hs view
@@ -0,0 +1,196 @@+-- GenI surface realiser+-- Copyright (C) 2005 Carlos Areces and Eric Kow+--+-- This program is free software; you can redistribute it and/or+-- modify it under the terms of the GNU General Public License+-- as published by the Free Software Foundation; either version 2+-- of the License, or (at your option) any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; if not, write to the Free Software+-- Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.++-- | The console user interface including batch processing on entire+-- test suites.++module NLP.GenI.Console(consoleGeni, runTestCaseOnly) where++import Control.Monad+import Data.IORef(readIORef, modifyIORef)+import Data.List(find, sort)+import Data.Maybe ( isJust, fromMaybe )+import System.Directory(createDirectoryIfMissing)+import System.Exit ( exitFailure )+import System.FilePath ( (</>) )+import Test.HUnit.Text (runTestTT)+import qualified Test.HUnit.Base as H+import Test.HUnit.Base ((@?))++import NLP.GenI.Btypes+ ( SemInput, showSem+ , TestCase(tcSem, tcName, tcExpected)+ )+import qualified NLP.GenI.Btypes as G+import NLP.GenI.General+ ( ePutStrLn, withTimeout, exitTimeout+ , fst3,+ )+import NLP.GenI.Geni+import NLP.GenI.Configuration+ ( Params+ , BatchDirFlg(..), EarlyDeathFlg(..), FromStdinFlg(..), OutputFileFlg(..)+ , MetricsFlg(..), RegressionTestModeFlg(..), RunUnitTestFlg(..), StatsFileFlg(..)+ , TestCaseFlg(..), TimeoutFlg(..), VerboseModeFlg(..)+ , hasFlagP, getFlagP+ , builderType , BuilderType(..)+ )+import qualified NLP.GenI.Builder as B+import NLP.GenI.CkyEarley.CkyBuilder+import NLP.GenI.Simple.SimpleBuilder+import NLP.GenI.Statistics ( showFinalStats, Statistics )+import NLP.GenI.Test (runTests)++consoleGeni :: ProgStateRef -> IO()+consoleGeni pstRef = do+ pst <- readIORef pstRef+ if hasFlagP RunUnitTestFlg (pa pst)+ then runTests+ else do+ loadEverything pstRef+ case getFlagP TimeoutFlg (pa pst) of+ Nothing -> runSuite pstRef+ Just t -> withTimeout t (timeoutErr t) $ runSuite pstRef+ where+ timeoutErr t = do ePutStrLn $ "GenI timed out after " ++ (show t) ++ "s"+ exitTimeout++-- | Runs a test suite.+-- We assume that the grammar and target semantics are already+-- loaded into the monadic state.+-- If batch processing is enabled, save the results to the batch output+-- directory with one subdirectory per case.+runSuite :: ProgStateRef -> IO ()+runSuite pstRef =+ do pst <- readIORef pstRef+ let suite = tsuite pst+ config = pa pst+ verbose = hasFlagP VerboseModeFlg config+ earlyDeath = hasFlagP EarlyDeathFlg config+ if hasFlagP RegressionTestModeFlg config+ then runRegressionSuite pstRef >> return ()+ else case getFlagP BatchDirFlg config of+ Nothing -> runTestCaseOnly pstRef >> return ()+ Just bdir -> runBatch earlyDeath verbose bdir suite+ where+ runBatch earlyDeath verbose bdir suite =+ if any null $ map tcName suite+ then ePutStrLn "Can't do batch processing. The test suite has cases with no name."+ else do ePutStrLn "Batch processing mode"+ mapM_ (runCase earlyDeath verbose bdir) suite+ runCase earlyDeath verbose bdir (G.TestCase { tcName = n, tcSem = s }) =+ do when verbose $+ ePutStrLn "======================================================"+ (res , _) <- runOnSemInput pstRef (PartOfSuite n bdir) s+ ePutStrLn $ " " ++ n ++ " - " ++ (show $ length res) ++ " results"+ when (null res && earlyDeath) $ do+ ePutStrLn $ "Exiting early because test case " ++ n ++ " failed."+ exitFailure++-- | Run a test suite, but in HUnit regression testing mode,+-- treating each GenI test case as an HUnit test. Obviously+-- we need a test suite, grammar, etc as input+runRegressionSuite :: ProgStateRef -> IO (H.Counts)+runRegressionSuite pstRef =+ do pst <- readIORef pstRef+ tests <- (mapM toTest) . tsuite $ pst+ runTestTT . (H.TestList) . concat $ tests+ where+ toTest :: G.TestCase -> IO [H.Test] -- ^ GenI test case to HUnit Tests+ toTest tc = -- run the case, and return a test case for each expected result+ do (res , _) <- runOnSemInput pstRef InRegressionTest (tcSem tc)+ let sentences = fst (unzip res)+ name = tcName tc+ semStr = showSem . fst3 . tcSem $ tc+ mainMsg = "for " ++ semStr ++ ", got no results"+ mainCase = H.TestLabel name+ $ H.TestCase $ (not.null $ sentences) @? mainMsg+ subMsg e = "for " ++ semStr ++ ", failed to get (" ++ e ++ ")"+ subCase e = H.TestLabel name+ $ H.TestCase $ (e `elem` sentences) @? subMsg e+ return $ (mainCase :) $ map subCase (tcExpected tc)++-- | Run the specified test case, or failing that, the first test+-- case in the suite+runTestCaseOnly :: ProgStateRef -> IO ([GeniResult], Statistics)+runTestCaseOnly pstRef =+ do pst <- readIORef pstRef+ let config = pa pst+ pstOutfile = fromMaybe "" $ getFlagP OutputFileFlg config+ sFile = fromMaybe "" $ getFlagP StatsFileFlg config+ semInput <- case getFlagP TestCaseFlg config of+ Nothing -> if hasFlagP FromStdinFlg config+ then do getContents >>= loadTargetSemStr pstRef+ ts `fmap` readIORef pstRef+ else getFirstCase pst+ Just c -> findCase pst c+ runOnSemInput pstRef (Standalone pstOutfile sFile) semInput+ where+ getFirstCase pst =+ case tsuite pst of+ [] -> fail "Test suite is empty."+ (c:_) -> return $ tcSem c+ findCase pst theCase =+ case find (\x -> tcName x == theCase) (tsuite pst) of+ Nothing -> fail ("No such test case: " ++ theCase)+ Just s -> return $ tcSem s++data RunAs = Standalone FilePath FilePath+ | PartOfSuite String FilePath+ | InRegressionTest++-- | Runs a case in the test suite. If the user does not specify any test+-- cases, we run the first one. If the user specifies a non-existing+-- test case we raise an error.+runOnSemInput :: ProgStateRef+ -> RunAs+ -> SemInput+ -> IO ([GeniResult], Statistics)+runOnSemInput pstRef args semInput =+ do modifyIORef pstRef (\x -> x{ts = semInput})+ pst <- readIORef pstRef+ let config = pa pst+ (results', stats) <- case builderType config of+ NullBuilder -> helper B.nullBuilder+ SimpleBuilder -> helper simpleBuilder_2p+ SimpleOnePhaseBuilder -> helper simpleBuilder_1p+ CkyBuilder -> helper ckyBuilder+ EarleyBuilder -> helper earleyBuilder+ let results = sort results'+ -- create directory if need be+ case args of+ PartOfSuite n f -> createDirectoryIfMissing False (f </> n)+ _ -> return ()+ let oWrite = case args of+ Standalone "" _ -> putStrLn+ Standalone f _ -> writeFile f+ PartOfSuite n f -> writeFile $ f </> n </> "responses"+ InRegressionTest -> const $ return ()+ soWrite = case args of+ Standalone _ "" -> putStrLn+ Standalone _ f -> writeFile f+ PartOfSuite n f -> writeFile $ f </> n </> "stats"+ InRegressionTest -> const $ return ()+ oWrite . unlines . map fst $ results+ -- print out statistical data (if available)+ when (isJust $ getFlagP MetricsFlg config) $+ do soWrite $ "begin stats\n" ++ showFinalStats stats ++ "end"+ return (results, stats)+ where+ helper builder =+ do (results, stats, _) <- runGeni pstRef builder+ return (results, stats)
+ src/NLP/GenI/General.hs view
@@ -0,0 +1,435 @@+-- GenI surface realiser+-- Copyright (C) 2005 Carlos Areces and Eric Kow+--+-- This program is free software; you can redistribute it and/or+-- modify it under the terms of the GNU General Public License+-- as published by the Free Software Foundation; either version 2+-- of the License, or (at your option) any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; if not, write to the Free Software+-- Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.++-- | This module provides some very generic, non-GenI specific functions on strings,+-- trees and other miscellaneous odds and ends. Whenever possible, one should try+-- to replace these functions with versions that are available in the standard+-- libraries, or the Haskell platform ones, or on hackage.++module NLP.GenI.General (+ -- * IO+ ePutStr, ePutStrLn, eFlush,+ -- ** Strict readFile+ readFile',+ lazySlurp,+ -- ** Timeouts+ withTimeout,+ exitTimeout,+ -- * Strings+ dropTillIncluding,+ trim,+ toUpperHead, toLowerHead,+ toAlphaNum,+ -- * Triples+ fst3, snd3, thd3,+ -- * Lists+ equating, comparing,+ map',+ wordsBy,+ boundsCheck,+ isEmptyIntersect,+ groupByFM,+ multiGroupByFM,+ insertToListMap,+ groupAndCount,+ combinations,+ mapMaybeM,+ repList,+ -- * Trees+ mapTree', filterTree,+ treeLeaves, preTerminals,+ repNode, repAllNode, listRepNode, repNodeByNode,+ -- * Intervals+ Interval,+ (!+!), ival, showInterval,+ -- * Bit vectors+ BitVector,+ showBitVector,+ -- * Bugs+ geniBug,+ )+ where++import Control.Monad (liftM)+import Data.Bits (shiftR, (.&.))+import Data.Char (isDigit, isSpace, toUpper, toLower)+import Data.List (intersect, groupBy, group, sort)+import Data.Tree+import System.IO (hPutStrLn, hPutStr, hFlush, stderr)+import qualified Data.Map as Map++-- for timeout+import Control.Concurrent+import Control.Exception+import Data.Dynamic(Typeable, typeOf, TyCon, mkTyCon, mkTyConApp, toDyn)+import Data.Unique+import System.Exit(exitWith, ExitCode(ExitFailure))++-- for non-lazy IO+import System.IO (openFile, IOMode(ReadMode), hFileSize, hGetBuf)+import System.IO.Unsafe (unsafeInterleaveIO)+import Foreign (mallocForeignPtrBytes, withForeignPtr, ForeignPtr, Ptr, peekElemOff, plusPtr, Word8)+import Data.Char (chr)++-- ----------------------------------------------------------------------+-- IO+-- ----------------------------------------------------------------------++-- | putStr on stderr+ePutStr :: String -> IO ()+ePutStr = hPutStr stderr++ePutStrLn :: String -> IO()+ePutStrLn = hPutStrLn stderr++eFlush :: IO()+eFlush = hFlush stderr++-- ----------------------------------------------------------------------+-- Strings+-- ----------------------------------------------------------------------++trim :: String -> String+trim = reverse . (dropWhile isSpace) . reverse . (dropWhile isSpace) ++-- | Drop all characters up to and including the one in question+dropTillIncluding :: Char -> String -> String+dropTillIncluding c = drop 1 . (dropWhile (/= c))++-- | Make the first character of a string upper case+toUpperHead :: String -> String+toUpperHead [] = []+toUpperHead (h:t) = (toUpper h):t++-- | Make the first character of a string lower case+toLowerHead :: String -> String+toLowerHead [] = []+toLowerHead(h:t) = (toLower h):t++-- ----------------------------------------------------------------------+-- Alphanumeric sort+-- ----------------------------------------------------------------------++-- | Intermediary type used for alphanumeric sort+data AlphaNum = A String | N Int deriving Eq++-- we don't derive this, because we want num < alpha+instance Ord AlphaNum where+ compare (A s1) (A s2) = compare s1 s2+ compare (N s1) (N s2) = compare s1 s2+ compare (A _) (N _) = GT+ compare (N _) (A _) = LT++-- | An alphanumeric sort is one where you treat the numbers in the string+-- as actual numbers. An alphanumeric sort would put x2 before x100,+-- because 2 < 10, wheraeas a naive sort would put it the other way+-- around because the characters 1 < 2. To sort alphanumerically, just+-- 'sortBy (comparing toAlphaNum)'+toAlphaNum :: String -> [AlphaNum]+toAlphaNum = map readOne . groupBy (equating isDigit)+ where+ readOne s+ | all isDigit s = N (read s)+ | otherwise = A s++-- ----------------------------------------------------------------------+-- Triples+-- ----------------------------------------------------------------------++fst3 :: (a,b,c) -> a+fst3 (x,_,_) = x++snd3 :: (a,b,c) -> b+snd3 (_,x,_) = x++thd3 :: (a,b,c) -> c+thd3 (_,_,x) = x++-- ----------------------------------------------------------------------+-- Lists+-- ----------------------------------------------------------------------++equating :: Eq b => (a -> b) -> (a -> a -> Bool)+equating f a b = f a == f b++comparing :: Ord b => (a -> b) -> (a -> a -> Ordering)+comparing f a b = compare (f a) (f b)++-- | A strict version of 'map'+map' :: (a->b) -> [a] -> [b]+map' _ [] = []+map' f (x:xs) = let a = f x in a `seq` (a:(map' f xs))++-- | A generic version of the Data.List.words+-- TODO: replace by version from split+wordsBy :: (Eq a) => a -> [a] -> [[a]]+wordsBy c xs = filter (/= [c]) $ groupBy (\x y -> x /= c && y /= c) xs++-- | Makes sure that index s is in the bounds of list l. +-- Surely there must be some more intelligent way to deal with this.+boundsCheck :: Int -> [a] -> Bool+boundsCheck s l = s >= 0 && s < length l++-- | True if the intersection of two lists is empty.+isEmptyIntersect :: (Eq a) => [a] -> [a] -> Bool+isEmptyIntersect a b = null $ intersect a b++-- ----------------------------------------------------------------------+-- Grouping+-- ----------------------------------------------------------------------++-- | Serves the same function as 'Data.List.groupBy'. It groups together+-- items by some property they have in common. The difference is that the+-- property is used as a key to a Map that you can lookup.+groupByFM :: (Ord b) => (a -> b) -> [a] -> (Map.Map b [a])+groupByFM fn list = + let addfn x acc key = insertToListMap key x acc+ helper acc x = addfn x acc (fn x)+ in foldl helper Map.empty list++-- | Same as 'groupByFM', except that we let an item appear in+-- multiple groups. The fn extracts the property from the item,+-- and returns multiple results in the form of a list+multiGroupByFM :: (Ord b) => (a -> [b]) -> [a] -> (Map.Map b [a])+multiGroupByFM fn list = + let addfn x acc key = insertToListMap key x acc+ helper acc x = foldl (addfn x) acc (fn x)+ in foldl helper Map.empty list++{-# INLINE insertToListMap #-}+insertToListMap :: (Ord b) => b -> a -> Map.Map b [a] -> Map.Map b [a]+insertToListMap k i m =+ case Map.lookup k m of+ Nothing -> Map.insert k [i] m+ Just p -> Map.insert k (i:p) m++-- | Convert a list of items into a list of tuples (a,b) where+-- a is an item in the list and b is the number of times a+-- in occurs in the list.+groupAndCount :: (Eq a, Ord a) => [a] -> [(a, Int)]+groupAndCount xs = + map (\x -> (head x, length x)) grouped+ where grouped = (group.sort) xs++-- Given a list of lists, return all lists such that one item from each sublist is chosen.+-- If returns the empty list if there are any empty sublists.+combinations :: [[a]] -> [[a]]+combinations = sequence++mapMaybeM :: (Monad m) => (a -> m (Maybe b)) -> [a] -> m [b]+mapMaybeM _ [] = return []+mapMaybeM f (x:xs) =+ f x >>=+ (\my -> case my of+ Nothing -> mapMaybeM f xs+ Just y -> liftM (y:) (mapMaybeM f xs))++-- | Return the list, modifying only the first matching item.+repList :: (a->Bool) -> (a->a) -> [a] -> [a]+repList _ _ [] = []+repList pr fn (x:xs)+ | pr x = fn x : xs+ | otherwise = x : (repList pr fn xs)++-- ----------------------------------------------------------------------+-- Trees+-- ----------------------------------------------------------------------++-- | Strict version of 'mapTree' (for non-strict, just use fmap)+mapTree' :: (a->b) -> Tree a -> Tree b+mapTree' fn (Node a []) = let b = fn a in b `seq` Node b []+mapTree' fn (Node a l) = let b = fn a+ bs = map' (mapTree' fn) l+ in b `seq` bs `seq` Node b bs++-- | Like 'filter', except on Trees. Filter might not be a good name, though,+-- because we return a list of nodes, not a tree.+filterTree :: (a->Bool) -> Tree a -> [a]+filterTree fn = (filter fn) . flatten++-- | The leaf nodes of a Tree+treeLeaves :: Tree a -> [a]+treeLeaves (Node n []) = [n]+treeLeaves (Node _ l ) = concatMap treeLeaves l++-- | Return pairs of (parent, terminal)+preTerminals :: Tree a -> [(a,a)]+preTerminals (Node _ []) = []+preTerminals (Node x ks) =+ [ (x,y) | (Node y ys) <- ks, null ys ] ++ concatMap preTerminals ks++-- | 'repNode' @fn filt t@ returns a version of @t@ in which the first+-- node which @filt@ matches is transformed using @fn@.+repNode :: (Tree a -> Tree a) -- ^ replacement function+ -> (Tree a -> Bool) -- ^ filtering function+ -> Tree a -> Maybe (Tree a)+repNode fn filt t =+ case listRepNode fn filt [t] of+ (_, False) -> Nothing+ ([t2], True) -> Just t2+ _ -> geniBug "Either repNode or listRepNode are broken"++-- | Like 'repNode' except that it performs the operations on+-- all nodes that match and doesn't care if any nodes match+-- or not+repAllNode :: (Tree a -> Tree a) -> (Tree a -> Bool)+ -> Tree a -> Tree a+repAllNode fn filt n | filt n = fn n+repAllNode fn filt (Node p ks) = Node p $ map (repAllNode fn filt) ks++-- | Like 'repNode' but on a list of tree nodes+listRepNode :: (Tree a -> Tree a) -- ^ replacement function+ -> (Tree a -> Bool) -- ^ filtering function+ -> [Tree a] -- ^ nodes+ -> ([Tree a], Bool)+listRepNode _ _ [] = ([], False)+listRepNode fn filt (n:l2) | filt n = (fn n : l2, True)+listRepNode fn filt ((n@(Node a l1)):l2) =+ case listRepNode fn filt l1 of+ (lt1, True) -> ((Node a lt1):l2, True)+ _ -> case listRepNode fn filt l2 of+ (lt2, flag2) -> (n:lt2, flag2)++-- | Replace a node in the tree in-place with another node; keep the+-- children the same. If the node is not found in the tree, or if+-- there are multiple instances of the node, this is treated as an+-- error.+repNodeByNode :: (a -> Bool) -- ^ which node?+ -> a -> Tree a -> Tree a+repNodeByNode nfilt rep t =+ let tfilt (Node n _) = nfilt n+ replaceFn (Node _ k) = Node rep k+ in case listRepNode replaceFn tfilt [t] of+ ([t2], True) -> t2+ (_ , False) -> geniBug "Node not found in repNode"+ _ -> geniBug "Unexpected result in repNode"++-- ----------------------------------------------------------------------+-- Errors+-- ----------------------------------------------------------------------++-- | errors specifically in GenI, which is very likely NOT the user's fault.+geniBug :: String -> a+geniBug s = error $ "Bug in GenI!\n" ++ s +++ "\nPlease file a report on http://wiki.loria.fr/wiki/GenI/Complaints" ++-- ----------------------------------------------------------------------+-- Intervals+-- ----------------------------------------------------------------------++type Interval = (Int,Int)++-- | Add two intervals+(!+!) :: Interval -> Interval -> Interval+(!+!) (a1,a2) (b1,b2) = (a1+b1, a2+b2)++-- | 'ival' @x@ builds a trivial interval from 'x' to 'x'+ival :: Int -> Interval+ival i = (i,i)++showInterval :: Interval -> String+showInterval (x,y) =+ let sign i = if i > 0 then "+" else ""+ --+ in if (x==y) + then (sign x) ++ (show x) + else show (x,y)++-- ----------------------------------------------------------------------+-- Bit vectors+-- ----------------------------------------------------------------------++type BitVector = Integer++-- | displays a bit vector, using a minimum number of bits+showBitVector :: Int -> BitVector -> String+showBitVector min_ 0 = replicate min_ '0'+showBitVector min_ x = showBitVector (min_ - 1) (shiftR x 1) ++ (show $ x .&. 1)++-- ----------------------------------------------------------------------+-- Strict readfile+-- Simon Marlow wrote this code on the Haskell mailing list 2005-08-02.+-- ----------------------------------------------------------------------++-- | Using readFile' can be a good idea if you're dealing with not-so-huge+-- files (i.e. where you don't want lazy evaluation), because it ensures+-- that the handles are closed. No more ``too many open files''+readFile' :: FilePath -> IO String+readFile' f = do+ h <- openFile f ReadMode+ s <- hFileSize h+ fp <- mallocForeignPtrBytes (fromIntegral s)+ len <- withForeignPtr fp $ \buf -> hGetBuf h buf (fromIntegral s)+ lazySlurp fp 0 len++buf_size :: Int+buf_size = 4096 :: Int++lazySlurp :: ForeignPtr Word8 -> Int -> Int -> IO String+lazySlurp fp ix len+ | fp `seq` False = undefined+ | ix >= len = return []+ | otherwise = do+ cs <- unsafeInterleaveIO (lazySlurp fp (ix + buf_size) len)+ ws <- withForeignPtr fp $ \p -> loop (min (len-ix) buf_size - 1)+ ((p :: Ptr Word8) `plusPtr` ix) cs+ return ws+ where+ loop :: Int -> Ptr Word8 -> String -> IO String+ loop sublen p acc+ | sublen `seq` p `seq` False = undefined+ | sublen < 0 = return acc+ | otherwise = do+ w <- peekElemOff p sublen+ loop (sublen-1) p (chr (fromIntegral w):acc)++-- ----------------------------------------------------------------------+-- Timeouts+-- ----------------------------------------------------------------------++data TimeOut = TimeOut Unique++timeOutTc :: TyCon+timeOutTc = mkTyCon "TimeOut"++instance Typeable TimeOut where+ typeOf _ = mkTyConApp timeOutTc []++withTimeout :: Integer+ -> IO a -- ^ action to run upon timing out+ -> IO a -- ^ main action to run+ -> IO a+withTimeout secs on_timeout action =+ do parent <- myThreadId+ i <- newUnique+ block $ do+ timeout <- forkIO (timeout_thread secs parent i)+ Control.Exception.catchDyn+ ( unblock $ do result <- action+ killThread timeout+ return result )+ ( \ex -> case ex of+ TimeOut u | u == i -> unblock on_timeout+ _ -> killThread timeout >>= throwDyn ex )+ where+ timeout_thread secs_ parent i =+ do threadDelay $ (fromInteger secs_) * 1000000+ throwTo parent (DynException $ toDyn $ TimeOut i)++-- | Like 'exitFailure', except that we return with a code that we reserve for timing out+exitTimeout :: IO ()+exitTimeout = exitWith $ ExitFailure 2
+ src/NLP/GenI/Geni.lhs view
@@ -0,0 +1,1029 @@+% GenI surface realiser+% Copyright (C) 2005 Carlos Areces and Eric Kow+%+% This program is free software; you can redistribute it and/or+% modify it under the terms of the GNU General Public License+% as published by the Free Software Foundation; either version 2+% of the License, or (at your option) any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; if not, write to the Free Software+% Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.++\chapter{Geni}+\label{cha:Geni}++Geni is the interface between the front and backends of the generator. The GUI+and the console interface both talk to this module, and in turn, this module+talks to the input file parsers and the surface realisation engine. This+module also does lexical selection and anchoring because these processes might+involve some messy IO performance tricks.++\begin{code}+module NLP.GenI.Geni (ProgState(..), ProgStateRef, emptyProgState,+ showRealisations, groupAndCount,+ initGeni, runGeni, runGeniWithSelector, getTraces, GeniResult, Selector,+ loadEverything, loadLexicon, loadGeniMacros,+ loadTestSuite, loadTargetSemStr,+ combine,++ -- used by auxiliary tools only+ chooseLexCand,+ )+where+\end{code}++\ignore{+\begin{code}+import Control.Arrow (first)+import Control.Monad.Error+import Control.Monad (unless)++import Data.Binary (Binary, decodeFile)+import Data.IORef (IORef, readIORef, modifyIORef)+import Data.List+import qualified Data.Map as Map+import Data.Maybe (mapMaybe, fromMaybe, isJust)+import Data.Tree (Tree(Node))+import Data.Typeable (Typeable)++import System.IO.Unsafe (unsafePerformIO)+import Text.ParserCombinators.Parsec +-- import System.Process +++import NLP.GenI.General(filterTree, repAllNode,+ equating, groupAndCount, multiGroupByFM,+ geniBug,+ repNodeByNode,+ wordsBy,+ fst3,+ ePutStr, ePutStrLn, eFlush,+ )++import NLP.GenI.Btypes+ (Macros, MTtree, ILexEntry, Lexicon,+ Replacable(..),+ Sem, SemInput, TestCase(..), sortSem, subsumeSem, params,+ GeniVal(GConst), fromGVar,+ GNode(ganchor, gnname, gup, gdown, gaconstr, gtype, gorigin), Flist,+ GType(Subs, Other),+ isemantics, ifamname, iword, iparams, iequations,+ iinterface, ifilters,+ isempols,+ toKeys,+ showLexeme, showSem,+ pidname, pfamily, pinterface, ptype, psemantics, ptrace,+ setAnchor, setLexeme, tree, unifyFeat,+ alphaConvert,+ )+import NLP.GenI.BtypesBinary ()++import NLP.GenI.Tags (Tags, TagElem, emptyTE,+ idname, ttreename,+ ttype, tsemantics, ttree, tsempols,+ tinterface, ttrace,+ setTidnums) ++import NLP.GenI.Configuration+ ( Params, getFlagP, hasFlagP, hasOpt, Optimisation(NoConstraints)+ , MacrosFlg(..), LexiconFlg(..), TestSuiteFlg(..), TestCaseFlg(..)+ , MorphInfoFlg(..), MorphCmdFlg(..), MorphLexiconFlg(..)+ , PartialFlg(..)+ , IgnoreSemanticsFlg(..), FromStdinFlg(..), VerboseModeFlg(..)+ , NoLoadTestSuiteFlg(..)+ , TracesFlg(..)+ , grammarType+ , GrammarType(..) )++import qualified NLP.GenI.Builder as B++import NLP.GenI.GeniParsers (geniMacros, geniTagElems,+ geniLexicon, geniTestSuite,+ geniTestSuiteString, geniSemanticInput,+ geniMorphInfo, geniMorphLexicon,+ )+import NLP.GenI.Morphology+import NLP.GenI.Statistics (Statistics)++-- import CkyBuilder +-- import SimpleBuilder (simpleBuilder)+\end{code}+}++\begin{code}+myEMPTY :: String+myEMPTY = "MYEMPTY" +\end{code}++% --------------------------------------------------------------------+\section{ProgState}+% --------------------------------------------------------------------++\begin{code}+data ProgState = ST{ -- | the current configuration being processed+ pa :: Params,+ --+ gr :: Macros,+ le :: Lexicon,+ morphinf :: MorphFn,+ morphlex :: Maybe [(String,String,Flist)],+ ts :: SemInput, + -- | names of test case to run+ tcase :: String, + -- | name, original string (for gui), sem+ tsuite :: [TestCase],+ -- | simplified traces (optional)+ traces :: [String]+ }++type ProgStateRef = IORef ProgState++-- | The program state when you start GenI for the very first time+emptyProgState :: Params -> ProgState+emptyProgState args =+ ST { pa = args+ , gr = []+ , le = Map.empty+ , morphinf = const Nothing+ , morphlex = Nothing+ , ts = ([],[],[])+ , tcase = []+ , tsuite = []+ , traces = []+ }+\end{code}++% --------------------------------------------------------------------+\section{Interface}+\subsection{Loading and parsing}+% --------------------------------------------------------------------++We have one master function that loads all the files GenI is expected to+use. This just calls the sub-loaders below, some of which are exported+for use by the graphical interface. The master function also makes sure+to complain intelligently if some of the required files are missing.++\begin{code}+loadEverything :: ProgStateRef -> IO() +loadEverything pstRef =+ do pst <- readIORef pstRef+ --+ let config = pa pst+ isMissing f = not $ hasFlagP f config+ -- grammar type+ isNotPreanchored = grammarType config /= PreAnchored+ isNotPrecompiled = grammarType config /= PreCompiled+ useTestSuite = isMissing FromStdinFlg+ && isMissing NoLoadTestSuiteFlg+ -- display + let errormsg =+ concat $ intersperse ", " [ msg | (con, msg) <- errorlst, con ]+ errorlst =+ [ (isNotPrecompiled && isMissing MacrosFlg,+ "a tree file")+ , (isNotPreanchored && isMissing LexiconFlg,+ "a lexicon file")+ , (useTestSuite && isMissing TestSuiteFlg,+ "a test suite") ]+ unless (null errormsg) $ fail ("Please specify: " ++ errormsg)+ -- we only have to read in grammars from the simple format+ case grammarType config of + PreAnchored -> return ()+ PreCompiled -> return ()+ _ -> loadGeniMacros pstRef+ -- we don't have to read in the lexicon if it's already pre-anchored+ when isNotPreanchored $ loadLexicon pstRef+ -- in any case, we have to...+ loadMorphInfo pstRef+ when useTestSuite $ loadTestSuite pstRef+ -- the morphological lexicon+ loadMorphLexicon pstRef+ -- the trace filter file+ loadTraces pstRef+\end{code}++The file loading functions all work the same way: we load the file,+and try to parse it. If this doesn't work, we just fail in IO, and+GenI dies. If we succeed, we update the program state passed in as+an IORef.++\begin{code}+loadLexicon, loadGeniMacros, loadMorphInfo, loadMorphLexicon, loadTraces :: ProgStateRef -> IO ()++loadLexicon pstRef =+ do config <- pa `fmap` readIORef pstRef+ let getSem l = if hasFlagP IgnoreSemanticsFlg config+ then [] else isemantics l+ sorter l = l { isemantics = (sortSem . getSem) l }+ cleanup = mapBySemKeys isemantics . map sorter+ loadThingOrDie LexiconFlg "lexicon" pstRef+ (parseFromFileOrFail geniLexicon)+ (\l p -> p { le = cleanup l })++-- | The macros are stored as a hashing function in the monad.+loadGeniMacros pstRef =+ loadThingOrDie MacrosFlg "trees" pstRef parser updater+ where parser = parseFromFileMaybeBinary geniMacros+ updater g p = p { gr = g }++++-- | The results are stored as a lookup function in the monad.+loadMorphInfo pstRef =+ loadThingOrIgnore MorphInfoFlg "morphological info" pstRef parser updater+ where parser = parseFromFileOrFail geniMorphInfo+ updater m p = p { morphinf = readMorph m }++loadMorphLexicon pstRef =+ loadThingOrIgnore MorphLexiconFlg "morphological lexicon" pstRef parser updater+ where parser = parseFromFileOrFail geniMorphLexicon+ updater m p = p { morphlex = Just m }++loadTraces pstRef =+ loadThingOrIgnore TracesFlg "traces" pstRef+ (\f -> lines `fmap` readFile f)+ (\t p -> p {traces = t})+\end{code}++\subsubsection{Target semantics}++Reading in the target semantics (or test suite) is a little more+complicated. It follows the same general schema as above, except+that we parse the file twice: once for our internal representation,+and once to get a string representation of each test case. The+string representation is for the graphical interface; it avoids us+figuring out how to pretty-print things because we can assume the+user will format it the way s/he wants.++\begin{code}+-- | Stores the results in the tcase and tsuite fields+loadTestSuite :: ProgStateRef -> IO ()+loadTestSuite pstRef = do+ config <- pa `fmap` readIORef pstRef+ unless (hasFlagP IgnoreSemanticsFlg config) $+ let parser f = do+ sem <- parseFromFileOrFail geniTestSuite f+ mStrs <- parseFromFileOrFail geniTestSuiteString f+ return $ zip sem mStrs+ updater s x =+ x { tsuite = map cleanup s+ , tcase = fromMaybe "" $ getFlagP TestCaseFlg config}+ cleanup (tc,str) =+ tc { tcSem = (sortSem sm, sort sr, lc)+ , tcSemString = str }+ where (sm, sr, lc) = tcSem tc+ in loadThingOrDie TestSuiteFlg "test suite" pstRef parser updater+\end{code}++Sometimes, the target semantics does not come from a file, but from+the graphical interface, so we also provide the ability to parse an+arbitrary string as the semantics.++\begin{code}+-- | Updates program state the same way as 'loadTestSuite'+loadTargetSemStr :: ProgStateRef -> String -> IO ()+loadTargetSemStr pstRef str = + do pst <- readIORef pstRef+ if hasFlagP IgnoreSemanticsFlg (pa pst) then return () else parseSem+ where+ parseSem = do+ let sem = runParser geniSemanticInput () "" str+ case sem of+ Left err -> fail (show err)+ Right sr -> modifyIORef pstRef (\x -> x{ts = smooth sr})+ smooth (s,r,l) = (sortSem s, sort r, l)+\end{code}++\subsubsection{Helpers for loading files}++\begin{code}+type UpdateFn a = (a -> ProgState -> ProgState)++loadThingOrIgnore, loadThingOrDie :: forall f a . (Eq f, Show f, Typeable f)+ => (FilePath -> f) -- ^ flag+ -> String+ -> ProgStateRef+ -> (FilePath -> IO [a])+ -> UpdateFn [a]+ -> IO ()++loadThing :: FilePath -- ^ file to load+ -> String -- ^ description+ -> ProgStateRef+ -> (FilePath -> IO [a]) -- ^ parsing cmd+ -> UpdateFn [a] -- ^ update fn+ -> IO ()++-- | Load the file if the relevant option is set, otherwise ignore+loadThingOrIgnore flag description pstRef parser job =+ do config <- pa `fmap` readIORef pstRef+ case getFlagP flag config of+ Nothing -> return ()+ Just f -> loadThing f description pstRef parser job++-- | Load the file if the relevant option is set, otherwise complain and die+loadThingOrDie flag description pstRef parser job =+ do config <- pa `fmap` readIORef pstRef+ case getFlagP flag config of+ Nothing -> fail $ "Please specify a " ++ description ++ "!"+ Just f -> loadThing f description pstRef parser job++loadThing filename description pstRef parser job =+ do config <- pa `fmap` readIORef pstRef+ let verbose = hasFlagP VerboseModeFlg config+ when verbose $ do+ ePutStr $ unwords [ "Loading", description, filename ++ "... " ]+ eFlush+ theTs <- parser filename+ when verbose $ ePutStr $ (show $ length theTs) ++ " entries\n"+ modifyIORef pstRef (job theTs)++parseFromFileOrFail :: Parser a -> FilePath -> IO a+parseFromFileOrFail p f = parseFromFile p f >>= either (fail.show) (return)++parseFromFileMaybeBinary :: Binary a+ => Parser a+ -> FilePath+ -> IO a+parseFromFileMaybeBinary p f =+ if (".genib" `isSuffixOf` f)+ then decodeFile f+ else parseFromFileOrFail p f+\end{code}++% --------------------------------------------------------------------+\subsection{Surface realisation - entry point}+% --------------------------------------------------------------------++This is your basic entry point. You call this if the only thing you want to do+is run the surface realiser.++\begin{enumerate}+\item It initialises the realiser (lexical selection, among other things),+ via \fnref{initGeni}+\item It runs the builder (the surface realisation engine proper)+\item It unpacks the builder results +\item It finalises the results (morphological generation)+\end{enumerate}++\begin{code}+type GeniResult = (String, B.Derivation)++-- | Returns a list of sentences, a set of Statistics, and the generator state.+-- The generator state is mostly useful for debugging via the graphical interface.+-- Note that we assumes that you have already loaded in your grammar and+-- parsed your input semantics.+runGeni :: ProgStateRef -> B.Builder st it Params -> IO ([GeniResult], Statistics, st)+runGeni pstRef builder = runGeniWithSelector pstRef defaultSelector builder++runGeniWithSelector :: ProgStateRef -> Selector -> B.Builder st it Params -> IO ([GeniResult], Statistics, st)+runGeniWithSelector pstRef selector builder =+ do let run = B.run builder+ unpack = B.unpack builder+ getPartial = B.partial builder+ -- step 1+ initStuff <- initGeniWithSelector pstRef selector+ --+ pst <- readIORef pstRef+ let config = pa pst+ -- step 2 + (finalSt, stats) = run initStuff config+ -- step 3+ uninflected = unpack finalSt+ partial = getPartial finalSt+ -- step 4+ sentences <- if null uninflected && hasFlagP PartialFlg config+ then map (first star) `fmap` finaliseResults pstRef partial+ else finaliseResults pstRef uninflected+ return (sentences, stats, finalSt)+ where star :: String -> String+ star s = '*' : s+\end{code}++% --------------------------------------------------------------------+\subsection{Surface realisation - sub steps}+% --------------------------------------------------------------------++Below are the initial and final steps of \fnreflite{runGeni}. These functions+are seperated out so that they may be individually called from the graphical+debugger. The middle steps (running and unpacking the builder) depend on your+builder implementation.++\begin{code}+-- | 'initGeni' performs lexical selection and strips the input semantics of+-- any morpohological literals+initGeni :: ProgStateRef -> IO (B.Input)+initGeni pstRef = initGeniWithSelector pstRef defaultSelector++initGeniWithSelector :: ProgStateRef -> Selector -> IO (B.Input)+initGeniWithSelector pstRef lexSelector =+ do -- disable constraints if the NoConstraintsFlg anti-optimisation is active+ modifyIORef pstRef+ (\p -> if hasOpt NoConstraints (pa p)+ then p { ts = (fst3 (ts p),[],[]) }+ else p)+ -- lexical selection+ pstLex <- readIORef pstRef+ (cand, lexonly) <- lexSelector pstLex+ -- strip morphological predicates+ let (tsem,tres,lc) = ts pstLex+ tsem2 = stripMorphSem (morphinf pstLex) tsem+ --+ let initStuff = B.Input + { B.inSemInput = (tsem2, tres, lc)+ , B.inLex = lexonly + , B.inCands = map (\c -> (c,-1)) cand+ }+ return initStuff +\end{code}++\begin{code}+-- | 'finaliseResults' for the moment consists only of running the+-- morphological generator, but there could conceivably be more involved.+finaliseResults :: ProgStateRef -> [B.Output] -> IO [GeniResult]+finaliseResults pstRef os =+ do mss <- runMorph pstRef ss+ return . concat $ zipWith merge mss ds+ where+ (ss,ds) = unzip os+ merge ms d = map (\m -> (m,d)) ms+\end{code}++% --------------------------------------------------------------------+\subsection{Displaying results}+% --------------------------------------------------------------------++\begin{code}+-- | Show the sentences produced by the generator, in a relatively compact form+showRealisations :: [String] -> String+showRealisations sentences =+ let sentencesGrouped = map (\ (s,c) -> s ++ countStr c) g+ where g = groupAndCount sentences + countStr c = if c > 1 then " (" ++ show c ++ " instances)"+ else ""+ in if null sentences+ then "(none)"+ else unlines sentencesGrouped+\end{code}++\begin{code}+-- | 'getTraces' is most likely useful for grammars produced by a+-- metagrammar system. Given a tree name, we retrieve the ``trace''+-- information from the grammar for all trees that have this name. We+-- assume the tree name was constructed by GenI; see the source code for+-- details.+getTraces :: ProgState -> String -> [String]+getTraces pst tname =+ filt $ concat [ ptrace t | t <- gr pst, pidname t == readPidname tname ]+ where+ filt = case traces pst of+ [] -> id+ theTs -> filter (`elem` theTs)++-- | We assume the name was constructed by 'combineName'+readPidname :: String -> String+readPidname n =+ case wordsBy ':' n of+ (_:_:p:_) -> p+ _ -> geniBug "readPidname or combineName are broken"+\end{code}++% --------------------------------------------------------------------+\section{Lexical selection}+\label{sec:candidate_selection} \label{sec:lexical_selecetion} \label{par:lexSelection}+% --------------------------------------------------------------------++\paragraph{runLexSelection} \label{fn:runLexSelection} determines which+candidates trees which will be used to generate the current target semantics. +In addition to the anchored candidate trees, we also return the lexical items +themselves. This list of lexical items is useful for debugging a grammar; +it lets us know if GenI managed to lexically select something, but did not +succeed in anchoring it.++\begin{code}+runLexSelection :: ProgState -> IO ([TagElem], [ILexEntry])+runLexSelection pst =+ do -- select lexical items first + let (tsem,_,litConstrs) = ts pst+ lexicon = le pst+ lexCand = chooseLexCand lexicon tsem+ config = pa pst+ verbose = hasFlagP VerboseModeFlg config+ -- then anchor these lexical items to trees+ let grammar = gr pst+ combineWithGr l =+ do let (_, res) = combineList grammar l+ familyMembers = [ p | p <- grammar, pfamily p == ifamname l ]+ -- snippets of error message+ let lexeme = showLexeme.iword $ l+ _outOfFamily n = show n ++ "/" ++ (show $ length familyMembers)+ ++ " instances of " ++ lexeme ++ ":" ++ ifamname l+ -- print out missing coanchors list+ case concatMap (missingCoanchors l) familyMembers of+ [] -> return ()+ cs -> mapM_ showWarning . group . sort $ cs+ where showWarning [] = geniBug "silly error in Geni.runLexSelection"+ showWarning xs =+ ePutStrLn $+ "Warning: Missing co-anchor '" ++ head xs ++ "'"+ ++ " in " ++ (_outOfFamily $ length xs) ++ "."+ -- print out enrichment errors+{-+ unless (null enrichEs) $ do+ let numDiscards = length enrichEs+ badEnrichments = [ av | av <- iequations l, hasMatch av ]+ hasMatch (a,_) = any (== parsePathEq a) errLocs+ errLocs = map eeLocation enrichEs+ ePutStrLn $ "Warning: Discarded "+ ++ _outOfFamily numDiscards+ ++ "\n due to enrichment failure with "+ ++ "[" ++ showPairs badEnrichments ++ "]."+ mapM (ePutStrLn.show) otherEs+-}++ -- FIXMENOW when (not.null $ errs) $ ePutStrLn (unlines errs)+ return res+ cand <- case grammarType config of+ PreAnchored -> readPreAnchored pst+ _ -> concat `liftM` mapM combineWithGr lexCand+ -- attach any morphological information to the candidates+ let considerMorph = attachMorph (morphinf pst) tsem+ -- filter out candidates which do not fulfill the trace constraints+ let matchesLc t = all (`elem` myTrace) constrs+ where constrs = concat [ cs | (l,cs) <- litConstrs, l `elem` mySem ]+ mySem = tsemantics t+ myTrace = ttrace t+ considerLc = filter matchesLc+ -- filter out candidates whose semantics has bonus stuff which does+ -- not occur in the input semantics+ let considerCoherency = filter (all (`elem` tsem) . tsemantics)+ considerHasSem = filter (not . null . tsemantics)+ --+ let candFinal = setTidnums . considerCoherency . considerHasSem+ . considerLc . considerMorph $ cand+ indent x = ' ' : x+ unlinesIndentAnd :: (x -> String) -> [x] -> String+ unlinesIndentAnd f = unlines . map (indent . f)+ when verbose $+ do ePutStrLn $ "Lexical items selected:\n" ++ (unlinesIndentAnd (showLexeme.iword) lexCand)+ ePutStrLn $ "Trees anchored (family) :\n" ++ (unlinesIndentAnd idname candFinal)+ -- lexical selection failures+ let missedSem = tsem \\ (nub $ concatMap tsemantics candFinal)+ hasTree l = isJust $ find (\t -> tsemantics t == lsem) cand+ where lsem = isemantics l+ missedLex = filter (not.hasTree) lexCand+ unless (null missedSem) $+ ePutStrLn $ "WARNING: no lexical selection for " ++ showSem missedSem+ unless (null missedLex) $+ ePutStrLn $ "WARNING: '" ++ (concat $ intersperse ", " $ map showLex missedLex)+ ++ "' were lexically selected, but are not anchored to"+ ++ " any trees"+ return (candFinal, lexCand)+ where showLex l = (showLexeme $ iword l) ++ "-" ++ (ifamname l)++-- | Select and returns the set of entries from the lexicon whose semantics+-- subsumes the input semantics.+chooseLexCand :: Lexicon -> Sem -> [ILexEntry]+chooseLexCand slex tsem = + let keys = toKeys tsem+ -- we choose candidates that match keys+ lookuplex t = Map.findWithDefault [] t slex+ cand = concatMap lookuplex $ myEMPTY : keys+ -- and refine the selection... + cand2 = chooseCandI tsem cand+ -- treat synonyms as a single lexical entry+ -- FIXME: disabled see mergeSynonyms for explanation+ -- cand3 = mergeSynonyms cand2+ in cand2+\end{code}++With a helper function, we refine the candidate selection by+instatiating the semantics, at the same time filtering those which+do not stay within the target semantics, and finally eliminating +the duplicates.++\begin{code}+chooseCandI :: Sem -> [ILexEntry] -> [ILexEntry]+chooseCandI tsem cand =+ let replaceLex i (sem,sub) = + (replace sub i) { isemantics = sem }+ --+ helper :: ILexEntry -> [ILexEntry]+ helper l = if null sem then [l]+ else map (replaceLex l) psubsem+ where psubsem = subsumeSem tsem sem+ sem = isemantics l+ --+ in nub $ concatMap helper cand +\end{code}++A semantic key is a semantic literal boiled down to predicate plus arity+(see section \ref{btypes_semantics}).+++\begin{code}+-- | 'mapBySemKeys' @xs fn@ organises items (@xs@) by their semantic key+-- (retrieved by @fn@). An item may have multiple keys.+--- This is used to organise the lexicon by its semantics.+mapBySemKeys :: (a -> Sem) -> [a] -> Map.Map String [a]+mapBySemKeys semfn xs = + let gfn t = if (null s) then [myEMPTY] else toKeys s + where s = semfn t+ in multiGroupByFM gfn xs+\end{code}++\fnlabel{mergeSynonyms} is a factorisation technique that uses+atomic disjunction to merge all synonyms into a single lexical+entry. Two lexical entries are considered synonyms if their+semantics match and they point to the same tree families.++FIXME: 2006-10-11 - note that this is no longer being used,+because it breaks the case where two lexical entries differ+only by their use of path equations. Perhaps it's worthwhile+just to add a check that the path equations match exactly.++\begin{code}+{-+mergeSynonyms :: [ILexEntry] -> [ILexEntry]+mergeSynonyms lexEntry =+ let mergeFn l1 l2 = l1 { iword = (iword l1) ++ (iword l2) }+ keyFn l = (ifamname l, isemantics l) + synMap = foldr helper Map.empty lexEntry+ where helper x acc = Map.insertWith mergeFn (keyFn x) x acc + in Map.elems synMap+-}+\end{code}++% --------------------------------------------------------------------+\subsection{Basic anchoring}+\label{sec:combine_macros}+% --------------------------------------------------------------------++This section of the code helps you to combined a selected lexical item with+a macro or a list of macros. This is a process that can go fail for any+number of reasons, so we try to record the possible failures for book-keeping.++\begin{code}+data LexCombineError =+ BoringError String+ | EnrichError { eeMacro :: MTtree+ , eeLexEntry :: ILexEntry+ , eeLocation :: PathEqLhs }+ | OtherError MTtree ILexEntry String++instance Error LexCombineError where+ noMsg = strMsg "error combining items"+ strMsg s = BoringError s++instance Show LexCombineError where+ show (BoringError s) = "Warning: " ++ s+ show (OtherError t l s) =+ "Warning: " ++ s ++ " on " ++ (pidname t) ++ "-" ++ (pfamily t) ++ " (" ++ (showLexeme $ iword l) ++ ")"+ show (EnrichError t l _) = show (OtherError t l "enrichment error")+\end{code}++The first step in lexical selection is to collect all the features and+parameters that we want to combine.++\begin{code}+-- | 'combine' @macros lex@ creates the 'Tags' repository combining lexical+-- entries and un-anchored trees from the grammar. It also unifies the+-- parameters used to specialize un-anchored trees and propagates additional+-- features given in the 'ILexEntry'.+combine :: Macros -> Lexicon -> Tags+combine gram lexicon =+ let helper li = mapEither (combineOne li) macs+ where tn = ifamname li+ macs = [ t | t <- gram, pfamily t == tn ]+ in Map.map (\e -> concatMap helper e) lexicon ++mapEither :: (a -> Either l r) -> [a] -> [r]+mapEither fn = mapMaybe (\x -> either (const Nothing) Just $ fn x)+\end{code}++\begin{code}+-- | Given a lexical item, looks up the tree families for that item, and+-- anchor the item to the trees.+combineList :: Macros -> ILexEntry+ -> ([LexCombineError],[TagElem]) -- ^ any warnings, plus the results+combineList gram lexitem =+ case [ t | t <- gram, pfamily t == tn ] of+ [] -> ([BoringError $ "Family " ++ tn ++ " not found in Macros"],[])+ macs -> unzipEither $ map (combineOne lexitem) macs+ where tn = ifamname lexitem++unzipEither :: (Error e, Show b) => [Either e b] -> ([e], [b])+unzipEither es = helper ([],[]) es where+ helper accs [] = accs+ helper (eAcc, rAcc) (Left e : next) = helper (e:eAcc,rAcc) next+ helper (eAcc, rAcc) (Right r : next) = helper (eAcc,r:rAcc) next+\end{code}++\begin{code}+-- | Combine a single tree with its lexical item to form a bonafide TagElem.+-- This process can fail, however, because of filtering or enrichement+combineOne :: ILexEntry -> MTtree -> Either LexCombineError TagElem+combineOne lexRaw eRaw = -- Maybe monad+ -- trace ("\n" ++ (show wt)) $+ do let l1 = alphaConvert "-l" lexRaw+ e1 = alphaConvert "-t" eRaw+ (l,e) <- unifyParamsWithWarning (l1,e1)+ >>= unifyInterfaceUsing iinterface+ >>= unifyInterfaceUsing ifilters -- filtering+ >>= enrichWithWarning -- enrichment+ let name = concat $ intersperse ":" $ filter (not.null)+ [ head (iword l) , pfamily e , pidname e ]+ return $ emptyTE+ { idname = name+ , ttreename = pfamily e+ , ttype = ptype e+ , ttree = setOrigin name . setLemAnchors . setAnchor (iword l) $ tree e+ , tsemantics =+ sortSem $ case psemantics e of+ Nothing -> isemantics l+ Just s -> s+ , tsempols = isempols l+ , tinterface = pinterface e+ , ttrace = ptrace e+ }+ where+ unifyParamsWithWarning (l,t) =+ -- trace ("unify params " ++ wt) $+ let lp = iparams l+ tp = map fromGVar $ params t+ psubst = zip tp lp+ in if (length lp) /= (length tp)+ then Left $ OtherError t l $ "Parameter length mismatch"+ else Right $ (replaceList psubst l, replaceList psubst t)+ --+ unifyInterfaceUsing ifn (l,e) =+ -- trace ("unify interface" ++ wt) $+ case unifyFeat (ifn l) (pinterface e) of+ Nothing -> Left $ OtherError e l $ "Interface unification error"+ Just (int2, fsubst) -> Right $ (replace fsubst l, e2)+ where e2 = (replace fsubst e) { pinterface = int2 }+ --+ enrichWithWarning (l,e) =+ -- trace ("enrich" ++ wt) $+ do e2 <- enrich l e+ return (l,e2)+\end{code}++\subsubsection{CGM Enrichement}++Enrichment is a concept introduced by the common grammar manifesto+\cite{kow05CGM}, the idea being that during lexical selection, you sometimes+want to add feature structures to specific nodes in a tree.++The conventions taken by GenI for path equations are:++\begin{tabular}{|l|p{8cm}|}+\hline+\verb!interface.foo=bar! &+\fs{foo=bar} is unified into the interface (not the tree) \\+\hline+\verb!anchor.bot.foo=bar! &+\fs{foo=bar} is unified into the bottom feature of the node+which is marked anchor. \\+\hline+\verb!toto.top.foo=bar! &+\fs{foo=bar} is unified into the top feature of node named toto \\+\hline+\verb!toto.bot.foo=bar! &+\fs{foo=bar} is unified into the bot feature of node named toto \\+\hline+\verb!anchor.foo=bar! &+same as \verb!anchor.bot.foo=bar! \\+\hline+\verb!anc.whatever...! &+same as \verb!anchor.whatever...! \\+\hline+\verb!top.foo=bar! &+same as \verb!anchor.top.foo=bar! \\+\hline+\verb!bot.foo=bar! &+same as \verb!anchor.bot.foo=bar! \\+\hline+\verb!foo=bar! &+same as \verb!anchor.bot.foo=bar! \\+\hline+\verb!toto.foo=bar! &+same as \verb!toto.top.foo=bar! (creates a warning) \\+\hline+\end{tabular}++\begin{code}+-- | (node, top, att) (node is Nothing if anchor)+type PathEqLhs = (String, Bool, String)+type PathEqPair = (PathEqLhs, GeniVal)++enrich :: ILexEntry -> MTtree -> Either LexCombineError MTtree+enrich l t =+ do -- separate into interface/anchor/named+ let (intE, namedE) = lexEquations l+ -- enrich the interface and everything else+ t2 <- foldM enrichInterface t intE+ -- enrich everything else+ foldM (enrichBy l) t2 namedE+ where+ toAvPair ((_,_,a),v) = (a,v)+ enrichInterface tx en =+ do (i2, isubs) <- unifyFeat [toAvPair en] (pinterface tx)+ `catchError` (\_ -> throwError $ ifaceEnrichErr en)+ return $ (replace isubs tx) { pinterface = i2 }+ ifaceEnrichErr (loc,_) = EnrichError+ { eeMacro = t+ , eeLexEntry = l+ , eeLocation = loc }++enrichBy :: ILexEntry -- ^ lexeme (for debugging info)+ -> MTtree+ -> (PathEqLhs, GeniVal) -- ^ enrichment eq+ -> Either LexCombineError MTtree+enrichBy lexEntry t (eqLhs, eqVal) =+ case seekCoanchor eqName t of+ Nothing -> return t -- to be robust, we accept if the node isn't there+ Just a ->+ do let tfeat = (if eqTop then gup else gdown) a+ (newfeat, sub) <- unifyFeat [(eqAtt,eqVal)] tfeat+ `catchError` (\_ -> throwError enrichErr)+ let newnode = if eqTop then a {gup = newfeat}+ else a {gdown = newfeat}+ return $ fixNode newnode $ replace sub t+ where+ (eqName, eqTop, eqAtt) = eqLhs+ fixNode n mt = mt { tree = repNodeByNode (matchNodeName eqName) n (tree mt) }+ enrichErr = EnrichError { eeMacro = t+ , eeLexEntry = lexEntry+ , eeLocation = eqLhs }++pathEqName :: PathEqPair -> String+pathEqName = fst3.fst++missingCoanchors :: ILexEntry -> MTtree -> [String]+missingCoanchors lexEntry t =+ -- list monad+ do eq <- nubBy (equating pathEqName) $ snd $ lexEquations lexEntry+ let name = pathEqName eq+ case seekCoanchor name t of+ Nothing -> [name]+ Just _ -> []++-- | Split a lex entry's path equations into interface enrichement equations+-- or (co-)anchor modifiers+lexEquations :: ILexEntry -> ([PathEqPair], [PathEqPair])+lexEquations =+ partition (nameIs "interface") . map (first parsePathEq) . iequations+ where nameIs n x = pathEqName x == n++seekCoanchor :: String -> MTtree -> Maybe GNode+seekCoanchor eqName t =+ case filterTree (matchNodeName eqName) (tree t) of+ [a] -> Just a+ [] -> Nothing+ _ -> geniBug $ "Tree with multiple matches in enrichBy. " +++ "\nTree: " ++ pidname t ++ "\nFamily: " ++ pfamily t +++ "\nMatching on: " ++ eqName++matchNodeName :: String -> GNode -> Bool+matchNodeName "anchor" = ganchor+matchNodeName n = (== n) . gnname++-- | Parse a path equation using the GenI conventions+parsePathEq :: String -> PathEqLhs+parsePathEq e =+ case wordsBy '.' e of+ (n:"top":r) -> (n, True, rejoin r)+ (n:"bot":r) -> (n, False, rejoin r)+ ("top":r) -> ("anchor", True, rejoin r)+ ("bot":r) -> ("anchor", False, rejoin r)+ ("anc":r) -> parsePathEq $ rejoin $ "anchor":r+ ("anchor":r) -> ("anchor", False, rejoin r)+ ("interface":r) -> ("interface", False, rejoin r)+ (n:r) -> unsafePerformIO $ do+ ePutStrLn $ "Warning: Interpreting path equation " ++ e +++ " as applying to top of " ++ n ++ "."+ return (n, True, rejoin r)+ _ -> unsafePerformIO $ do+ ePutStrLn $ "Warning: could not interpret path equation " ++ e+ return ("", True, e) -- unknown+ where+ rejoin = concat . (intersperse ".")+\end{code}++\subsubsection{Lemanchor mechanism}++One problem in building reversible grammars is the treatment of co-anchors.+In the French language, for example, we have some structures like+\natlang{C'est Jean qui regarde Marie}+\natlang{It is John who looks at Mary}++One might be tempted to hard code the ce (it) and the être (is) into the tree+for regarder (look at), something like \texttt{s(ce, être, n$\downarrow$, qui,+v(regarder), n$\downarrow$)}. Indeed, this would work just fine for+generation, but not for parsing. When you parse, you would encounter inflected+forms for these items for example \natlang{c'} for \natlang{ce} or+\natlang{sont} or \natlang{est} for \natlang{être}. Hard-coding the \natlang{ce}+into such trees would break parsing.++To work around this, we propose a mechanism to have our co-anchors and parsing+too. Co-anchors that are susceptible to morphological variation should be+\begin{itemize}+\item marked in a substitution site (this is to keep parsers happy)+\item have a feature \texttt{bot.lemanchor:foo} where foo is the+ coanchor you want+\end{itemize}++GenI will convert these into non-substitution sites with a lexical item+leaf node.++\begin{code}+setLemAnchors :: Tree GNode -> Tree GNode+setLemAnchors t =+ repAllNode fn filt t+ where+ filt (Node a []) = gtype a == Subs && (isJust. lemAnchor) a+ filt _ = False+ fn (Node x k) = setLexeme (lemAnchorMaybeFake x) $+ Node (x { gtype = Other, gaconstr = False }) k+ --+ lemAnchorMaybeFake :: GNode -> [String]+ lemAnchorMaybeFake n =+ case lemAnchor n of+ Nothing -> ["ERR_UNSET_LEMMANCHOR"]+ Just l -> l+ lemAnchor :: GNode -> Maybe [String]+ lemAnchor n =+ case [ v | (a,v) <- gdown n, a == _lemanchor ] of+ [GConst l] -> Just l+ _ -> Nothing++_lemanchor :: String+_lemanchor = "lemanchor"+\end{code}++\subsubsection{Node origins}++After lexical selection, we label each tree node with its origin, most+likely the name and id of its elementary tree. This is useful for+building derivation trees++\begin{code}+setOrigin :: String -> Tree GNode -> Tree GNode+setOrigin t = fmap (\g -> g { gorigin = t })+\end{code}++% --------------------------------------------------------------------+\subsection{Pre-selection and pre-anchoring}+\label{sec:pre-anchor}+% --------------------------------------------------------------------++For testing purposes, we can perform lexical selection ahead of time and store+it somewhere else.++\begin{code}+-- | Only used for instances of GenI where the grammar is compiled+-- directly into GenI.+type Selector = ProgState -> IO ([TagElem],[ILexEntry])++defaultSelector :: Selector+defaultSelector = runLexSelection+\end{code}++For debugging purposes, it is often useful to perform lexical selection and+surface realisation separately. Pre-anchored mode allows the user to just+pass the lexical selection in as a file of anchored trees associated with a+semantics.++\begin{code}+readPreAnchored :: ProgState -> IO [TagElem]+readPreAnchored pst =+ case getFlagP MacrosFlg (pa pst) of+ Nothing -> fail "No macros file specified (preanchored mode)"+ Just file -> parseFromFileOrFail geniTagElems file+\end{code}++% --------------------------------------------------------------------+\section{Morphology} +% --------------------------------------------------------------------++\begin{code}+-- | 'runMorph' inflects a list of sentences if a morphlogical generator+-- has been specified. If not, it returns the sentences as lemmas.+runMorph :: ProgStateRef -> [[(String,Flist)]] -> IO [[String]]+runMorph pstRef sentences = + do pst <- readIORef pstRef+ case morphlex pst of+ Just m -> return (inflectSentencesUsingLex m sentences)+ Nothing -> case getFlagP MorphCmdFlg (pa pst) of+ Nothing -> return $ map sansMorph sentences+ Just cmd -> inflectSentencesUsingCmd cmd sentences+\end{code}++
+ src/NLP/GenI/GeniParsers.lhs view
@@ -0,0 +1,763 @@+% GenI surface realiser+% Copyright (C) 2005 Carlos Areces and Eric Kow+%+% This program is free software; you can redistribute it and/or+% modify it under the terms of the GNU General Public License+% as published by the Free Software Foundation; either version 2+% of the License, or (at your option) any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; if not, write to the Free Software+% Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.++\chapter{File formats (GeniParsers)}+\label{cha:GeniParsers}++This chapter is a description of the file format used by GenI. You+might also have to look at the LORIA wiki for documentation on this.+See \url{http://wiki.loria.fr/wiki/GenI/Input_format}. If the+descriptions here sound a little weird to you, it's likely because+they used to be source code comments, and are being converted into+actual documentation.++\ignore{+\begin{code}+module NLP.GenI.GeniParsers (+ -- test suite stuff+ geniTestSuite, geniSemanticInput, geniTestSuiteString,+ geniDerivations,+ toSemInputString,+ -- macros + geniMacros,+ -- lexicons+ geniLexicon, geniMorphLexicon, geniMorphInfo,+ -- features and polarities+ geniFeats, geniPolarities,+ -- TagElem,+ geniTagElems,+ -- things used by external scripts+ geniSemantics, geniValue, geniWords, geniLanguageDef, tillEof,+) where++import NLP.GenI.General ((!+!), Interval, ival)+import NLP.GenI.Btypes+import NLP.GenI.Tags (TagElem(..), emptyTE, setTidnums)+import NLP.GenI.GeniShow (GeniShow(geniShow))+import Control.Monad (liftM, when)+import Data.List (sort)+import qualified Data.Map as Map +import qualified Data.Tree as T+import Text.ParserCombinators.Parsec+import Text.ParserCombinators.Parsec.Language (emptyDef)+import Text.ParserCombinators.Parsec.Token (TokenParser, + LanguageDef(..), makeTokenParser)+import qualified Text.ParserCombinators.Parsec.Token as P++-- reserved words+#define SEMANTICS "semantics"+#define SENTENCE "sentence"+#define OUTPUT "output"+#define TRACE "trace"+#define ANCHOR "anchor"+#define SUBST "subst"+#define FOOT "foot"+#define LEX "lex"+#define TYPE "type"+#define ACONSTR_NOADJ "aconstr:noadj"+#define INITIAL "initial"+#define AUXILIARY "auxiliary"+#define IDXCONSTRAINTS "idxconstraints"+#define BEGIN "begin"+#define END "end"+\end{code}+}++\section{Test suites}++The test suite format consists of arbitrarily many test cases:++\begin{code}+geniTestSuite :: Parser [TestCase]+geniTestSuite = + tillEof (many geniTestCase)++-- | Just the String representations of the semantics+-- in the test suite+geniTestSuiteString :: Parser [String]+geniTestSuiteString =+ tillEof (many geniTestCaseString)++-- | This is only used by the script genimakesuite+geniDerivations :: Parser [TestCaseOutput]+geniDerivations = tillEof $ many geniOutput+\end{code}++A test case is composed of an optional test id, some semantic input+\fnref{geniSemanticInput}, followed by any number of sentences+and optionally followed by a list of outputs.+The sentences can either be known good sentences (optionally preceded by the+keyword 'sentence' -- perhaps this should be mandatory one day). The outputs+are used directly by users. The field is useful for noting what outputs were+actually produced, say, in a script that generates test suites from GenI+output. This field doesn't have much use for GenI per se, just its satellite+scripts.++\begin{code}+geniTestCase :: Parser TestCase+geniTestCase =+ do name <- option "" (identifier <?> "a test case name")+ seminput <- geniSemanticInput+ sentences <- many geniSentence+ outputs <- many geniOutput+ return $ TestCase name "" seminput sentences outputs++-- note that the keyword is NOT optional+type TestCaseOutput = (String, Map.Map (String,String) [String])+geniOutput :: Parser TestCaseOutput+geniOutput =+ do ws <- keyword OUTPUT >> (squares geniWords)+ ds <- Map.fromList `fmap` many geniTraces+ return (ws, ds)++geniTraces :: Parser ((String,String), [String])+geniTraces =+ do keyword TRACE+ squares $ do+ k1 <- withWhite geniWord+ k2 <- withWhite geniWord+ whiteSpace >> char '!' >> whiteSpace+ traces <- sepEndBy1 geniWord whiteSpace+ return ((k1,k2), traces)++withWhite :: Parser a -> Parser a+withWhite p = p >>= (\a -> whiteSpace >> return a)++geniSentence :: Parser String+geniSentence = optional (keyword SENTENCE) >> squares geniWords++geniWords :: Parser String+geniWords =+ unwords `fmap` (sepEndBy1 geniWord whiteSpace <?> "a sentence")++geniWord :: Parser String+geniWord = many1 (noneOf "[]\v\f\t\r\n ")++-- | The original string representation of a test case semantics+-- (for gui)+geniTestCaseString :: Parser String+geniTestCaseString =+ do option "" (identifier <?> "a test case name")+ s <- geniSemanticInputString+ many geniSentence+ many geniOutput+ return s+\end{code}++\section{Semantics}++\fnlabel{geniSemanticInput} consists of a semantics, and optionally a+set of index constraints.++The semantics may contain literal based constraints as described in+section \ref{sec:fixme}. These constraints are just a space-delimited+list of String. When returning the results, we separate them out from+the semantics proper so that they can be treated separately.++Index constraints are represented as feature structures. For more+details about them, see \fnref{detectIdxConstraints}.++\begin{code}+geniSemanticInput :: Parser (Sem,Flist,[LitConstr])+geniSemanticInput =+ do keywordSemantics+ (sem,litC) <- liftM unzip $ squares $ many literalAndConstraint+ idxC <- option [] geniIdxConstraints+ --+ let sem2 = createHandles sem+ semlitC2 = [ (s,c) | (s,c) <- zip sem2 litC, (not.null) c ]+ return (createHandles sem, idxC, semlitC2)+ where + -- set all anonymous handles to some unique value+ -- this is to simplify checking if a result is+ -- semantically complete+ createHandles :: Sem -> Sem+ createHandles = zipWith setHandle ([1..] :: [Int])+ --+ setHandle i (h, pred_, par) =+ let h2 = if h /= GAnon then h + else GConst ["genihandle" ++ (show i)]+ in (h2, pred_, par)+ --+ literalAndConstraint :: Parser (Pred, [String])+ literalAndConstraint =+ do l <- geniLiteral+ t <- option [] $ squares $ many identifier+ return (l,t)++-- | The original string representation of the semantics (for gui)+geniSemanticInputString :: Parser String+geniSemanticInputString =+ do keywordSemantics+ s <- squaresString+ whiteSpace+ optional geniIdxConstraints+ return s++geniIdxConstraints :: Parser Flist+geniIdxConstraints = keyword IDXCONSTRAINTS >> geniFeats++squaresString :: Parser String+squaresString =+ do char '['+ s <- liftM concat $ many $ (many1 $ noneOf "[]") <|> squaresString+ char ']'+ return $ "[" ++ s ++ "]"++-- the output end of things+-- displaying preformatted semantic input++data SemInputString = SemInputString String Flist++instance GeniShow SemInputString where+ geniShow (SemInputString semStr idxC) =+ SEMANTICS ++ ":" ++ semStr ++ (if null idxC then "" else r)+ where r = "\n" ++ IDXCONSTRAINTS ++ ": " ++ showFlist idxC++toSemInputString :: SemInput -> String -> SemInputString+toSemInputString (_,lc,_) s = SemInputString s lc+\end{code}++\section{Lexicon}++A lexicon is just a whitespace seperated list of lexical entries.+Each lexical entry is +\begin{enumerate}+\item A lemma+\item The family name of things this lemma anchors to+\item The interface to the tree. Here's the compicated bit. + Either you provide :+\begin{itemize}+\item A list of parameters and an interface, as defined in+ \fnref{geniParams}. The interface is meant to be unified with+ the tree interface.+\item A feature structure which is to be unifed with the tree interface.+ This is equivalent to the attribute-value pairs above; the only+ difference is that we don't do any parameters, and we use square+ brackets instead of parentheses.+\item Optionally: a set of path equations for enrichmment.+ This feature structure can consist of+ path equations of the form node.att:val, because they will be+ unified with the entire tree and not just the tree interface. To+ force something to unify with a tree interface in XMG, you should+ supply ``interface.'' as a node name.+\end{itemize}+\item Optionally: a set of filters. This is to be used in conjunction+ with XMG's SelectTAG. Note that you must explicitly include + family as an attribute, even if it's already declared in the + lexical entry.+\end{enumerate}++\begin{code}+geniLexicon :: Parser [ILexEntry]+geniLexicon = tillEof $ many1 geniLexicalEntry++geniLexicalEntry :: Parser ILexEntry+geniLexicalEntry = + do lemma <- (looseIdentifier <|> stringLiteral) <?> "a lemma"+ family <- identifier <?> "a tree family"+ (pars, interface) <- option ([],[]) $ parens paramsParser+ equations <- option [] $ do keyword "equations"+ geniFeats <?> "path equations"+ filters <- option [] $ do keyword "filters"+ geniFeats+ keywordSemantics+ (sem,pols) <- squares geniLexSemantics+ --+ return emptyLE { iword = [lemma]+ , ifamname = family + , iparams = pars+ , iinterface = sortFlist interface+ , iequations = equations+ , ifilters = filters+ , isemantics = sem+ , isempols = pols }+ where + paramsParser :: Parser ([GeniVal], Flist)+ paramsParser = do+ pars <- many geniValue <?> "some parameters"+ interface <- option [] $ do symbol "!"+ many geniAttVal+ return (pars, interface)+\end{code}++\section{Trees}++\subsection{Macros}++A macro library is basically a list of trees.++\begin{code}+geniMacros :: Parser [MTtree]+geniMacros = tillEof $ many geniTreeDef++initType, auxType :: Parser Ptype+initType = do { reserved INITIAL ; return Initial }+auxType = do { reserved AUXILIARY ; return Auxiliar }+\end{code}++\subsection{Tree definitions}++A tree definition consists of +\begin{enumerate}+\item a family name, followed by an optional tree id+\item the tree parameters/interface as defined in \fnref{geniParams}+\item (optional) a tree type specification, as parameterised through the+ \fnparam{ttypeP} argument +\item the tree itself+\end{enumerate}++\begin{code}+geniTreeDef :: Parser MTtree+geniTreeDef =+ do sourcePos <- getPosition+ family <- identifier+ tname <- option "" $ do { colon; identifier }+ (pars,iface) <- geniParams + theTtype <- (initType <|> auxType)+ theTree <- geniTree+ -- sanity checks?+ let treeFail x =+ do setPosition sourcePos -- FIXME does not do what I expect+ fail $ "In tree " ++ family ++ ":" ++ tname ++ " " ++ show sourcePos ++ ": " ++ x+ let theNodes = T.flatten theTree+ numFeet = length [ x | x <- theNodes, gtype x == Foot ]+ numAnchors = length [ x | x <- theNodes, ganchor x ]+ when (not $ any ganchor theNodes) $+ treeFail "At least one node in an LTAG tree must be an anchor"+ when (numAnchors > 1) $+ treeFail "There can be no more than 1 anchor node in a tree"+ when (numFeet > 1) $+ treeFail "There can be no more than 1 foot node in a tree"+ when (theTtype == Initial && numFeet > 0) $+ treeFail "Initial trees may not have foot nodes"+ --+ psem <- option Nothing $ do { keywordSemantics; liftM Just (squares geniSemantics) }+ ptrc <- option [] $ do { keyword TRACE; squares (many identifier) }+ --+ return TT{ params = pars+ , pfamily = family+ , pidname = tname+ , pinterface = sortFlist iface+ , ptype = theTtype+ , tree = theTree+ , ptrace = ptrc+ , psemantics = psem+ }+\end{code}++\subsection{Tree structure}++A tree is recursively defined as a node followed by an optional list of child+nodes. If there are any child nodes, they appear between curly brackets.++A node consists of ++\begin{enumerate}+\item A node name+\item (optionally) a node type (anchor, lexeme, foot, subst).+\item (if node type is lexeme) a lexeme+\item (optionally) an adjunction constraint + (Notes: We only know about null adjunction constraints.+ If the node has a type, it is assumed as having+ a null adjunction constraint)+\end{enumerate}++Example of a tree:+\begin{verbatim}+n2 type:subst [cat:np idx:?Agent]![]+n3[cat:vp idx:?Event]![]+{+ n4 aconstr:noadj [cat:v idx:?Event]![]+ {+ n5 anchor+ }+\end{verbatim}++\begin{code}+geniTree :: Parser (T.Tree GNode)+geniTree = + do node <- geniNode+ kids <- option [] (braces $ many geniTree)+ <?> "child nodes"+ -- sanity checks+ let noKidsAllowed t c = when (c node && (not.null $ kids)) $+ fail $ t ++ " nodes may *not* have any children"+ noKidsAllowed "Anchor" $ ganchor+ noKidsAllowed "Substitution" $ (== Subs) . gtype+ noKidsAllowed "Foot" $ (== Foot) . gtype+ --+ return (T.Node node kids)++geniNode :: Parser GNode+geniNode = + do name <- identifier + nodeType <- option "" ( (keyword TYPE >> typeParser)+ <|>+ reserved ANCHOR)+ lex_ <- if nodeType == LEX+ then (sepBy (stringLiteral<|>identifier) (symbol "|") <?> "some lexemes") + else return [] + constr <- case nodeType of+ "" -> adjConstraintParser+ ANCHOR -> adjConstraintParser+ _ -> return True+ (top_,bot_) <- -- features only obligatory for non-lex nodes+ if nodeType == LEX+ then option ([],[]) $ try topbotParser+ else topbotParser+ --+ let top = sort top_+ bot = sort bot_+ nodeType2 = case nodeType of+ ANCHOR -> Lex+ LEX -> Lex+ FOOT -> Foot+ SUBST -> Subs+ "" -> Other+ other -> error ("unknown node type: " ++ other)+ return $ GN { gnname = name, gtype = nodeType2+ , gup = top, gdown = bot+ , glexeme = lex_+ , ganchor = (nodeType == ANCHOR)+ , gaconstr = constr+ , gorigin = "" }+ where + typeParser = choice $ map (try.symbol) [ ANCHOR, FOOT, SUBST, LEX ]+ adjConstraintParser = option False $ reserved ACONSTR_NOADJ >> return True+ topbotParser =+ do top <- geniFeats <?> "top features" + symbol "!"+ bot <- geniFeats <?> "bot features"+ return (top,bot)+\end{code}++\subsection{TagElem}++For debugging purposes, it is often useful to be able to read TagElem's+directly. Note that this shares a lot of code with the macros above.+Hopefully, it is reasonably refactored.++FIXME: note that this is very rudimentary; we do not set id numbers,+parse polarities. You'll have to call+some of our helper functions if you want that functionality.++\begin{code}+geniTagElems :: Parser [TagElem]+geniTagElems = tillEof $ setTidnums `fmap` many geniTagElem++geniTagElem :: Parser TagElem+geniTagElem =+ do family <- identifier+ tname <- option "" $ do { colon; identifier }+ iface <- (snd `liftM` geniParams) <|> geniFeats+ theType <- initType <|> auxType+ theTree <- geniTree+ sem <- do { keywordSemantics; squares geniSemantics }+ --+ return $ emptyTE { idname = tname+ , ttreename = family+ , tinterface = iface+ , ttype = theType+ , ttree = theTree+ , tsemantics = sem }+\end{code}++\section{Polarities}++The polarities parser is used for parsing extra polarity input from the+user. For more information, see chapter \ref{cha:Polarity}.++\begin{code}+geniPolarities :: Parser (Map.Map String Interval)+geniPolarities = tillEof $ toMap `fmap` many pol+ where + toMap = Map.fromListWith (!+!)+ pol = do p <- geniPolarity + i <- identifier+ return (i,ival p)+\end{code}++\fnlabel{geniPolarity} associates a numerical value to a polarity symbol,+ that is, '+' or '-'.++\begin{code}+geniPolarity :: Parser Int+geniPolarity = option 0 (plus <|> minus)+ where + plus = do { char '+'; return 1 }+ minus = do { char '-'; return (-1) } +\end{code}+++\section{Morphology}++GenI has two types of morphological input.++\paragraph{morphinfo} A morphinfo file associates predicates with+morphological feature structures. Each morphological entry consists of+a predicate followed by a feature structuer. For more information, see+chapter \ref{cha:Morphology}.++\begin{code}+geniMorphInfo :: Parser [(String,Flist)]+geniMorphInfo = tillEof $ many morphEntry++morphEntry :: Parser (String,Flist)+morphEntry =+ do pred_ <- identifier+ feats <- geniFeats+ return (pred_, feats)+\end{code}++\paragraph{morphlexicon} A morphological lexicon is a table where each+entry is an inflected form followed by the lemma and the feature+structure to which it is associated. The table is whitespace-delimited.++\begin{code}+geniMorphLexicon :: Parser [MorphLexEntry]+geniMorphLexicon = tillEof $ many morphLexiconEntry++morphLexiconEntry :: Parser (String, String, Flist)+morphLexiconEntry =+ do inflected <- try stringLiteral <|> geniWord+ whiteSpace+ lemma <- try stringLiteral <|> geniWord+ whiteSpace+ feats <- geniFeats+ return (inflected, lemma, feats)+\end{code}++\section{Generic GenI stuff}++\subsection{Lexer}++Some preliminaries about GenI formats in general - comments start with +\verb!%! There is also the option of using \verb'/* */' for embedded+comments. ++\begin{code}+lexer :: TokenParser ()+lexer = makeTokenParser geniLanguageDef++geniLanguageDef :: LanguageDef ()+geniLanguageDef = emptyDef+ { commentLine = "%"+ , commentStart = "/*"+ , commentEnd = "*/"+ , opLetter = oneOf ""+ , reservedOpNames = [""]+ , reservedNames =+ [ SEMANTICS , SENTENCE, OUTPUT, IDXCONSTRAINTS, TRACE+ , ANCHOR , SUBST , FOOT , LEX , TYPE , ACONSTR_NOADJ+ , INITIAL , AUXILIARY+ , BEGIN , END ]+ , identLetter = identStuff+ , identStart = identStuff+ }+ where identStuff = alphaNum <|> oneOf "_'+-."++whiteSpace :: CharParser () ()+whiteSpace = P.whiteSpace lexer++looseIdentifier, identifier, stringLiteral, colon :: CharParser () String+identifier = P.identifier lexer++-- stolen from Parsec code (ident)+-- | Like 'identifier' but allows for reserved words too+looseIdentifier =+ do { i <- ident ; whiteSpace; return i }+ where+ ident =+ do { c <- identStart geniLanguageDef+ ; cs <- many (identLetter geniLanguageDef)+ ; return (c:cs) } <?> "identifier"++stringLiteral = P.stringLiteral lexer+colon = P.colon lexer++squares, braces, parens :: CharParser () a -> CharParser () a+squares = P.squares lexer+braces = P.braces lexer+parens = P.parens lexer++reserved, symbol :: String -> CharParser () String+reserved s = P.reserved lexer s >> return s+symbol = P.symbol lexer+\end{code}++\subsection{Keyword}++A key is nothing simpler than the keyword, followed by a colon.+We factor this into a seperate function to account for whitespace.++\begin{code}+{-# INLINE keyword #-}+keyword :: String -> Parser String +keyword k = + do let helper = try $ do { reserved k; colon; return k }+ helper <?> k ++ ":"++{-# INLINE keywordSemantics #-}+keywordSemantics :: Parser String+keywordSemantics = keyword SEMANTICS+\end{code}++\subsection{Feature structures}++Feature structures take the form \verb!val : att! with only+whitespace to separate each attval pair. See \fnref{geniValue} for+details about what the values look like.++\begin{code}+geniFeats :: Parser Flist+geniFeats = option [] $ squares $ many geniAttVal++geniAttVal :: Parser AvPair+geniAttVal = do+ att <- identifier <?> "an attribute"; colon + val <- geniValue <?> "a GenI value"+ return (att, val)+\end{code}++\fnlabel{geniParams} recognises a list of parameters optionally followed by a+bang (\verb$!$) and a list of attribute-value pairs. This whole thing is to+wrapped in the parens.++\textbf{Note:} sometimes people prefer not to use parameters - instead they+stick to using the interface. This is fine, but they should not forget the+bang seperator.++\begin{code}+geniParams :: Parser ([GeniVal], Flist)+geniParams = parens $ do+ pars <- many geniValue <?> "some parameters"+ interface <- option [] $ do { symbol "!"; many geniAttVal }+ return (pars, interface)+\end{code}++\subsection{Semantics}++A semantics is simply a list of literals. A literal can take one of two+forms:+\begin{verbatim}+ handle:predicate(arguments)+ predicate(arguments)+\end{verbatim}++The arguments are space-delimited. Not providing a handle is+equivalent to providing an anonymous one.++\begin{code}+geniSemantics :: Parser Sem+geniSemantics = + do sem <- many (geniLiteral <?> "a literal")+ return (sortSem sem)++geniLiteral :: Parser Pred+geniLiteral = + do handle <- option GAnon handleParser <?> "a handle"+ predicate <- geniValue <?> "a predicate"+ pars <- parens (many geniValue) <?> "some parameters"+ --+ return (handle, predicate, pars)+ where handleParser = + try $ do { h <- geniValue ; char ':' ; return h }+\end{code}++\subsection{Lexical semantics}++A lexical semantics is almost exactly the same as a regular semantics, +except that each variable may be preceded by a polarity symbol. When+we figure out how to automate the detection of lexical semantic+polarities, we can start using a regular semantics again.++\begin{code}+geniLexSemantics :: Parser (Sem, [[Int]])+geniLexSemantics = + do litpols <- many (geniLexLiteral <?> "a literal")+ return $ unzip litpols++geniLexLiteral :: Parser (Pred, [Int])+geniLexLiteral = + do (handle, hpol) <- option (GAnon,0) (handleParser <?> "a handle") + predicate <- geniValue <?> "a predicate"+ paramsPols <- parens (many geniPolValue) <?> "some parameters"+ --+ let (pars, pols) = unzip paramsPols+ literal = (handle, predicate, pars)+ return (literal, hpol:pols)+ where handleParser = + try $ do { h <- geniPolValue; colon; return h }++geniPolValue :: Parser (GeniVal, Int)+geniPolValue = + do p <- geniPolarity+ v <- geniValue+ return (v,p)+\end{code}+++\subsection{Miscellaneous}++\fnlabel{geniValue} is recognised both in feature structures and in the +GenI semantics.++\begin{enumerate}+\item As of geni 0.8, variables are prefixed with a question+ mark.+\item The underscore, \verb!_!, and \verb!?_! are treated as anonymous+ variables.+\item Atomic disjunctions are seperated with a pipe, \verb!|!. Only+ constants may be separated by atomic disjunction+\item Anything else is just a constant+\end{enumerate}++\begin{code}+geniValue :: Parser GeniVal +geniValue = ((try $ anonymous) <?> "_ or ?_")+ <|> (constants <?> "a constant or atomic disjunction")+ <|> (variable <?> "a variable")+ where + question = "?"+ --+ constants :: Parser GeniVal + constants = + do c <- sepBy1 (looseIdentifier <|> stringLiteral) (symbol "|")+ return (GConst c)+ variable :: Parser GeniVal+ variable = + do symbol question + v <- identifier + return (GVar v)+ anonymous :: Parser GeniVal+ anonymous = + do optional $ symbol question + symbol "_"+ return GAnon+\end{code}++\begin{code}+tillEof :: Parser a -> Parser a+tillEof p =+ do whiteSpace+ r <- p+ eof+ return r+\end{code}++
+ src/NLP/GenI/GeniShow.lhs view
@@ -0,0 +1,185 @@+% GenI surface realiser+% Copyright (C) 2005 Carlos Areces and Eric Kow+%+% This program is free software; you can redistribute it and/or+% modify it under the terms of the GNU General Public License+% as published by the Free Software Foundation; either version 2+% of the License, or (at your option) any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; if not, write to the Free Software+% Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.++The GeniShow module provides specialised functions for visualising tree data.++% ----------------------------------------------------------------------+\section{GeniShow}+% ----------------------------------------------------------------------++We need to be able to dump some of GenI's data structures into a simple+text format we call GeniHand.++There are at leaste two uses for this, one is that it allows us to+interrupt the debugging process, dump everything to file, muck around+with the trees and then pick up where we left off.++The other use is to make large grammars faster to load. We don't actually do+this anymore, mind you, but it's nice to have the option. The idea is to take+a massive XML grammar, parse it to a set of TagElems and then write these back+in the lighter syntax. It's not that XML is inherently less efficient to parse+than the handwritten syntax, just that writing an efficient parser for XML+based format is more annoying, so I stuck with HaXml to make my life easy.+Unfortunately, HaXml seems to have some kind of space leak.++\begin{code}+module NLP.GenI.GeniShow+where+\end{code}++\ignore{+\begin{code}+import Data.Tree+import Data.List(intersperse, isPrefixOf)+import qualified Data.Map as Map++import NLP.GenI.Tags+ ( TagElem, idname,+ tsemantics, ttree, tinterface, ttype, ttreename,+ )+import NLP.GenI.Btypes (GeniVal(GConst), AvPair, Ptype(..),+ Ttree(params, pidname, pfamily, pinterface, ptype, tree, psemantics, ptrace),+ GNode(..), GType(..),+ SemInput, Pred,+ TestCase(..),+ )+\end{code}+}++\begin{code}+class GeniShow a where+ geniShow :: a -> String++instance GeniShow Ptype where+ geniShow Initial = "initial"+ geniShow Auxiliar = "auxiliary"+ geniShow _ = ""++instance GeniShow AvPair where+ geniShow (a,v) = a ++ ":" ++ geniShow v++instance GeniShow GeniVal where+ geniShow (GConst xs) = concat $ intersperse "|" xs+ geniShow x = show x++instance GeniShow Pred where+ geniShow (h, p, l) =+ showh ++ geniShow p ++ "(" ++ unwords (map geniShow l) ++ ")"+ where+ hideh (GConst [x]) = "genihandle" `isPrefixOf` x+ hideh _ = False+ showh = if hideh h then "" else geniShow h ++ ":"++instance GeniShow GNode where+ geniShow x =+ let gaconstrstr = case (gaconstr x, gtype x) of+ (True, Other) -> "aconstr:noadj"+ _ -> ""+ gtypestr n = case (gtype n) of+ Subs -> "type:subst"+ Foot -> "type:foot"+ Lex -> if ganchor n && (null.glexeme) n+ then "type:anchor" else "type:lex"+ _ -> ""+ glexstr n =+ if null ls then ""+ else concat $ intersperse "|" $ map quote ls+ where quote s = "\"" ++ s ++ "\""+ ls = glexeme n+ tbFeats n = (geniShow $ gup n) ++ "!" ++ (geniShow $ gdown n)+ in unwords $ filter (not.null) $ [ gnname x, gaconstrstr, gtypestr x, glexstr x, tbFeats x ]++instance (GeniShow a) => GeniShow [a] where+ geniShow = squares . unwords . (map geniShow)++instance (GeniShow a) => GeniShow (Tree a) where+ geniShow t =+ let treestr i (Node a l) =+ spaces i ++ geniShow a +++ case (l,i) of+ ([], 0) -> "{}"+ ([], _) -> ""+ (_, _) -> "{\n" ++ (unlines $ map next l) ++ spaces i ++ "}"+ where next = treestr (i+1)+ --+ spaces i = take i $ repeat ' '+ in treestr 0 t++instance GeniShow TagElem where+ geniShow te =+ "\n% ------------------------- " ++ idname te+ ++ "\n" ++ (ttreename te) ++ ":" ++ (idname te)+ ++ " " ++ (geniShow.tinterface $ te)+ ++ " " ++ (geniShow.ttype $ te)+ ++ "\n" ++ (geniShow.ttree $ te)+ ++ "\n" ++ geniShowKeyword "semantics" "" ++ (geniShow.tsemantics $ te)++instance (GeniShow a) => GeniShow (Ttree a) where+ geniShow tt =+ "\n% ------------------------- " ++ pidname tt+ ++ "\n" ++ (pfamily tt) ++ ":" ++ (pidname tt)+ ++ " " ++ (parens $ (unwords $ map geniShow $ params tt)+ ++ " ! "+ ++ (unwords $ map geniShow $ pinterface tt))+ ++ " " ++ (geniShow.ptype $ tt)+ ++ "\n" ++ (geniShow.tree $ tt)+ ++ (case psemantics tt of+ Nothing -> ""+ Just psem -> "\n" ++ geniShowKeyword "semantics" (geniShow psem))+ ++ "\n" ++ geniShowKeyword "trace" (squares.unwords.ptrace $ tt)++instance GeniShow TestCase where+ geniShow (TestCase { tcName = name+ , tcExpected = sentences+ , tcOutputs = outputs+ , tcSemString = semStr+ , tcSem = sem }) =+ unlines $ [ name, semS ]+ ++ map (geniShowKeyword "sentence" . squares) sentences+ ++ (concat.prettify.map outStuff $ outputs)+ where+ semS = if null semStr then geniShowSemInput sem "" else semStr+ prettify = if all (Map.null . snd) outputs then id else map ("":)+ gshowTrace ((k1,k2),ts) =+ geniShowKeyword "trace" . squares . showString (k1 ++ " " ++ k2 ++ " ! " ++ unwords ts) $ ""+ outStuff (o,ds) =+ [ geniShowKeyword "output" . squares $ o ]+ ++ (map gshowTrace $ Map.toList ds)+++parens, squares :: String -> String+parens s = "(" ++ s ++ ")"+squares s = "[" ++ s ++ "]"++geniShowKeyword :: String -> ShowS+geniShowKeyword k = showString k . showChar ':'++geniShowSemInput :: SemInput -> ShowS+geniShowSemInput (sem,icons,lcons) =+ let withConstraints lit =+ case concat [ cs | (p,cs) <- lcons, p == lit ] of+ [] -> geniShow lit+ cs -> geniShow lit ++ (squares . unwords $ cs)+ semStuff = geniShowKeyword "semantics" . squares+ . (showString . unwords . map withConstraints $ sem)+ idxStuff = geniShowKeyword "idxconstraints"+ . (showString . geniShow $ icons) . squares+ in semStuff . (if null icons then id else showChar '\n' . idxStuff)+\end{code}++\include{src/NLP/GenI/GraphvizShow.lhs}+\include{src/NLP/GenI/HsShow.lhs}
+ src/NLP/GenI/Graphviz.hs view
@@ -0,0 +1,212 @@+{-# OPTIONS -fglasgow-exts #-}++{-+ GenI surface realiser+ Copyright (C) 2005 Carlos Areces and Eric Kow+ + This program is free software; you can redistribute it and/or+ modify it under the terms of the GNU General Public License+ as published by the Free Software Foundation; either version 2+ of the License, or (at your option) any later version.+ + This program is distributed in the hope that it will be useful,+ but WITHOUT ANY WARRANTY; without even the implied warranty of+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+ GNU General Public License for more details.+ + You should have received a copy of the GNU General Public License+ along with this program; if not, write to the Free Software+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.+-}++{- | Graphviz is an open source tool which converts an abstract+ representation of a graph (node foo is connected to node bar, etc.)+ into a nicely laid out graphic. This module contains methods + to invoke graphviz and to convert graphs and trees to its input format.++ You can download this (open source) tool at+ <http://www.research.att.com/sw/tools/graphviz>+-}++module NLP.GenI.Graphviz+where++import Control.Monad(when)+import Data.List(intersperse)+import Data.Tree+import System.IO(hPutStrLn, hClose)+import System.Exit(ExitCode)++import NLP.GenI.SysGeni(waitForProcess, runInteractiveProcess)++{- |+ Data structures which can be visualised with GraphViz should+ implement this class. Note the first argument to graphvizShowGraph is+ so that you can parameterise your show function (i.e. pass in+ flags to change the way you show particular object). Note+ that by default, all graphs are treated as directed graphs. You+ can hide this by turning off edge arrows.+-}+class GraphvizShow flag b where+ graphvizShowGraph :: flag -> b -> String+ graphvizShowAsSubgraph :: flag -- ^ flag+ -> String -- ^ prefix+ -> b -- ^ item+ -> String -- ^ gv output + graphvizLabel :: flag -- ^ flag+ -> b -- ^ item+ -> String -- ^ gv output+ graphvizParams :: flag -> b -> [String] ++ graphvizShowGraph f b = + let l = graphvizLabel f b+ in "digraph {\n" + ++ (unlines $ graphvizParams f b)+ ++ graphvizShowAsSubgraph f "_" b ++ "\n"+ ++ (if null l then "" else " label = \"" ++ l ++ "\";\n")+ ++ "}"+ graphvizLabel _ _ = ""+ graphvizParams _ _ = []++class GraphvizShowNode flag b where+ graphvizShowNode :: flag -- ^ flag + -> String -- ^ prefix + -> b -- ^ item + -> String -- ^ gv output++-- | Things which are meant to be displayed within some other graph+-- as (part) of a node label+class GraphvizShowString flag b where+ graphvizShow :: flag -- ^ flag+ -> b -- ^ item+ -> String -- ^ gv output++-- | Note: the 'dotFile' argument allows you to save the intermediary+-- dot output to a file. You can pass in the empty string if you don't+toGraphviz :: (GraphvizShow f a) => f + -> a + -> String -- ^ the 'dotFile'+ -> String -> IO ExitCode +toGraphviz p x dotFile outputFile = do+ graphviz (graphvizShowGraph p x) dotFile outputFile++-- ---------------------------------------------------------------------+-- useful utility functions+-- ---------------------------------------------------------------------++gvNewline :: String+gvNewline = "\\n"++gvUnlines :: [String] -> String+gvUnlines = concat . (intersperse gvNewline)++gvSubgraph :: String -> String+gvSubgraph g = "subgraph {\n" ++ g ++ "}\n"++-- | The Graphviz string for a node. Note that we make absolutely no+-- effort to escape any characters for you; so if you need to protect+-- anything from graphviz, you're on your own+gvNode :: String -- ^ the node name+ -> String -- ^ the label (may be empty)+ -> [(String,String)] -- ^ any other parameters+ -> String+gvNode name label params = + " " ++ name ++ " " ++ (gvLabelAndParams label params) ++ "\n"++-- | The Graphviz string for a connection between two nodes. +-- Same disclaimer as 'gvNode' applies.+gvEdge :: String -- ^ the 'from' node+ -> String -- ^ the 'to' node+ -> String -- ^ the label (may be empty)+ -> [(String,String)] -- ^ any other parameters + -> String+gvEdge from to label params = + " " ++ from ++ " -> " ++ to ++ (gvLabelAndParams label params) ++ "\n"++gvLabelAndParams :: String -> [(String,String)] -> String+gvLabelAndParams l p = + gvParams $ if null l then p else ("label", l) : p++gvParams :: [(String,String)] -> String+gvParams [] = ""+gvParams p = "[ " ++ (concat $ intersperse ", " $ map showPair p) ++ " ]"+ where showPair (a,v) = a ++ "=\"" ++ v ++ "\""++-- ---------------------------------------------------------------------+-- some instances +-- ---------------------------------------------------------------------++instance (GraphvizShow f b) => GraphvizShow f (Maybe b) where+ graphvizShowAsSubgraph _ _ Nothing = ""+ graphvizShowAsSubgraph f p (Just b) = graphvizShowAsSubgraph f p b ++ graphvizLabel _ Nothing = ""+ graphvizLabel f (Just b) = graphvizLabel f b++ graphvizParams _ Nothing = [] + graphvizParams f (Just b) = graphvizParams f b++-- | Displays a tree in graphviz format. +{- Note that we could make this an+ instance of GraphvizShow, but I'm not too sure about the wisdom of+ such a move. ++ Maybe if we had some really super-sophisticated types in Haskell, where+ we can define this as the default instance which could be overrided by+ something more specific, that would be cool.++ The prefix argument is interpreted as the name of the top node. Node+ names below are basically Gorn addresses (e.g. n0x2x3 means 3rd child of+ the 2nd child of the root) to keep them distinct. Note : We use the+ letter `x' as seperator because graphviz will choke on `.' or `-', even+ underscore. -}+gvShowTree :: (GraphvizShowNode f n) => + (n->[(String,String)]) -- ^ function to convert a node to a list of graphviz parameters for the edge + -> f -- ^ GraphvizShow flag+ -> String -- ^ node prefix+ -> (Tree n) -- ^ the tree+ -> String+gvShowTree edgeFn f prefix t = + "edge [ arrowhead = none ]\n" ++ gvShowTreeHelper edgeFn f prefix t ++gvShowTreeHelper :: forall n . forall f . (GraphvizShowNode f n) => (n->[(String,String)]) -> f -> String -> (Tree n) -> String+gvShowTreeHelper edgeFn f prefix (Node node l) = + let showNode = graphvizShowNode f prefix + showKid :: Integer -> Tree n -> String+ showKid index kid = + gvShowTreeHelper edgeFn f kidname kid ++ " " + ++ (gvEdge prefix kidname "" (edgeFn node))+ where kidname = prefix ++ "x" ++ (show index)+ in showNode node ++ "\n" ++ (concat $ zipWith showKid [0..] l)++-- ---------------------------------------------------------------------+-- invocation +-- ---------------------------------------------------------------------++-- | Calls graphviz. If the second argument is the empty string, then we+-- just send stuff directly to dot's stdin++graphviz :: String -- ^ graphviz's dot format.+ -> String -- ^ the name of the file graphviz should write the dot + -> String -- ^ the name of the file graphviz should write its output + -> IO ExitCode++-- We write the dot String to a temporary file which we then feed to graphviz.+-- This is avoid complications with fork and pipes. We use png output even+-- though it's uglier, because we don't have a wxhaskell widget that can +-- display postscript... do we?++graphviz dot dotFile outputFile = do+ let dotArgs' = ["-Gfontname=courier", + "-Nfontname=courier", + "-Efontname=courier", + "-Gcharset=latin1", -- FIXME: should really output UTF-8 instead+ "-Tpng", "-o" ++ outputFile ]+ dotArgs = dotArgs' ++ (if (null dotFile) then [] else [dotFile])+ -- putStrLn ("sending to graphviz:\n" ++ dot) + when (not $ null dotFile) $ writeFile dotFile dot+ (_, toGV, _, pid) <- runInteractiveProcess "dot" dotArgs Nothing Nothing+ when (null dotFile) $ do + hPutStrLn toGV dot + hClose toGV+ waitForProcess pid
+ src/NLP/GenI/GraphvizShow.lhs view
@@ -0,0 +1,222 @@+% GenI surface realiser+% Copyright (C) 2005 Carlos Areces and Eric Kow+%+% This program is free software; you can redistribute it and/or+% modify it under the terms of the GNU General Public License+% as published by the Free Software Foundation; either version 2+% of the License, or (at your option) any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; if not, write to the Free Software+% Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.++\section{GraphvizShow}++Outputting core GenI data to graphviz.++\begin{code}+{-# LANGUAGE FlexibleInstances, TypeSynonymInstances, FlexibleContexts #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+module NLP.GenI.GraphvizShow+where+\end{code}++\ignore{+\begin{code}+import Data.List(intersperse,nub)++import NLP.GenI.Tags+ ( TagElem, TagDerivation, idname,+ tsemantics, ttree,+ )+import NLP.GenI.Btypes (GeniVal(GConst), AvPair,+ GNode(..), GType(..), Flist,+ isConst,+ showSem,+ )+import NLP.GenI.General (wordsBy)+import NLP.GenI.Graphviz+ ( gvUnlines, gvNewline+ , GraphvizShow(graphvizShowAsSubgraph, graphvizLabel, graphvizParams)+ , GraphvizShowNode(graphvizShowNode)+ , GraphvizShowString(graphvizShow)+ , gvNode, gvEdge, gvShowTree+ )++\end{code}+}++% ----------------------------------------------------------------------+\section{For GraphViz}+% ----------------------------------------------------------------------++\begin{code}+type GvHighlighter a = a -> (a, Maybe String)++nullHighlighter :: GvHighlighter GNode+nullHighlighter a = (a,Nothing)++instance GraphvizShow Bool TagElem where+ graphvizShowAsSubgraph sf = graphvizShowAsSubgraph (sf, nullHighlighter)+ graphvizLabel sf = graphvizLabel (sf, nullHighlighter )+ graphvizParams sf = graphvizParams (sf, nullHighlighter)+++instance GraphvizShow (Bool, GvHighlighter GNode) TagElem where+ graphvizShowAsSubgraph (sf,hfn) prefix te =+ (gvShowTree (\_->[]) sf (prefix ++ "DerivedTree0") $+ fmap hfn $ ttree te)++ graphvizLabel _ te =+ -- we display the tree semantics as the graph label+ let treename = "name: " ++ (idname te)+ semlist = "semantics: " ++ (showSem $ tsemantics te)+ in gvUnlines [ treename, semlist ]++ graphvizParams _ _ =+ [ "fontsize = 10", "ranksep = 0.3"+ , "node [fontsize=10]"+ , "edge [fontsize=10 arrowhead=none]" ]+\end{code}++Helper functions for the TagElem GraphvizShow instance++\section{GNode - GraphvizShow}++\begin{code}+instance GraphvizShowNode (Bool) (GNode, Maybe String) where+ -- compact -> (node, mcolour) -> String+ graphvizShowNode detailed prefix (gn, mcolour) =+ let -- attributes+ filledParam = ("style", "filled")+ fillcolorParam = ("fillcolor", "lemonchiffon")+ shapeRecordParam = ("shape", "record")+ shapePlaintextParam = ("shape", "plaintext")+ --+ colorParams = case mcolour of+ Nothing -> []+ Just c -> [ ("fontcolor", c) ]+ shapeParams = if detailed+ then [ shapeRecordParam, filledParam, fillcolorParam ]+ else [ shapePlaintextParam ]+ -- content+ stub = showGnStub gn+ extra = showGnDecorations gn+ summary = if null extra then stub+ else "{" ++ stub ++ "|" ++ extra ++ "}"+ --+ body = if not detailed then graphvizShow_ gn+ else "{" ++ summary+ ++ (barAnd.showFs $ gup gn)+ ++ (maybeShow (barAnd.showFs) $ gdown gn)+ ++ "}"+ where barAnd x = "|" ++ x+ showFs = gvUnlines . (map graphvizShow_)+ in gvNode prefix body (shapeParams ++ colorParams)+\end{code}++\begin{code}+instance GraphvizShowString () GNode where+ graphvizShow () gn =+ let stub = showGnStub gn+ extra = showGnDecorations gn+ in stub ++ maybeShow_ " " extra++instance GraphvizShowString () AvPair where+ graphvizShow () (a,v) = a ++ ":" ++ (graphvizShow_ v)++instance GraphvizShowString () GeniVal where+ graphvizShow () (GConst x) = concat $ intersperse " ! " x+ graphvizShow () x = show x++showGnDecorations :: GNode -> String+showGnDecorations gn =+ case gtype gn of+ Subs -> "!"+ Foot -> "*"+ _ -> if (gaconstr gn) then "#" else ""++showGnStub :: GNode -> String+showGnStub gn =+ let cat = case getGnVal gup "cat" gn of+ Nothing -> ""+ Just v -> graphvizShow_ v+ --+ getIdx f =+ case getGnVal f "idx" gn of+ Nothing -> ""+ Just v -> if isConst v then graphvizShow_ v else ""+ idxT = getIdx gup+ idxB = getIdx gdown+ idx = idxT ++ (maybeShow_ "." idxB)+ --+ lexeme = concat $ intersperse "!" $ glexeme gn+ in concat $ intersperse ":" $ filter (not.null) [ cat, idx, lexeme ]++getGnVal :: (GNode -> Flist) -> String -> GNode -> Maybe GeniVal+getGnVal getFeat attr gn =+ case [ av | av <- getFeat gn, fst av == attr ] of+ [] -> Nothing+ (av:_) -> Just (snd av)++-- | Apply fn to s if s is not null+maybeShow :: ([a] -> String) -> [a] -> String+maybeShow fn s = if null s then "" else fn s+-- | Prefix a string if it is not null+maybeShow_ :: String -> String -> String+maybeShow_ prefix s = maybeShow (prefix++) s++graphvizShow_ :: (GraphvizShowString () a) => a -> String+graphvizShow_ = graphvizShow ()+\end{code}++% ----------------------------------------------------------------------+\section{Derivation tree}+% ----------------------------------------------------------------------++\begin{code}+graphvizShowDerivation :: TagDerivation -> String+graphvizShowDerivation deriv =+ if (null histNodes)+ then ""+ else " node [ shape = plaintext ];\n"+ ++ (concatMap showHistNode histNodes)+ ++ (concatMap graphvizShowDerivation' deriv)+ where showHistNode n = gvNode (gvDerivationLab n) (label n) []+ label n = case wordsBy ':' n of+ name:fam:tree:_ -> name ++ ":" ++ fam ++ gvNewline ++ tree+ _ -> n ++ " (geni/gv ERROR)"+ histNodes = reverse $ nub $ concatMap (\ (_,c,(p,_)) -> [c,p]) deriv+\end{code}++\begin{code}+graphvizShowDerivation' :: (Char, String, (String, String)) -> String+graphvizShowDerivation' (substadj, child, (parent,_)) =+ gvEdge (gvDerivationLab parent) (gvDerivationLab child) "" p+ where p = if substadj == 'a' then [("style","dashed")] else []+\end{code}++We have a couple of functions to help massage our data into Graphviz input+format: node names can't have hyphens in them and newlines within the node+labels should be represented literally as \verb$\n$.++\begin{code}+gvDerivationLab :: String -> String+gvDerivationLab xs = "Derivation" ++ gvMunge xs++newlineToSlashN :: Char -> String+newlineToSlashN '\n' = gvNewline+newlineToSlashN x = [x]++gvMunge :: String -> String+gvMunge = map dot2x . filter (/= ':') . filter (/= '-')++dot2x :: Char -> Char+dot2x '.' = 'x'+dot2x c = c+\end{code}
+ src/NLP/GenI/GraphvizShowPolarity.lhs view
@@ -0,0 +1,130 @@+% GenI surface realiser+% Copyright (C) 2005 Carlos Areces and Eric Kow+%+% This program is free software; you can redistribute it and/or+% modify it under the terms of the GNU General Public License+% as published by the Free Software Foundation; either version 2+% of the License, or (at your option) any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; if not, write to the Free Software+% Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.++\begin{code}+{-# LANGUAGE TypeSynonymInstances #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+module NLP.GenI.GraphvizShowPolarity+where++import Data.List (intersperse)+import qualified Data.Map as Map++import NLP.GenI.Btypes(showSem)+import NLP.GenI.General(showInterval, isEmptyIntersect)+import NLP.GenI.Polarity(PolAut, PolState(PolSt), NFA(states, transitions), finalSt)+import NLP.GenI.Graphviz(GraphvizShow(..), gvUnlines, gvNewline, gvNode, gvEdge)+import NLP.GenI.Tags(idname)+\end{code}++\begin{code}+instance GraphvizShow () PolAut where+ -- we want a directed graph (arrows)+ graphvizShowGraph f aut =+ "digraph aut {\n"+ ++ "rankdir=LR\n"+ ++ "ranksep = 0.02\n"+ ++ "pack=1\n"+ ++ "edge [ fontsize=10 ]\n"+ ++ "node [ fontsize=10 ]\n"+ ++ graphvizShowAsSubgraph f "aut" aut+ ++ "}"++ --+ graphvizShowAsSubgraph _ prefix aut =+ let st = (concat.states) aut+ ids = map (\x -> prefix ++ show x) ([0..] :: [Int])+ -- map which permits us to assign an id to a state+ stmap = Map.fromList $ zip st ids+ in --+ gvShowFinal aut stmap+ -- any other state should be an ellipse+ ++ "node [ shape = ellipse, peripheries = 1 ]\n"+ -- draw the states and transitions+ ++ (concat $ zipWith gvShowState ids st)+ ++ (concat $ zipWith (gvShowTrans aut stmap) ids st )+\end{code}++\begin{code}+gvShowState :: String -> PolState -> String+gvShowState stId st =+ -- note that we pass the label param explicitly to allow for null label+ gvNode stId "" [ ("label", showSt st) ]+ where showSt (PolSt pr ex po) = showPr pr ++ showEx ex ++ showPo po+ showPr _ = "" -- (_,pr,_) = pr ++ gvNewline+ showPo po = concat $ intersperse "," $ map showInterval po+ showEx ex = if null ex then "" else showSem ex ++ gvNewline+\end{code}++Specify that the final states are drawn with a double circle++\begin{code}+gvShowFinal :: PolAut -> Map.Map PolState String -> String+gvShowFinal aut stmap =+ if isEmptyIntersect (concat $ states aut) fin+ then ""+ else "node [ peripheries = 2 ]; "+ ++ concatMap (\x -> " " ++ lookupId x) fin+ ++ "\n"+ where fin = finalSt aut+ lookupId x = Map.findWithDefault "error_final" x stmap+\end{code}++Each transition is displayed with the name of the tree. If there is more+than one transition to the same state, they are displayed on a single+label.++\begin{code}+gvShowTrans :: PolAut -> Map.Map PolState String+ -> String -> PolState -> String+gvShowTrans aut stmap idFrom st =+ let -- outgoing transition labels from st+ trans = Map.findWithDefault Map.empty st $ transitions aut+ -- returns the graphviz dot command to draw a labeled transition+ drawTrans (stTo,x) = case Map.lookup stTo stmap of+ Nothing -> drawTrans' ("id_error_" ++ (sem_ stTo)) x+ Just idTo -> drawTrans' idTo x+ where sem_ (PolSt i _ _) = show i+ --showSem (PolSt (_,pred,_) _ _) = pred+ drawTrans' idTo x = gvEdge idFrom idTo (drawLabel x) []+ drawLabel labels = gvUnlines labs+ where+ lablen = length labels+ maxlabs = 6+ excess = "...and " ++ (show $ lablen - maxlabs) ++ " more"+ --+ labstrs = map fn labels+ fn Nothing = "EMPTY"+ fn (Just x) = idname x+ --+ labs = if lablen > maxlabs+ then take maxlabs labstrs ++ [ excess ]+ else labstrs+ in unlines $ map drawTrans $ Map.toList trans+\end{code}++%gvShowTransPred te =+% let p = tpredictors te+% charge fv = case () of _ | c == -1 -> "-"+% | c == 1 -> "+"+% | c > 0 -> "+" ++ (show c)+% | otherwise -> (show c)+% where c = lookupWithDefaultFM p 0 fv+% showfv (f,v) = charge (f,v) ++ f+% ++ (if (null v) then "" else ":" ++ v)+% in map showfv $ Map.keys p+
+ src/NLP/GenI/Gui.lhs view
@@ -0,0 +1,756 @@+% GenI surface realiser+% Copyright (C) 2005 Carlos Areces and Eric Kow+%+% This program is free software; you can redistribute it and/or+% modify it under the terms of the GNU General Public License+% as published by the Free Software Foundation; either version 2+% of the License, or (at your option) any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; if not, write to the Free Software+% Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.++\chapter{Graphical User Interface} ++\begin{code}+{-# LANGUAGE FlexibleContexts #-}+module NLP.GenI.Gui(guiGeni) where+\end{code}++\ignore{+\begin{code}+import Graphics.UI.WX++import qualified Control.Monad as Monad +import qualified Data.Map as Map++import Data.IORef+import Data.List (isPrefixOf, nub, delete, (\\), find)+import Data.Maybe (isJust)+import System.Directory +import System.Exit (exitWith, ExitCode(ExitSuccess))++import qualified NLP.GenI.Builder as B+import qualified NLP.GenI.BuilderGui as BG+import NLP.GenI.Geni+ ( ProgState(..), ProgStateRef, combine, initGeni+ , loadEverything, loadTestSuite, loadTargetSemStr)+import NLP.GenI.General (boundsCheck, geniBug, trim, fst3)+import NLP.GenI.Btypes (ILexEntry(isemantics), TestCase(..), showFlist,)+import NLP.GenI.Tags (idname, tpolarities, tsemantics, TagElem)+import NLP.GenI.GeniShow (geniShow)+import NLP.GenI.Configuration+ ( Params(..), Instruction, hasOpt+ , hasFlagP, deleteFlagP, setFlagP, getFlagP, getListFlagP+ , parseFlagWithParsec+ --+ , ExtraPolaritiesFlg(..)+ , IgnoreSemanticsFlg(..)+ , LexiconFlg(..)+ , MacrosFlg(..)+ , MaxTreesFlg(..)+ , MorphCmdFlg(..)+ , MorphInfoFlg(..)+ , OptimisationsFlg(..)+ , RootFeatureFlg(..)+ , TestSuiteFlg(..)+ , TestCaseFlg(..)+ , TestInstructionsFlg(..)+ , ViewCmdFlg(..)+ --+ , Optimisation(..)+ , BuilderType(..), mainBuilderTypes )+import NLP.GenI.GeniParsers+import NLP.GenI.GuiHelper++import NLP.GenI.Polarity+import NLP.GenI.Simple.SimpleGui+import NLP.GenI.CkyEarley.CkyGui+++\end{code}+}++\section{Main Gui}++\begin{code}+guiGeni :: ProgStateRef -> IO() +guiGeni pstRef = start $ mainGui pstRef+\end{code}++When you first start GenI, you will see this screen:+[[FIXME:screenshot wanted]]++It allows you to type in an input semantics (or to modify the one that was+automatically loaded up), twiddle some optimisations and run the realiser. You+can also opt to run the debugger instead of the realiser; see page+\pageref{sec:gui:debugger}.++\begin{code}+mainGui :: ProgStateRef -> IO ()+mainGui pstRef + = do --+ pst <- readIORef pstRef+ -- Top Window+ f <- frame [text := "Geni Project"]+ -- create statusbar field+ status <- statusField []+ -- create the file menu+ fileMen <- menuPane [text := "&File"]+ loadMenIt <- menuItem fileMen [text := "&Open files or configure GenI"]+ quitMenIt <- menuQuit fileMen [text := "&Quit"]+ set quitMenIt [on command := close f ]+ -- create the tools menu+ toolsMen <- menuPane [text := "&Tools"]+ gbrowserMenIt <- menuItem toolsMen [ text := "&Inspect grammar" + , help := "Displays the trees in the grammar" ]+ -- create the help menu+ helpMen <- menuPane [text := "&Help"]+ aboutMeIt <- menuAbout helpMen [help := "About"]+ -- Tie the menu to this window+ set f [ statusBar := [status] + , menuBar := [fileMen, toolsMen, helpMen]+ -- put the menu event handler for an about box on the frame.+ , on (menu aboutMeIt) := infoDialog f "About GenI" "The GenI generator.\nhttp://wiki.loria.fr/wiki/GenI" + -- event handler for the tree browser+ , on (menu gbrowserMenIt) := do { loadEverything pstRef; treeBrowserGui pstRef } + ]+ -- -----------------------------------------------------------------+ -- buttons+ -- -----------------------------------------------------------------+ let config = pa pst + hasSem = hasFlagP TestSuiteFlg config+ ignoreSem = hasFlagP IgnoreSemanticsFlg config+ -- Target Semantics+ testSuiteChoice <- choice f [ selection := 0, enabled := hasSem ]+ tsTextBox <- textCtrl f [ wrap := WrapWord+ , clientSize := sz 400 80+ , enabled := hasSem + , text := if ignoreSem+ then "% --ignoresemantics set" else "" ]+ testCaseChoice <- choice f [ selection := 0 + , enabled := hasSem ]+ -- Box and Frame for files loaded + macrosFileLabel <- staticText f [ text := getListFlagP MacrosFlg config ]+ lexiconFileLabel <- staticText f [ text := getListFlagP LexiconFlg config ]+ -- Generate and Debug + let genfn = doGenerate f pstRef tsTextBox+ pauseOnLexChk <- checkBox f [ text := "Inspect lex", tooltip := "Affects debugger only" ]+ debugBt <- button f [ text := "Debug"+ , on command := get pauseOnLexChk checked >>= genfn True ]+ genBt <- button f [text := "Generate", on command := genfn False False ]+ quitBt <- button f [ text := "Quit",+ on command := close f]+ -- -----------------------------------------------------------------+ -- optimisations+ -- -----------------------------------------------------------------+ algoChoiceBox <- radioBox f Vertical (map show mainBuilderTypes)+ [ selection := case builderType config of+ SimpleBuilder -> 0+ SimpleOnePhaseBuilder -> 1+ CkyBuilder -> 2+ EarleyBuilder -> 3+ NullBuilder -> 0 ]+ set algoChoiceBox [ on select := toggleAlgo pstRef algoChoiceBox ]+ polChk <- optCheckBox Polarised pstRef f+ [ text := "Polarities"+ , tooltip := "Use the polarity optimisation"+ ]+ useSemConstraintsChk <- antiOptCheckBox NoConstraints pstRef f+ [ text := "Sem constraints"+ , tooltip := "Use any sem constraints the user provides"+ ]+ iafChk <- optCheckBox Iaf pstRef f+ [ text := "Idx acc filter"+ , tooltip := "Only available in CKY/Earley for now"+ ]+ semfilterChk <- optCheckBox SemFiltered pstRef f+ [ text := "Semantic filters"+ , tooltip := "(2p only) Filter away semantically incomplete structures before adjunction phase"+ ]+ rootfilterChk <- optCheckBox RootCatFiltered pstRef f+ [ text := "Root filters"+ , tooltip := "(2p only) Filter away non-root structures before adjunction phase"+ ]+ extrapolText <- staticText f + [ text := maybe "" showLitePm $ getFlagP ExtraPolaritiesFlg config+ , tooltip := "Use the following additional polarities" + ]+ -- commands for the checkboxes+ let togglePolStuff = do c <- get polChk checked+ set extrapolText [ enabled := c ]+ set polChk [on command :~ (>> togglePolStuff) ]+ -- -----------------------------------------------------------------+ -- layout; packing it all together+ -- -----------------------------------------------------------------+ -- set any last minute handlers, run any last minute functions+ let onLoad = readConfig f pstRef macrosFileLabel lexiconFileLabel testSuiteChoice tsTextBox testCaseChoice+ set loadMenIt [ on command := do configGui pstRef onLoad ]+ onLoad+ togglePolStuff+ --+ let labeledRow l w = row 1 [ label l, hfill (widget w) ]+ let gramsemBox = boxed "Files last loaded" $ + hfill $ column 1 + [ labeledRow "trees:" macrosFileLabel+ , labeledRow "lexicon:" lexiconFileLabel+ ]+ optimBox = --boxed "Optimisations " $ -- can't used boxed with wxwidgets 2.6 -- bug?+ column 5 [ label "Algorithm"+ , dynamic $ widget algoChoiceBox+ , label "Optimisations"+ , dynamic $ widget polChk + , row 5 [ label " ", column 5 + [ dynamic $ row 5 [ label "Extra: ", widget extrapolText ] ] ]+ , dynamic $ widget useSemConstraintsChk+ , dynamic $ widget semfilterChk + , dynamic $ widget rootfilterChk+ , dynamic $ widget iafChk+ ]+ set f [layout := column 5 [ gramsemBox+ , row 5 [ fill $ -- boxed "Input Semantics" $ + hfill $ column 5 + [ labeledRow "test suite: " testSuiteChoice+ , labeledRow "test case: " testCaseChoice+ , fill $ widget tsTextBox ]+ , vfill optimBox ]+ -- ----------------------------- Generate and quit + , row 1 [ widget quitBt + , hfloatRight $ row 5 [ widget pauseOnLexChk, widget debugBt, widget genBt ]] ]+ , clientSize := sz 525 325+ , on closing := exitWith ExitSuccess + ]+\end{code}++\subsection{Configuration}++Most of the optimisations are availalable as checkboxes. Note the following+point about anti-optimisations: An anti-optimisation disables a default+behaviour which is assumed to be ``optimisation''. But of course we don't+want to confuse the GUI user, so we confuse the programmer instead:+Given an anti-optimisation DisableFoo, we have a check box UseFoo. If UseFoo+is checked, we remove DisableFoo from the list; if it is unchecked, we add+it to the list. This is the opposite of the default behaviour, but the+result, I hope, is intuitive for the user.++\begin{code}+toggleAlgo :: (Selection a, Items a String) => ProgStateRef -> a -> IO ()+toggleAlgo pstRef box =+ do asel <- get box selection+ aitems <- get box items+ let selected = aitems !! asel+ btable = zip (map show mainBuilderTypes) mainBuilderTypes+ btype = case [ b | (name, b) <- btable, name == selected ] of+ [] -> geniBug $ "Unknown builder type " ++ selected+ [b] -> b+ _ -> geniBug $ "More than one builder has the name " ++ selected+ modifyIORef pstRef (\x -> x { pa = (pa x) { builderType = btype } })++optCheckBox, antiOptCheckBox ::+ Optimisation -> ProgStateRef+ -> Window a -> [Prop (CheckBox ())]+ -> IO (CheckBox ())++-- | Checkbox for enabling or disabling an optimisation+-- You need not set the checked or on command attributes+-- as this is done for you (but you can if you want,+-- setting checked will override the default, and any+-- command you set will be run before the toggle stuff)+optCheckBox = optCheckBoxHelper id++-- | Same as 'optCheckBox' but for anti-optimisations+antiOptCheckBox = optCheckBoxHelper not++optCheckBoxHelper :: (Bool -> Bool) -> Optimisation -> ProgStateRef+ -> Window a -> [Prop (CheckBox ())]+ -> IO (CheckBox ())+optCheckBoxHelper idOrNot o pstRef f as =+ do pst <- readIORef pstRef+ chk <- checkBox f $ [ checked := idOrNot $ hasOpt o $ pa pst ] ++ as+ set chk [ on command :~ (>> onCheck chk) ]+ return chk+ where+ onCheck chk =+ do isChecked <- get chk checked+ pst <- readIORef pstRef+ let config = pa pst+ modopt = if idOrNot isChecked then (o:) else delete o+ newopts = nub.modopt $ getListFlagP OptimisationsFlg config+ modifyIORef pstRef (\x -> x{pa = setFlagP OptimisationsFlg newopts (pa x)})+\end{code}++% --------------------------------------------------------------------+\section{Loading files}+% --------------------------------------------------------------------++\paragraph{readConfig} is used to update the graphical interface after+you run the \fnref{configGui}. It is also called when you first launch+the GUI++\begin{code}+readConfig :: (Textual l, Textual t, Able ch, Items ch String, Selection ch, Selecting ch)+ => Window w -> ProgStateRef -> l -> l -> ch -> t -> ch -> IO ()+readConfig f pstRef macrosFileLabel lexiconFileLabel suiteChoice tsBox caseChoice =+ do pst <- readIORef pstRef+ let config = pa pst+ -- errHandler title err = errorDialog f title (show err)+ set macrosFileLabel [ text := getListFlagP MacrosFlg config ]+ set lexiconFileLabel [ text := getListFlagP LexiconFlg config ]+ -- set tsFileLabel [ text := getListFlagP TestSuiteFlg config ]+ -- read the test suite if there is one+ case getListFlagP TestInstructionsFlg config of+ [] ->+ do set suiteChoice [ enabled := False, items := [] ]+ set caseChoice [ enabled := False, items := [] ]+ is ->+ do -- handler for selecting a test suite+ let imap = Map.fromList $ zip [0..] is+ onTestSuiteChoice = do+ sel <- get suiteChoice selection+ case Map.lookup sel imap of+ Nothing -> geniBug $ "No such index in test suite selector (gui): " ++ show sel+ Just t -> loadTestSuiteAndRefresh f pstRef t tsBox caseChoice+ set suiteChoice [ enabled := True, items := map fst is+ , on select := onTestSuiteChoice, selection := 0 ]+ set caseChoice [ enabled := True ]+ onTestSuiteChoice -- load the first suite++-- | Load the given test suite and update the GUI accordingly.+-- This is used when you first start the graphical interface+-- or when you run the configuration menu.+loadTestSuiteAndRefresh :: (Textual a, Selecting b, Selection b, Items b String) + => Window w -> ProgStateRef -> Instruction -> a -> b -> IO ()+loadTestSuiteAndRefresh f pstRef (suitePath,mcs) tsBox caseChoice =+ do modifyIORef pstRef $ \pst ->+ pst { pa = setFlagP TestSuiteFlg suitePath+ $ deleteFlagP TestCaseFlg -- shouldn't change anything+ $ pa pst }+ catch+ (loadTestSuite pstRef)+ (\e -> errorDialog f ("Error reading test suite " ++ suitePath) (show e))+ pst <- readIORef pstRef+ let suite = tsuite pst+ theCase = tcase pst+ filterCases =+ case mcs of+ Nothing -> id+ Just cs -> filter (\c -> tcName c `elem` cs)+ suiteCases = filterCases suite+ suiteCaseNames = map tcName suiteCases+ -- we number the cases for easy identification, putting + -- a star to highlight the selected test case (if available)+ let numfn :: Int -> String -> String+ numfn n t = (if t == theCase then "* " else "")+ ++ (show n) ++ ". " ++ t+ tcaseLabels = zipWith numfn [1..] suiteCaseNames+ -- we select the first case in cases_, if available+ let fstInCases _ [] = 0 + fstInCases n (x:xs) = + if (x == theCase) then n else fstInCases (n+1) xs+ caseSel = if null theCase then 0 + else fstInCases 0 suiteCaseNames+ ----------------------------------------------------+ -- handler for selecting a test case+ ----------------------------------------------------+ let displaySemInput (TestCase { tcSem = si, tcSemString = str }) =+ geniShow $ toSemInputString si str+ let onTestCaseChoice = do+ csel <- get caseChoice selection+ if (boundsCheck csel suite)+ then do let s = (suiteCases !! csel)+ set tsBox [ text :~ (\_ -> displaySemInput s) ]+ else geniBug $ "Gui: test case selector bounds check error: " +++ show csel ++ " of " ++ show suite ++ "\n" + ----------------------------------------------------+ set caseChoice [ items := tcaseLabels + , selection := caseSel+ , on select := onTestCaseChoice ]+ when (not $ null suite) onTestCaseChoice -- run this once+\end{code}+ +% --------------------------------------------------------------------+\section{Configuration}+% --------------------------------------------------------------------++\paragraph{configGui}\label{fn:configGui} provides a graphical interface which+aims to be a complete substitute for the command line switches. In addition to+the program state \fnparam{pstRef}, it takes a continuation \fnparam{loadFn}+which tells what to do when the user closes the window.++The only thing which are not provided in this GUI are a list of optimisations+and a test case selector (which are already handled by the main interface).+This GUI is a standalone window with two tabbed sections. Note: one thing+you may want to note is that we do not divide the same way between basic+and advanced options as with the console interface.++\begin{code}+configGui :: ProgStateRef -> IO () -> IO () +configGui pstRef loadFn = do + pst <- readIORef pstRef+ let config = pa pst+ -- + f <- frame []+ p <- panel f []+ nb <- notebook p []+ let browseTxt = "Browse"+ --+ let fakeBoxed title lst = hstretch $ column 3 $ map hfill $ + [ hrule 1 , alignRight $ label title, vspace 5 ] + ++ map hfill lst+ let shortSize = sz 10 25+ let longSize = sz 20 25+\end{code}++The first tab contains only the basic options:++\begin{code}+ pbas <- panel nb []+ -- files loaded (labels)+ macrosFileLabel <- staticText pbas [ text := getListFlagP MacrosFlg config ]+ lexiconFileLabel <- staticText pbas [ text := getListFlagP LexiconFlg config ]+ tsFileLabel <- staticText pbas [ text := getListFlagP TestSuiteFlg config ]+ -- "Browse buttons"+ macrosBrowseBt <- button pbas [ text := browseTxt ]+ lexiconBrowseBt <- button pbas [ text := browseTxt ]+ tsBrowseBt <- button pbas [ text := browseTxt ]+ -- root feature+ rootFeatTxt <- entry pbas+ [ text := showFlist $ getListFlagP RootFeatureFlg config+ , size := longSize ]+ let layFiles = [ row 1 [ label "trees:" + , fill $ widget macrosFileLabel+ , widget macrosBrowseBt ]+ , row 1 [ label "lexicon:"+ , fill $ widget lexiconFileLabel+ , widget lexiconBrowseBt ] + , row 1 [ label "test suite:"+ , fill $ widget tsFileLabel+ , widget tsBrowseBt ]+ , hspace 5+ , hfill $ vrule 1+ , row 3 [ label "root features"+ , hglue+ , rigid $ widget rootFeatTxt ] + ] + -- the layout for the basic stuff+ let layBasic = dynamic $ container pbas $ -- boxed "Basic options" $ + hfloatLeft $ dynamic $ fill $ column 4 $ map (dynamic.hfill) $ layFiles +\end{code}++The second tab contains more advanced options. Maybe we should split this+into more tabs?++\begin{code}+ padv <- panel nb []+ -- XMG tools + viewCmdTxt <- entry padv + [ tooltip := "Command used for XMG tree viewing"+ , text := getListFlagP ViewCmdFlg config ]+ let layXMG = fakeBoxed "XMG tools" + [ row 3 [ label "XMG view command"+ , marginRight $ hfill $ widget viewCmdTxt ] ]+ -- polarities+ extraPolsTxt <- entry padv + [ text := maybe "" showLitePm $ getFlagP ExtraPolaritiesFlg config+ , size := shortSize ]+ let layPolarities = fakeBoxed "Polarities" [ hfill $ row 1 + [ label "extra polarities", rigid $ widget extraPolsTxt ] ]+ -- morphology+ morphFileLabel <- staticText padv [ text := getListFlagP MorphInfoFlg config ]+ morphFileBrowseBt <- button padv [ text := browseTxt ]+ morphCmdTxt <- entry padv + [ tooltip := "Commmand used for morphological generation" + , text := getListFlagP MorphCmdFlg config ]+ let layMorph = fakeBoxed "Morphology" + [ row 3 [ label "morph info:"+ , expand $ hfill $ widget morphFileLabel+ , widget morphFileBrowseBt ]+ , row 3 [ label "morph command"+ , (marginRight.hfill) $ widget morphCmdTxt ] ]+ -- ignore semantics+ ignoreSemChk <- checkBox padv + [ text := "Ignore semantics"+ , tooltip := "Useful as a corpus generator"+ , checked := hasFlagP IgnoreSemanticsFlg config ]+ let maxTreesStr = maybe "" show $ getFlagP MaxTreesFlg config+ maxTreesText <- entry padv + [ text := maxTreesStr + , tooltip := "Limit number of elementary trees in a derived tree" + , size := shortSize ]+ let layIgnoreSem = fakeBoxed "Ignore Semantics Mode" + [ row 3 [ widget ignoreSemChk + , hspace 5 + , label "max trees", rigid $ widget maxTreesText ] ]+ -- put the whole darn thing together+ let layAdvanced = hfloatLeft $ container padv $ column 10 + $ [ layXMG, layPolarities, layMorph, layIgnoreSem ]+\end{code}++When the user clicks on a Browse button, an open file dialogue should pop up.+It gets its value from the file label on its left (passed in as an argument),+and updates said label when the user has made a selection.++\begin{code}+ -- helper functions+ curDir <- getCurrentDirectory+ let curDir2 = curDir ++ "/"+ trim2 pth = if curDir2 `isPrefixOf` pth2+ then drop (length curDir2) pth2+ else pth2+ where pth2 = trim pth+ let onBrowse theLabel + = do rawFilename <- get theLabel text+ let filename = trim2 rawFilename+ filetypes = [("Any file",["*","*.*"])]+ fsel <- fileOpenDialog f False True+ "Choose your file..." filetypes "" filename+ case fsel of+ -- if the user does not select any file there are no changes+ Nothing -> return () + Just file -> set theLabel [ text := trim2 file ]+ -- end onBrowse+ -- activate those "Browse" buttons+ let setBrowse w l = set w [ on command := onBrowse l ]+ setBrowse macrosBrowseBt macrosFileLabel+ setBrowse lexiconBrowseBt lexiconFileLabel + setBrowse tsBrowseBt tsFileLabel+ setBrowse morphFileBrowseBt morphFileLabel+\end{code}++Let's not forget the layout which puts the whole configGui together and the+command that makes everything ``work'':++\begin{code}+ let parsePol = parseFlagWithParsec "polarities" geniPolarities+ parseRF = parseFlagWithParsec "root features" geniFeats+ onLoad + = do macrosVal <- get macrosFileLabel text+ lexconVal <- get lexiconFileLabel text+ tsVal <- get tsFileLabel text+ --+ rootCatVal <- get rootFeatTxt text+ extraPolVal <- get extraPolsTxt text+ --+ viewVal <- get viewCmdTxt text + --+ morphCmdVal <- get morphCmdTxt text+ morphInfoVal <- get morphFileLabel text+ --+ ignoreVal <- get ignoreSemChk checked + maxTreesVal <- get maxTreesText text+ --+ let maybeSet fl fn x =+ if null x then deleteFlagP fl else setFlagP fl (fn x)+ maybeSetStr fl x = maybeSet fl id x+ toggleFlag fl b = if b then setFlagP fl () else deleteFlagP fl+ let setConfig = id+ . (maybeSet MaxTreesFlg read maxTreesVal)+ . (toggleFlag IgnoreSemanticsFlg ignoreVal)+ . (maybeSetStr MacrosFlg macrosVal)+ . (maybeSetStr LexiconFlg lexconVal)+ . (maybeSetStr TestSuiteFlg tsVal)+ . (maybeSet RootFeatureFlg parseRF rootCatVal)+ . (maybeSet ExtraPolaritiesFlg parsePol extraPolVal)+ . (maybeSetStr ViewCmdFlg viewVal)+ . (maybeSetStr MorphCmdFlg morphCmdVal)+ . (maybeSetStr MorphInfoFlg morphInfoVal)+ modifyIORef pstRef $ \x -> x { pa = setConfig (pa x) }+ loadFn + -- end onLoad+ -- the button bar+ cancelBt <- button p + [ text := "Cancel", on command := close f ]+ loadBt <- button p + [ text := "Load", on command := do { onLoad; close f } ]+ let layButtons = hfill $ row 1 + [ hfloatLeft $ widget cancelBt+ , hfloatRight $ widget loadBt ]+ --+ set f [ layout := dynamic $ fill $ container p $ column 0 + [ fill $ tabs nb [ tab "Basic" layBasic+ , tab "Advanced" layAdvanced ] + , hfill $ layButtons ]+ ] +\end{code}+ +% --------------------------------------------------------------------+\section{Running the generator}+% --------------------------------------------------------------------++\paragraph{doGenerate} parses the target semantics, then calls the+generator and displays the result in a results gui (below).++\begin{code}+doGenerate :: Textual b => Window a -> ProgStateRef -> b -> Bool -> Bool -> IO ()+doGenerate f pstRef sembox useDebugger pauseOnLex =+ do loadEverything pstRef+ sem <- get sembox text+ loadTargetSemStr pstRef sem+ --+ pst <- readIORef pstRef+ let config = pa pst+ withBuilderGui a =+ case builderType config of+ NullBuilder -> error "No gui available for NullBuilder"+ SimpleBuilder -> a simpleGui_2p+ SimpleOnePhaseBuilder -> a simpleGui_1p+ CkyBuilder -> a ckyGui+ EarleyBuilder -> a earleyGui+ --+ let doDebugger bg = debugGui bg pstRef pauseOnLex+ doResults bg = resultsGui bg pstRef+ do catch (withBuilderGui $ if useDebugger then doDebugger else doResults)+ (handler "Error during realisation")+ -- FIXME: it would be nice to distinguish between generation and ts+ -- parsing errors+ `catch` (handler "Error parsing the input semantics")+ where+ handler title err = errorDialog f title (show err)+\end{code}++\paragraph{resultsGui} displays generation result in a window. The window+consists of various tabs for intermediary results in lexical+selection, derived trees, derivation trees and generation statistics.++\begin{code}+resultsGui :: BG.BuilderGui -> ProgStateRef -> IO ()+resultsGui builderGui pstRef =+ do -- results window+ f <- frame [ text := "Results"+ , fullRepaintOnResize := False+ , layout := stretch $ label "Generating..."+ , clientSize := sz 300 300+ ]+ p <- panel f []+ nb <- notebook p []+ -- realisations tab+ (results,stats,resTab) <- BG.resultsPnl builderGui pstRef nb+ -- statistics tab+ let sentences = (fst . unzip) results+ statTab <- statsGui nb sentences stats+ -- pack it all together+ set f [ layout := container p $ column 0 [ tabs nb+ -- we put the realisations tab last because of what+ -- seems to be buggy behaviour wrt to wxhaskell+ -- or wxWidgets 2.4 and the splitter+ [ tab "summary" statTab+ , tab "realisations" resTab ] ]+ , clientSize := sz 700 600 ]+ return ()+\end{code}++\paragraph{debuggerGui} All GenI builders can make use of an interactive+graphical debugger. We provide here a universal debugging interface,+which makes use of some parameterisable bits as defined in the BuilderGui+module. This window shows a seperate tab for each surface realisation+task (lexical selection, filtering, building). We also rely heavily on+helper code defined in \ref{sec:debugger_helpers}.++\begin{code}+debugGui :: BG.BuilderGui -> ProgStateRef -> Bool -> IO ()+debugGui builderGui pstRef pauseOnLex =+ do pst <- readIORef pstRef+ let config = pa pst+ btype = show $ builderType config+ --+ f <- frame [ text := "GenI Debugger - " ++ btype ++ " edition"+ , fullRepaintOnResize := False+ , clientSize := sz 300 300 ]+ p <- panel f []+ nb <- notebook p []+ -- generation step 1+ initStuff <- initGeni pstRef+ let (tsem,_,_) = B.inSemInput initStuff+ (cand,_) = unzip $ B.inCands initStuff+ lexonly = B.inLex initStuff+ -- continuation for candidate selection tab+ let step2 newCands =+ do -- generation step 2.A (run polarity stuff)+ let newInitStuff = initStuff { B.inCands = map (\x -> (x, -1)) newCands }+ (input2, _, autstuff) = B.preInit newInitStuff config+ -- automata tab+ let (auts, _, finalaut, _) = autstuff+ autPnl <- if hasOpt Polarised config+ then fst3 `fmap` polarityGui nb auts finalaut+ else messageGui nb "polarity filtering disabled"+ -- generation step 2.B (start the generator for each path)+ debugPnl <- BG.debuggerPnl builderGui nb config input2 btype+ let autTab = tab "automata" autPnl+ debugTab = tab (btype ++ "-session") debugPnl+ genTabs = if hasOpt Polarised config+ then [ autTab, debugTab ] else [ debugTab ]+ --+ set f [ layout := container p $ tabs nb genTabs+ , clientSize := sz 700 600 ]+ return ()+ -- candidate selection tab+ let missedSem = tsem \\ (nub $ concatMap tsemantics cand)+ -- we assume that for a tree to correspond to a lexical item,+ -- it must have the same semantics+ hasTree l = isJust $ find (\t -> tsemantics t == lsem) cand+ where lsem = isemantics l+ missedLex = [ l | l <- lexonly, (not.hasTree) l ]+ (canPnl,_,_) <- if pauseOnLex+ then pauseOnLexGui pst nb cand missedSem missedLex step2+ else candidateGui pst nb cand missedSem missedLex+ -- basic tabs+ let basicTabs = [ tab "lexical selection" canPnl ]+ --+ set f [ layout := container p $ tabs nb basicTabs+ , clientSize := sz 700 600 ]+ -- display all tabs if we are not told to pause on lex selection+ when (not pauseOnLex) (step2 cand)+\end{code}+++ +% --------------------------------------------------------------------+\section{Tree browser}+\label{sec:treebrowser_gui}+% --------------------------------------------------------------------++This is a very simple semantically-separated browser for all the+trees in the grammar. Note that we can't just reuse candidateGui's+code because we label and sort the trees differently. Here we +ignore the arguments in tree semantics, and we display the tree+polarities in its label.++\begin{code}+treeBrowserGui :: ProgStateRef -> IO () +treeBrowserGui pstRef = do+ pst <- readIORef pstRef+ -- ALL THE TREES in the grammar... muahahaha!+ let semmap = combine (gr pst) (le pst)+ -- browser window+ f <- frame [ text := "Tree Browser" + , fullRepaintOnResize := False + ] + -- the heavy GUI artillery+ let sem = Map.keys semmap+ --+ lookupTr k = Map.findWithDefault [] k semmap+ treesfor k = Nothing : (map Just $ lookupTr k)+ labsfor k = ("___" ++ k ++ "___") : (map fn $ lookupTr k)+ where fn t = idname t ++ polfn (tpolarities t)+ polfn p = if Map.null p + then "" + else " (" ++ showLitePm p ++ ")"+ --+ trees = concatMap treesfor sem+ itNlabl = zip trees (concatMap labsfor sem)+ (browser,_,_) <- tagViewerGui pst f "tree browser" "grambrowser" itNlabl+ -- the button panel+ let count = length trees - length sem+ quitBt <- button f [ text := "Close", on command := close f ]+ -- pack it all together + set f [ layout := column 5 [ browser, + row 5 [ label ("number of trees: " ++ show count)+ , hfloatRight $ widget quitBt ] ]+ , clientSize := sz 700 600 ]+ return ()+\end{code}
+ src/NLP/GenI/GuiHelper.lhs view
@@ -0,0 +1,860 @@+% GenI surface realiser+% Copyright (C) 2005 Carlos Areces and Eric Kow+%+% This program is free software; you can redistribute it and/or+% modify it under the terms of the GNU General Public License+% as published by the Free Software Foundation; either version 2+% of the License, or (at your option) any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; if not, write to the Free Software+% Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.++\chapter{GUI Helper} ++This module provides helper functions for building the GenI graphical+user interface++\begin{code}+{-# LANGUAGE FlexibleContexts #-}+module NLP.GenI.GuiHelper where+\end{code}++\ignore{+\begin{code}+import Graphics.UI.WX+-- import Graphics.UI.WXCore++import qualified Control.Monad as Monad +import Control.Monad.State ( execStateT, runState )+import qualified Data.Map as Map++import Data.IORef+import Data.List (intersperse)+import System.Directory +import System.FilePath ((<.>),(</>),dropExtensions)+import System.Process (runProcess)+import Text.ParserCombinators.Parsec (parseFromFile)++import NLP.GenI.Graphviz+import NLP.GenI.Automaton (numStates, numTransitions)+import NLP.GenI.Statistics (Statistics, showFinalStats)++import NLP.GenI.Configuration ( getFlagP, MacrosFlg(..), ViewCmdFlg(..) )+import NLP.GenI.GeniShow(geniShow)+import NLP.GenI.GraphvizShow ()+import NLP.GenI.Tags (TagItem(tgIdName), tagLeaves)+import NLP.GenI.Geni+ ( ProgState(..), showRealisations )+import NLP.GenI.GeniParsers ( geniTagElems )+import NLP.GenI.General+ (geniBug, boundsCheck, dropTillIncluding, ePutStrLn)+import NLP.GenI.Btypes+ ( showAv, showPred, showSem, showLexeme, Sem, ILexEntry(iword, ifamname), )+import NLP.GenI.Tags+ ( idname, mapBySem, TagElem(ttrace, tinterface) )++import NLP.GenI.Configuration+ ( Params(..), MetricsFlg(..), setFlagP )++import qualified NLP.GenI.Builder as B+import NLP.GenI.Builder (queryCounter, num_iterations, chart_size,+ num_comparisons)+import NLP.GenI.Polarity (PolAut, detectPolFeatures)+import NLP.GenI.GraphvizShowPolarity ()+\end{code}+}++\subsection{Lexically selected items}++We have a browser for the lexically selected items. We group the lexically+selected items by the semantics they subsume, inserting along the way some+fake trees and labels for the semantics.++The arguments \fnparam{missedSem} and \fnparam{missedLex} are used to +indicate to the user respectively if any bits of the input semantics+have not been accounted for, or if there have been lexically selected+items for which no tree has been found.++\begin{code}+candidateGui :: ProgState -> (Window a) -> [TagElem] -> Sem -> [ILexEntry]+ -> GvIO Bool (Maybe TagElem)+candidateGui pst f xs missedSem missedLex = do+ p <- panel f [] + (tb,gvRef,updater) <- tagViewerGui pst p "lexically selected item" "candidates"+ $ sectionsBySem xs+ let warningSem = if null missedSem then ""+ else "WARNING: no lexical selection for " ++ showSem missedSem+ warningLex = if null missedLex then ""+ else "WARNING: '" ++ (concat $ intersperse ", " $ map showLex missedLex)+ ++ "' were lexically selected, but are not anchored to"+ ++ " any trees"+ where showLex l = (showLexeme $ iword l) ++ "-" ++ (ifamname l)+ --+ polFeats = "Polarity attributes detected: " ++ (unwords.detectPolFeatures) xs+ warning = unlines $ filter (not.null) [ warningSem, warningLex, polFeats ]+ -- side panel+ sidePnl <- panel p []+ ifaceLst <- singleListBox sidePnl [ tooltip := "interface for this tree (double-click me!)" ]+ traceLst <- singleListBox sidePnl [ tooltip := "trace for this tree (double-click me!)" ]+ tNoted <- textCtrl sidePnl [ wrap := WrapWord, text := "Hint: copy from below and paste into the sem:\n" ]+ let laySide = container sidePnl $ column 2+ [ label "interface"+ , fill $ widget ifaceLst+ , label "trace"+ , fill $ widget traceLst+ , label "notes"+ , fill $ widget tNoted ]+ -- handlers+ let addLine :: String -> String -> String+ addLine x y = y ++ "\n" ++ x+ --+ addToNoted w =+ do sel <- get w selection+ things <- get w items+ when (sel > 0) $ set tNoted [ text :~ addLine (things !! sel) ]+ set ifaceLst [ on doubleClick := \_ -> addToNoted ifaceLst ]+ set traceLst [ on doubleClick := \_ -> addToNoted traceLst ]+ -- updaters : what happens when the user selects an item+ let updateTrace = gvOnSelect (return ())+ (\s -> set traceLst [ items := ttrace s ])+ updateIface = gvOnSelect (return ())+ (\s -> set ifaceLst [ items := map showAv $ tinterface s ])+ Monad.unless (null xs) $ do+ addGvHandler gvRef updateTrace+ addGvHandler gvRef updateIface+ -- first time run+ gvSt <- readIORef gvRef+ updateIface gvSt+ updateTrace gvSt+ --+ let layMain = fill $ row 2 [ fill tb, vfill laySide ]+ theItems = if null warning then [ layMain ] else [ hfill (label warning) , layMain ]+ lay = fill $ container p $ column 5 theItems+ return (lay, gvRef, updater)++sectionsBySem :: (TagItem t) => [t] -> [ (Maybe t, String) ]+sectionsBySem tsem =+ let semmap = mapBySem tsem+ sem = Map.keys semmap+ --+ lookupTr k = Map.findWithDefault [] k semmap+ section k = (Nothing, header) : (map tlab $ lookupTr k)+ where header = "___" ++ showPred k ++ "___"+ tlab t = (Just t, tgIdName t)+ in concatMap section sem+\end{code}+ +\subsection{Polarity Automata}++A browser to see the automata constructed during the polarity optimisation+step.++\begin{code}+polarityGui :: (Window a) -> [(String,PolAut,PolAut)] -> PolAut+ -> GvIO () PolAut+polarityGui f xs final = do+ let stats a = " (" ++ (show $ numStates a) ++ "st " ++ (show $ numTransitions a) ++ "tr)"+ aut2 (_ , a1, a2) = [ a1, a2 ]+ autLabel (fv,a1,a2) = [ fv ++ stats a1, fv ++ " pruned" ++ stats a2]+ autlist = (concatMap aut2 xs) ++ [ final ]+ labels = (concatMap autLabel xs) ++ [ "final" ++ stats final ]+ --+ gvRef <- newGvRef () labels "automata"+ setGvDrawables gvRef autlist+ graphvizGui f "polarity" gvRef+\end{code}+ +\paragraph{statsGui} displays the generation statistics and provides a+handy button for saving results to a text file.++\begin{code}+statsGui :: (Window a) -> [String] -> Statistics -> IO Layout+statsGui f sentences stats =+ do let msg = showRealisations sentences+ --+ p <- panel f []+ t <- textCtrl p [ text := msg, enabled := False ]+ statsTxt <- staticText p [ text := showFinalStats stats ]+ --+ saveBt <- button p [ text := "Save to file"+ , on command := maybeSaveAsFile f msg ]+ return $ fill $ container p $ column 1 $+ [ hfill $ label "Performance data"+ , hfill $ widget statsTxt+ , hfill $ label "Realisations"+ , fill $ widget t+ , hfloatRight $ widget saveBt ]+\end{code}++\subsection{TAG trees}++Our graphical interfaces have to display a great variety of items. To+keep things nicely factorised, we define some type classes to describe+the things that these items may have in common.++\begin{code}+-- | Any data structure which has corresponds to a TAG tree and which+-- has some notion of derivation+class XMGDerivation a where+ getSourceTrees :: a -> [String]++instance XMGDerivation TagElem where+ getSourceTrees te = [idname te]+\end{code}++\fnlabel{toSentence} almost displays a TagElem as a sentence, but only+good enough for debugging needs. The problem is that each leaf may be+an atomic disjunction. Our solution is just to display each choice and+use some delimiter to seperate them. We also do not do any+morphological processing.++\begin{code}+toSentence :: TagElem -> String+toSentence = unwords . map squishLeaf . tagLeaves++squishLeaf :: (a,([String], b)) -> String+squishLeaf = showLexeme.fst.snd+\end{code}++\subsection{TAG viewer}++A TAG viewer is a graphvizGui that lets the user toggle the display+of TAG feature structures.++\begin{code}+tagViewerGui :: (GraphvizShow Bool t, TagItem t, XMGDerivation t)+ => ProgState -> (Window a) -> String -> String -> [(Maybe t,String)]+ -> GvIO Bool (Maybe t)+tagViewerGui pst f tip cachedir itNlab = do+ p <- panel f [] + let (tagelems,labels) = unzip itNlab+ gvRef <- newGvRef False labels tip+ setGvDrawables gvRef tagelems + (lay,ref,updaterFn) <- graphvizGui p cachedir gvRef+ -- button bar widgets+ detailsChk <- checkBox p [ text := "Show features"+ , checked := False ]+ viewTagLay <- viewTagWidgets p gvRef (pa pst)+ -- handlers+ let onDetailsChk =+ do isDetailed <- get detailsChk checked+ setGvParams gvRef isDetailed+ updaterFn+ set detailsChk [ on command := onDetailsChk ]+ -- pack it all in + let cmdBar = hfill $ row 5 + [ dynamic $ widget detailsChk+ , viewTagLay ]+ lay2 = fill $ container p $ column 5 [ fill lay, cmdBar ]+ return (lay2,ref,updaterFn)+\end{code}++\subsection{XMG Metagrammar stuff}++XMG trees are produced by the XMG metagrammar system+(\url{http://sourcesup.cru.fr/xmg/}). To debug these grammars, it is useful,+given a TAG tree, to see what its metagrammar origins are. We provide here an+interface to Yannick Parmentier's handy visualisation tool ViewTAG.++\begin{code}+viewTagWidgets :: (GraphvizShow Bool t, TagItem t, XMGDerivation t)+ => Window a -> GraphvizRef (Maybe t) Bool -> Params+ -> IO Layout+viewTagWidgets p gvRef config =+ do viewTagBtn <- button p [ text := "ViewTAG" ]+ viewTagCom <- choice p [ tooltip := "derivation tree" ]+ -- handlers+ let onViewTag = readIORef gvRef >>=+ gvOnSelect (return ())+ (\t -> do let derv = getSourceTrees t+ ds <- get viewTagCom selection+ if boundsCheck ds derv+ then runViewTag config (derv !! ds)+ else geniBug $ "Gui: bounds check in onViewTag"+ )+ set viewTagBtn [ on command := onViewTag ]+ -- when the user selects a tree, we want to update the list of derivations+ let updateDerivationList = gvOnSelect+ (set viewTagCom [ enabled := False ])+ (\s -> set viewTagCom [ enabled := True+ , items := getSourceTrees s+ , selection := 0] )+ addGvHandler gvRef updateDerivationList+ updateDerivationList =<< readIORef gvRef+ --+ return $ row 5 $ map dynamic [ widget viewTagCom, widget viewTagBtn ]++runViewTag :: Params -> String -> IO ()+runViewTag params drName =+ case getFlagP MacrosFlg params of+ Nothing -> ePutStrLn "Warning: No macros files specified (runViewTag)"+ Just f -> do+ -- figure out what grammar file to use+ let gramfile = dropExtensions f <.> "rec"+ treenameOnly = takeWhile (/= ':') . dropTillIncluding ':' . dropTillIncluding ':'+ -- run the viewer+ case getFlagP ViewCmdFlg params of+ Nothing -> ePutStrLn "Warning: No viewcmd specified (runViewTag)"+ Just c -> do -- run the viewer+ runProcess c [gramfile, treenameOnly drName]+ Nothing Nothing Nothing Nothing Nothing+ return ()+\end{code}++% --------------------------------------------------------------------+\section{Graphical debugger}+\label{sec:debugger_helpers}+% --------------------------------------------------------------------++All GenI builders can make use of an interactive graphical debugger. In+this section, we provide some helper code to build such a debugger. ++\paragraph{pauseOnLexGui} sometimes it is useful for the user to see the+lexical selection only and either dump it to file or read replace it by+the contents of some other file. We provide an optional wrapper around+\fnref{candidateGui} which adds this extra functionality. The wrapper+also includes a "Begin" button which runs your continuation on the new+lexical selection.++\begin{code}+pauseOnLexGui :: ProgState -> (Window a) -> [TagElem] -> Sem -> [ILexEntry]+ -> ([TagElem] -> IO ()) -- ^ continuation+ -> GvIO Bool (Maybe TagElem)+pauseOnLexGui pst f xs missedSem missedLex job = do+ p <- panel f []+ candV <- varCreate xs+ (tb, ref, updater) <- candidateGui pst p xs missedSem missedLex+ -- supplementary button bar+ let saveCmd =+ do c <- varGet candV+ let cStr = unlines $ map geniShow c+ maybeSaveAsFile f cStr+ loadCmd =+ do let filetypes = [("Any file",["*","*.*"])]+ fsel <- fileOpenDialog f False True "Choose your file..." filetypes "" ""+ case fsel of+ Nothing -> return ()+ Just file ->+ do parsed <- parseFromFile geniTagElems file+ case parsed of+ Left err -> errorDialog f "" (show err)+ Right c -> do varSet candV c+ setGvDrawables2 ref (sectionsBySem c)+ updater+ --+ saveBt <- button p [ text := "Save to file", on command := saveCmd ]+ loadBt <- button p [ text := "Load from file", on command := loadCmd ]+ nextBt <- button p [ text := "Begin" ]+ let disableW w = set w [ enabled := False ]+ set nextBt [ on command := do mapM disableW [ saveBt, loadBt, nextBt ]+ varGet candV >>= job ]+ --+ let lay = fill $ container p $ column 5+ [ fill tb, hfill (vrule 1)+ , row 0 [ row 5 [ widget saveBt, widget loadBt ]+ , hfloatRight $ widget nextBt ] ]+ return (lay, ref, updater)+\end{code}++\paragraph{debuggerTab} is potentially the most useful part of the+debugger. It shows you the contents of chart, agenda and other+such structures used during the actual surface realisation process.+This may be a bit complicated to use because there is lots of extra+stuff you need to pass in order to parameterise the whole deal.++The function \fnreflite{debuggerTab} fills the parent window with the+standard components of a graphical debugger:+\begin{itemize}+\item An item viewer which allows the user to select one of the items+ in the builder state.+\item An item bar which provides some options on how to view the + currently selected item, for example, if you want to display the+ features or not. +\item A dashboard which lets the user do things like ``go ahead 6+ steps''.+\end{itemize}++See the API for more details.++\begin{code}+type DebuggerItemBar flg itm + = (Panel ()) -- ^ parent panel+ -> GraphvizRef (Maybe itm) flg + -- ^ gv ref to use+ -> GvUpdater -- ^ updaterFn+ -> IO Layout++-- | A generic graphical debugger widget for GenI+-- +-- Besides the Builder, there are two functions you need to pass in make this+-- work: +--+-- 1. a 'stateToGv' which converts the builder state into a list of items+-- and labels the way 'graphvizGui' likes it+--+-- 2. an 'item bar' function which lets you control what bits you display+-- of a selected item (for example, if you want a detailed view or not)+-- the item bar should return a layout +--+-- Note that we don't constrain the type of item returned by the builder to+-- be the same as the type handled by your gui: that's quite normal because+-- you might want to decorate the type with some other information+debuggerPanel :: (GraphvizShow flg itm) + => B.Builder st itm2 Params -- ^ builder to use+ -> flg -- ^ initial value for the flag argument in GraphvizShow+ -> (st -> [(Maybe itm, String)])+ -- ^ function to convert a Builder state into lists of items+ -- and their labels, the way graphvizGui likes it+ -> (DebuggerItemBar flg itm)+ -- ^ 'itemBar' function returning a control panel configuring+ -- how you want the currently selected item in the debugger+ -- to be displayed+ -> (Window a) -- ^ parent window+ -> Params -- ^ geni params+ -> B.Input -- ^ builder input+ -> String -- ^ graphviz cache directory+ -> IO Layout +debuggerPanel builder gvInitial stateToGv itemBar f config input cachedir = + do let initBuilder = B.init builder + nextStep = B.step builder + allSteps = B.stepAll builder + --+ let (initS, initStats) = initBuilder input config2+ config2 = setFlagP MetricsFlg (B.defaultMetricNames) config+ (theItems,labels) = unzip $ stateToGv initS+ p <- panel f [] + -- ---------------------------------------------------------+ -- item viewer: select and display an item+ -- ---------------------------------------------------------+ gvRef <- newGvRef gvInitial labels "debugger session" + setGvDrawables gvRef theItems+ (layItemViewer,_,updaterFn) <- graphvizGui p cachedir gvRef+ -- ----------------------------------------------------------+ -- item bar: controls for how an individual item is displayed+ -- ----------------------------------------------------------+ layItemBar <- itemBar p gvRef updaterFn+ -- ------------------------------------------- + -- dashboard: controls for the debugger itself + -- ------------------------------------------- + db <- panel p []+ restartBt <- button db [text := "Start over"]+ nextBt <- button db [text := "Step by..."]+ leapVal <- entry db [ text := "1", clientSize := sz 30 25 ]+ finishBt <- button db [text := "Leap to end"]+ statsTxt <- staticText db []+ -- dashboard commands+ let showQuery c gs = case queryCounter c gs of+ Nothing -> "???"+ Just q -> show q+ updateStatsTxt gs = set statsTxt [ text :~ (\_ -> txtStats gs) ]+ txtStats gs = "itr " ++ (showQuery num_iterations gs) ++ " "+ ++ "chart sz: " ++ (showQuery chart_size gs)+ ++ "\ncomparisons: " ++ (showQuery num_comparisons gs)+ let genStep _ (st,stats) = runState (execStateT nextStep st) stats+ let showNext s_stats = + do leapTxt <- get leapVal text+ let leapInt :: Integer+ leapInt = read leapTxt+ (s2,stats2) = foldr genStep s_stats [1..leapInt]+ setGvDrawables2 gvRef (stateToGv s2)+ setGvSel gvRef 1+ updaterFn+ updateStatsTxt stats2+ set nextBt [ on command :~ (\_ -> showNext (s2,stats2) ) ]+ let showLast = + do -- redo generation from scratch+ let (s2, stats2) = runState (execStateT allSteps initS) initStats + setGvDrawables2 gvRef (stateToGv s2)+ updaterFn+ updateStatsTxt stats2+ let showReset = + do set nextBt [ on command := showNext (initS, initStats) ]+ updateStatsTxt initStats + setGvDrawables2 gvRef (stateToGv initS)+ setGvSel gvRef 1+ updaterFn+ -- dashboard handlers+ set finishBt [ on command := showLast ]+ set restartBt [ on command := showReset ]+ showReset+ -- dashboard layout + let layCmdBar = hfill $ container db $ row 5+ [ widget statsTxt, hfloatRight $ row 5 + [ widget restartBt, widget nextBt + , widget leapVal, label " step(s)"+ , widget finishBt ] ]+ -- ------------------------------------------- + -- overall layout+ -- ------------------------------------------- + return $ fill $ container p $ column 5 [ layItemViewer, layItemBar, hfill (vrule 1), layCmdBar ] +\end{code}++% --------------------------------------------------------------------+\section{Graphviz GUI}+\label{sec:graphviz_gui}+% --------------------------------------------------------------------++A general-purpose GUI for displaying a list of items graphically via+AT\&T's excellent Graphviz utility. We have a list box where we display+all the labels the user provided. If the user selects an entry from+this box, then the item corresponding to that label will be displayed.+See section \ref{sec:draw_item}.++\paragraph{gvRef}++We use IORef as a way to keep track of the gui state and to provide you+the possibility for modifying the contents of the GUI. The idea is that ++\begin{enumerate}+\item you create a GvRef with newGvRef+\item you call graphvizGui and get back an updater function+\item whenever you want to modify something, you use setGvWhatever+ and call the updater function+\item if you want to react to the selection being changed,+ you should set gvhandler+\end{enumerate}++\begin{code}+data GraphvizOrder = GvoParams | GvoItems | GvoSel + deriving Eq+data GraphvizGuiSt a b = + GvSt { gvitems :: Map.Map Int a,+ gvparams :: b,+ gvlabels :: [String],+ -- tooltip for the selection box+ gvtip :: String, + -- handler function to call when the selection is+ -- updated (note: before displaying the object)+ gvhandler :: Maybe (GraphvizGuiSt a b -> IO ()),+ gvsel :: Int,+ gvorders :: [GraphvizOrder] }+type GraphvizRef a b = IORef (GraphvizGuiSt a b)++newGvRef :: forall a . forall b . b -> [String] -> String -> IO (GraphvizRef a b)+newGvRef p l t =+ let st = GvSt { gvparams = p,+ gvitems = Map.empty,+ gvlabels = l, + gvhandler = Nothing,+ gvtip = t,+ gvsel = 0,+ gvorders = [] }+ in newIORef st++setGvSel :: GraphvizRef a b -> Int -> IO ()+setGvSel gvref s =+ do let fn x = x { gvsel = s,+ gvorders = GvoSel : (gvorders x) }+ modifyIORef gvref fn + +setGvParams :: GraphvizRef a b -> b -> IO ()+setGvParams gvref c =+ do let fn x = x { gvparams = c,+ gvorders = GvoParams : (gvorders x) }+ modifyIORef gvref fn ++modifyGvParams :: GraphvizRef a b -> (b -> b) -> IO ()+modifyGvParams gvref fn =+ do gvSt <- readIORef gvref+ setGvParams gvref (fn $ gvparams gvSt)++setGvDrawables :: GraphvizRef a b -> [a] -> IO ()+setGvDrawables gvref it =+ do let fn x = x { gvitems = Map.fromList $ zip [0..] it,+ gvorders = GvoItems : (gvorders x) }+ modifyIORef gvref fn ++setGvDrawables2 :: GraphvizRef a b -> [(a,String)] -> IO ()+setGvDrawables2 gvref itlb =+ do let (it,lb) = unzip itlb+ fn x = x { gvlabels = lb }+ modifyIORef gvref fn + setGvDrawables gvref it++-- | Helper function for making selection handlers (see 'addGvHandler')+-- Note that this was designed for cases where the contents is a Maybe+gvOnSelect :: IO () -> (a -> IO ()) -> GraphvizGuiSt (Maybe a) b -> IO ()+gvOnSelect onNothing onJust gvSt =+ let sel = gvsel gvSt+ things = gvitems gvSt+ in case Map.lookup sel things of+ Just (Just s) -> onJust s+ _ -> onNothing++setGvHandler :: GraphvizRef a b -> Maybe (GraphvizGuiSt a b -> IO ()) -> IO ()+setGvHandler gvref mh =+ do gvSt <- readIORef gvref+ modifyIORef gvref (\x -> x { gvhandler = mh })+ case mh of + Nothing -> return ()+ Just fn -> fn gvSt++-- | add a selection handler - if there already is a handler+-- this handler will be called before the new one+addGvHandler :: GraphvizRef a b -> (GraphvizGuiSt a b -> IO ()) -> IO ()+addGvHandler gvref h =+ do gvSt <- readIORef gvref+ let newH = case gvhandler gvSt of + Nothing -> Just h+ Just oldH -> Just (\g -> oldH g >> h g)+ setGvHandler gvref newH+\end{code}++\paragraph{graphvizGui} returns a layout (wxhaskell container) and a+function for updating the contents of this GUI.++Arguments:+\begin{enumerate}+\item f - (parent window) the GUI is provided as a panel within the parent.+ Note: we use window in the WxWidget's sense, meaning it could be+ anything as simple as a another panel, or a notebook tab.+\item glab - (gui labels) a tuple of strings (tooltip, next button text)+\item cachedir - the cache subdirectory. We intialise this by creating a cache+ directory for images which will be generated from the results+\item gvRef - see above+\end{enumerate}++Returns: a function for updating the GUI. FIXME: it's not entirely clear+what the updater function is for; note that it's not the same as the +handler function!++\begin{code}+graphvizGui :: (GraphvizShow f d) => + (Window a) -> String -> GraphvizRef d f -> GvIO f d+type GvIO f d = IO (Layout, GraphvizRef d f, GvUpdater)+type GvUpdater = IO ()++graphvizGui f cachedir gvRef = do+ initGvSt <- readIORef gvRef+ -- widgets+ p <- panel f [ fullRepaintOnResize := False ]+ split <- splitterWindow p []+ (dtBitmap,sw) <- scrolledBitmap split + rchoice <- singleListBox split [tooltip := gvtip initGvSt]+ -- set handlers+ let openFn = openImage sw dtBitmap + -- pack it all together+ let lay = fill $ container p $ margin 1 $ fill $ + vsplit split 5 200 (widget rchoice) (widget sw) + set p [ on closing := closeImage dtBitmap ]+ -- bind an action to rchoice+ let showItem = do createAndOpenImage cachedir p gvRef openFn+ `catch` \e -> errorDialog f "" (show e)+ ------------------------------------------------+ -- create an updater function+ ------------------------------------------------+ let updaterFn = do + gvSt <- readIORef gvRef+ let orders = gvorders gvSt + labels = gvlabels gvSt+ sel = gvsel gvSt+ initCacheDir cachedir + Monad.when (GvoItems `elem` orders) $ + set rchoice [ items :~ (\_ -> labels) ]+ Monad.when (GvoSel `elem` orders) $+ set rchoice [ selection :~ (\_ -> sel) ]+ modifyIORef gvRef (\x -> x { gvorders = []})+ -- putStrLn "updaterFn called" + showItem + ------------------------------------------------+ -- enable the tree selector+ -- FIXME: not sure that this is correct+ ------------------------------------------------+ let selectAndShow = do+ -- putStrLn "selectAndShow called" + sel <- get rchoice selection+ -- note: do not use setGvSel (infinite loop)+ modifyIORef gvRef (\x -> x { gvsel = sel })+ -- call the handler if there is one + gvSt <- readIORef gvRef+ case (gvhandler gvSt) of + Nothing -> return ()+ Just h -> h gvSt+ -- now do the update+ updaterFn+ ------------------------------------------------+ set rchoice [ on select := selectAndShow ]+ -- call the updater function for the first time+ -- setGvSel gvRef 1+ updaterFn + -- return the layout, the gvRef, and an updater function+ -- The gvRef is to make it easier for users to muck around with the+ -- state of the gui. Here, it's trivial, but when people combine guis+ -- together, it might be easier to keep track of when returned+ return (lay, gvRef, updaterFn)+\end{code}++\subsection{Scroll bitmap}++Bitmap with a scrollbar++\begin{code}+scrolledBitmap :: Window a -> IO(VarBitmap, ScrolledWindow ())+scrolledBitmap p = do+ dtBitmap <- variable [value := Nothing]+ sw <- scrolledWindow p [scrollRate := sz 10 10, bgcolor := white,+ on paint := onPaint dtBitmap,+ fullRepaintOnResize := False ] + return (dtBitmap, sw)+\end{code}++\subsection{Bitmap functions}++The following helper functions were taken directly from the WxHaskell+sample code.++\begin{code}+type OpenImageFn = FilePath -> IO ()+type VarBitmap = Var (Maybe (Bitmap ())) ++openImage :: Window a -> VarBitmap -> OpenImageFn+openImage sw vbitmap fname = do + -- load the new bitmap+ bm <- bitmapCreateFromFile fname -- can fail with exception+ closeImage vbitmap+ set vbitmap [value := Just bm]+ -- reset the scrollbars + bmsize <- get bm size + set sw [virtualSize := bmsize]+ repaint sw+ `catch` \_ -> repaint sw++closeImage :: VarBitmap -> IO ()+closeImage vbitmap = do + mbBitmap <- swap vbitmap value Nothing+ case mbBitmap of+ Nothing -> return ()+ Just bm -> objectDelete bm++onPaint :: VarBitmap -> DC a -> b -> IO ()+onPaint vbitmap dc _ = do + mbBitmap <- get vbitmap value+ case mbBitmap of+ Nothing -> return () + Just bm -> do dcClear dc+ drawBitmap dc bm pointZero False []+\end{code}++\subsection{Drawing stuff}+\label{sec:draw_item}++\paragraph{createAndOpenImage} Attempts to draw an image +(or retrieve it from cache) and opens it if we succeed. Otherwise, it+does nothing at all; the creation function will display an error message+if it fails.++\begin{code}+createAndOpenImage :: (GraphvizShow f b) => + FilePath -> Window a -> GraphvizRef b f -> OpenImageFn -> IO ()+createAndOpenImage cachedir f gvref openFn = do + let errormsg g = "The file " ++ g ++ " was not created!\n"+ ++ "Is graphviz installed?"+ r <- createImage cachedir f gvref + case r of + Just graphic -> do exists <- doesFileExist graphic + if exists + then openFn graphic+ else fail (errormsg graphic)+ Nothing -> return ()++-- | Creates a graphical visualisation for anything which can be displayed+-- by graphviz.+createImage :: (GraphvizShow f b)+ => FilePath -- ^ cache directory+ -> Window a -- ^ parent window+ -> GraphvizRef b f -- ^ stuff to display+ -> IO (Maybe FilePath)+createImage cachedir f gvref = do+ gvSt <- readIORef gvref+ -- putStrLn $ "creating image via graphviz"+ let drawables = gvitems gvSt+ sel = gvsel gvSt+ config = gvparams gvSt+ dotFile <- createDotPath cachedir (show sel)+ graphicFile <- createImagePath cachedir (show sel)+ let create x = do toGraphviz config x dotFile graphicFile+ return (Just graphicFile)+ handler err = do errorDialog f "Error calling graphviz" (show err) + return Nothing+ exists <- doesFileExist graphicFile+ -- we only call graphviz if the image is not in the cache+ if exists+ then return (Just graphicFile)+ else case Map.lookup sel drawables of+ Nothing -> return Nothing+ Just it -> create it `catch` handler+\end{code}++\subsection{Cache directory}++We create a directory to put image files in so that we can avoid regenerating+images. If the directory already exists, we can just delete all the files+in it.++\begin{code}+initCacheDir :: String -> IO()+initCacheDir cachesubdir = do + mainCacheDir <- gv_CACHEDIR+ cmainExists <- doesDirectoryExist mainCacheDir + Monad.when (not cmainExists) $ createDirectory mainCacheDir + -- + let cachedir = mainCacheDir </> cachesubdir+ cExists <- doesDirectoryExist cachedir+ if (cExists)+ then do let notdot x = (x /= "." && x /= "..")+ contents <- getDirectoryContents cachedir+ olddir <- getCurrentDirectory+ setCurrentDirectory cachedir+ mapM removeFile $ filter notdot contents+ setCurrentDirectory olddir+ return ()+ else createDirectory cachedir+\end{code}++\section{Miscellaneous}+\label{sec:gui_misc}++\begin{code}+-- | Save the given string to a file, if the user selets one via the file save+-- dialog. Otherwise, don't do anything.+maybeSaveAsFile :: (Window a) -> String -> IO ()+maybeSaveAsFile f msg =+ do let filetypes = [("Any file",["*","*.*"])]+ fsel <- fileSaveDialog f False True "Save to" filetypes "" ""+ case fsel of+ Nothing -> return ()+ Just file -> writeFile file msg++-- | A message panel for use by the Results gui panels.+messageGui :: (Window a) -> String -> IO Layout +messageGui f msg = do + p <- panel f []+ -- sw <- scrolledWindow p [scrollRate := sz 10 10 ]+ t <- textCtrl p [ text := msg, enabled := False ]+ return (fill $ container p $ column 1 $ [ fill $ widget t ]) +\end{code}++\begin{code}+gv_CACHEDIR :: IO String+gv_CACHEDIR = do+ home <- getHomeDirectory+ return $ home </> ".gvcache"++createImagePath :: String -> String -> IO String+createImagePath subdir name = do+ cdir <- gv_CACHEDIR+ return $ cdir </> subdir </> name <.> "png"++createDotPath :: String -> String -> IO String+createDotPath subdir name = do + cdir <- gv_CACHEDIR+ return $ cdir </> subdir </> name <.> "dot"+\end{code}++
+ src/NLP/GenI/Morphology.lhs view
@@ -0,0 +1,218 @@+% GenI surface realiser+% Copyright (C) 2005 Carlos Areces and Eric Kow+%+% This program is free software; you can redistribute it and/or+% modify it under the terms of the GNU General Public License+% as published by the Free Software Foundation; either version 2+% of the License, or (at your option) any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; if not, write to the Free Software+% Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.++\chapter{Morphology}+\label{cha:Morphology}++This module handles mostly everything to do with morphology in Geni.+There are two basic tasks: morphological input and output. +GenI farms out morphology to whatever third party program you+specify in the configuration file.++\begin{code}+module NLP.GenI.Morphology where+\end{code}++\ignore{+\begin{code}+import Data.Maybe (isNothing, isJust)+import Data.List (intersperse)+import Data.Tree+import qualified Data.Map as Map+import System.IO+import System.Process++import NLP.GenI.Btypes+import NLP.GenI.General+import NLP.GenI.Tags+\end{code}+}++\begin{code}+type MorphFn = Pred -> Maybe Flist+\end{code}++\section{Input}++Morphological input means attaching morphological features on trees. The+user specifies morphological input through the input semantics. Our job+is to identify morphological predicates like \semexpr{plural(x)} and +apply features like \fs{\it num:pl} on the relevant trees.++\begin{code}+-- | Converts information from a morphological information file into GenI's+-- internal format.+readMorph :: [(String,[AvPair])] -> MorphFn+readMorph minfo pred_ = Map.lookup key fm+ where fm = Map.fromList minfo+ key = show $ snd3 pred_++-- | Filters away from an input semantics any literals whose realisation is+-- strictly morphological. The first argument tells us helps identify the+-- morphological literals -- it associates literals with morphological stuff;+-- if it returns 'Nothing', then it is non-morphological+stripMorphSem :: MorphFn -> Sem -> Sem+stripMorphSem morphfn tsem = + [ l | l <- tsem, (isNothing.morphfn) l ]++-- | 'attachMorph' @morphfn sem cands@ does the bulk of the morphological+-- input processing. We use @morphfn@ to determine which literals in+-- @sem@ contain morphological information and what information they contain.+-- Then we attach this morphological information to the relevant trees in+-- @cand@. A tree is considered relevant w.r.t to a morphological+-- literal if its semantics contains at least one literal whose first index+-- is the same as the first index of the morphological literal.+attachMorph :: MorphFn -> Sem -> [TagElem] -> [TagElem]+attachMorph morphfn sem cands = + let -- relevance of a tree wrt to an index+ relTree i = not.null.relfilt.tsemantics+ where relfilt = filter (relLit i) + relLit i l = if null args then False else (head args == i)+ where args = thd3 l+ -- perform the attachment for a tree if it is relevant+ attachHelper :: GeniVal -> Flist -> TagElem -> TagElem + attachHelper i mfs t = + if relTree i t then attachMorphHelper mfs t else t + -- perform all attachments for a literal+ attach :: Pred -> [TagElem] -> [TagElem]+ attach l cs = + case morphfn l of + Nothing -> cs+ Just mfs -> map (attachHelper i mfs) cs+ where i = if null args then GAnon else head args+ args = thd3 l + in foldr attach cands sem ++-- | Actually unify the morphological features into the anchor node+--+-- FIXME: we'll need to make sure this still works as promised +-- when we implement co-anchors.+attachMorphHelper :: Flist -> TagElem -> TagElem+attachMorphHelper mfs te = + let -- unification with anchor+ tt = ttree te + anchor = head $ filterTree fn tt+ where fn a = (ganchor a && gtype a == Lex)+ in case unifyFeat mfs (gup anchor) of+ Nothing -> error ("Morphological unification failure on " ++ idname te)+ Just (unf,subst) ->+ let -- perform replacements+ te2 = replace subst te + tt2 = ttree te2+ -- replace the anchor with the unification results+ newgdown = replace subst (gdown anchor) + newa = anchor { gup = unf, gdown = newgdown }+ in te2 { ttree = setMorphAnchor newa tt2 }++setMorphAnchor :: GNode -> Tree GNode -> Tree GNode+setMorphAnchor n t =+ let filt (Node a _) = (gtype a == Lex && ganchor a)+ fn (Node _ l) = Node n l+ in (head.fst) $ listRepNode fn filt [t]+\end{code}++\section{Output}++Output (\jargon{morphological generation}) refers to the actual process+of converting lemmas and morphological information into inflected forms.+We do this by calling some third party software specified by the user.++The morphological software must accept on stdin a newline delimited list+of lemmas and features, with \verb$----$ (four hyphens) as an intersentence+delimiter:++\begin{verbatim}+le [num:sg gen:f]+fille [num:sg]+detester [num:sg tense:past]+le [num:pl gen:m]+garcon [num:pl]+---- []+ce []+etre []+le [num:pl]+garcon [num:pl]+que []+le [num:sg gen:f]+fille [num:sg] +detester [num:sg tense:past]+\end{verbatim}++It must return inflected forms on stdout, \emph{sentences} delimited by+newlines. Note also that we expect exactly one result for every input.+Notice that the morphological generator can choose to delete+spaces or do other orthographical tricks in between words:++\begin{verbatim}+la fille detestait les garcons+c'est les garcons que la fille detestait+\end{verbatim}++If your morphological software does not do this, you could wrap it+with a simple shell or Perl script.++\begin{code}+-- | Extracts the lemmas from a list of uninflected sentences. This is used+-- when the morphological generator is unavailable, doesn't work, etc.+sansMorph :: [(String,Flist)] -> [String]+sansMorph = singleton . unwords . (map fst)++type MorphLexicon = [(String, String, Flist)]+type UninflectedDisjunction = (String, Flist)++-- | Return a list of results for each sentence+inflectSentencesUsingLex :: MorphLexicon -> [[UninflectedDisjunction]] -> [[String]]+inflectSentencesUsingLex mlex = map (inflectSentenceUsingLex mlex)++inflectSentenceUsingLex :: MorphLexicon -> [UninflectedDisjunction] -> [String]+inflectSentenceUsingLex mlex = map unwords . mapM (inflectWordUsingLex mlex)++-- | Return only n matches, but note any excessive ambiguities or missing matches+inflectWordUsingLex :: MorphLexicon -> UninflectedDisjunction -> [String]+inflectWordUsingLex mlex (lem,fs)+ | null matches = [ lem ++ "-" ] -- no matches = lemma plus little icon+ | length matches > 2 = [ lem ++ "*" ] -- too many matches!+ | otherwise = matches+ where+ matches = [ word | (word, mLem, mFs) <- mlex, lem == mLem, isJust $ fs `unifyFeat` mFs ]++-- | Converts a list of uninflected sentences into inflected ones by calling+--- the third party software.+-- FIXME: this doesn't actually support lists-of-results per input+-- will need to work it out+inflectSentencesUsingCmd :: String -> [[UninflectedDisjunction]] -> IO [[String]]+inflectSentencesUsingCmd morphcmd sentences =+ do -- add intersential delimiters+ let delim = [("----",[])]+ morphlst = concat (intersperse delim sentences)+ -- format the stuff as input to the inflector+ let fn (lem,fs) = lem ++ " " ++ showFlist fs+ order = unlines $ map fn morphlst + -- run the inflector+ (toP, fromP, _, pid) <- runInteractiveCommand morphcmd+ hPutStrLn toP order+ hClose toP+ waitForProcess pid + -- read the inflector output back as a list of strings+ (map (singleton . trim) . lines) `fmap` hGetContents fromP+ `catch` \e -> do ePutStrLn "Error calling morphological generator"+ ePutStrLn $ show e+ return $ map sansMorph sentences++singleton :: a -> [a]+singleton x = [x]+\end{code}
+ src/NLP/GenI/Polarity.lhs view
@@ -0,0 +1,1167 @@+% GenI surface realiser+% Copyright (C) 2005 Carlos Areces and Eric Kow+%+% This program is free software; you can redistribute it and/or+% modify it under the terms of the GNU General Public License+% as published by the Free Software Foundation; either version 2+% of the License, or (at your option) any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; if not, write to the Free Software+% Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.++\chapter{Polarity Optimisation}+\label{cha:Polarity}++We introduce a notion of polarities as a means of pre-detecting+incompatibilities between candidate trees for different propositions.+This optimisation is inserted between candidate selection +(section \ref{sec:candidate_selection})+and chart generation. The input to this optimisation is the+\jargon{target semantics} and the corresponding \jargon{candidate+ trees}.++This whole optimisation is based on adding polarities to the grammar.+We have a set of strings which we call \jargon{polarity keys}, and some+positive or negative integers which we call \jargon{charges}. Each tree+in the grammar may assign a charge to some number of polarity keys. For+example, here is a simple grammar that uses the polarity keys n and v.++\begin{tabular}{|l|l|}+\hline+tree & polarity effects \\+\hline+s(n$\downarrow$, v$\downarrow$, n$\downarrow$) & -2n -v\\+v(hates) & +v\\+n(mary) & +n\\+n(john) & +n\\+\hline+\end{tabular}++For now, these annotations are done by hand, and are based on syntactic+criteria (substitution and root node categories) but one could envisage+alternate criteria or an eventual means of automating the process. ++The basic idea is to use the polarity keys to determine which subsets of+candidate trees are incompatible with each other and rule them out. We+construct a finite state automaton which uses polarity keys to+pre-calculate the compatibility of sets of trees. At the end of the+optimisation, we are left with an automaton, each path of which is a+potentially compatible set of trees. We then preform surface+realisation seperately, treating each path as a set of candidate trees.++\emph{Important note}: one thing that may be confusing in this chapter+is that we refer to polarities (charges) as single integers, e.g, $-2n$.+In reality, to account for weird stuff like atomic disjunction, we do+not use simple integers, but polarities intervals, so more something +like $(-2,-2)n$! But for the most part, the intervals are zero length,+and you can just think of $-2n$ as shorthand for $(-2,-2)n$.++\begin{code}+module NLP.GenI.Polarity(+ -- * Entry point+ PolAut, PolState(PolSt), AutDebug, PolResult,+ buildAutomaton,++ -- * Inner stuff (exported for debugging?)+ makePolAut,+ fixPronouns,+ detectSansIdx, detectPolFeatures, detectPols, detectPolPaths,+ declareIdxConstraints, detectIdxConstraints,+ showLite, showLitePm, showPolPaths, showPolPaths',++ -- re-exported from Automaton+ automatonPaths, finalSt,+ NFA(states, transitions),+ )+where+\end{code}++\begin{code}+import Data.Bits+import qualified Data.Map as Map+import Data.List+import Data.Maybe (isNothing)+import Data.Tree (flatten)+import qualified Data.Set as Set++import NLP.GenI.Automaton+import NLP.GenI.Btypes(Pred, SemInput, Sem, Flist, AvPair, showAv,+ GeniVal(GAnon), fromGConst, isConst,+ Replacable(..),+ emptyPred, Ptype(Initial), + showFlist, showSem, sortSem,+ root, gup, gdown, gtype, GType(Subs),+ SemPols, unifyFeat, rootUpd)+import NLP.GenI.General(+ BitVector, isEmptyIntersect, thd3,+ Interval, ival, (!+!), showInterval)+import NLP.GenI.Tags(TagElem(..), TagItem(..), setTidnums)+\end{code}++\section{Interface}++\begin{code}+-- | intermediate auts, seed aut, final aut, potentially modified sem+type PolResult = ([AutDebug], PolAut, PolAut, Sem)+type AutDebug = (String, PolAut, PolAut)++-- | Constructs a polarity automaton from the surface realiser's input: input+-- semantics, lexical selection, extra polarities and index constraints. For+-- debugging purposes, it returns all the intermediate automata produced by+-- the construction algorithm.+buildAutomaton :: SemInput -> [TagElem] -> Flist -> PolMap -> PolResult+buildAutomaton (tsem,tres,_) candRaw rootFeat extrapol =+ let -- root categories, index constraints, and external polarities+ rcatPol :: Map.Map String Interval+ rcatPol = Map.fromList $ polarise (-1) $ getval __cat__ rootFeat+ allExtraPols = Map.unionsWith (!+!) [ extrapol, inputRest, rcatPol ]+ -- index constraints on candidate trees+ detect = detectIdxConstraints tres+ constrain t = t { tpolarities = Map.unionWith (!+!) p r+ } --, tinterface = [] }+ where p = tpolarities t+ r = (detect . tinterface) t+ candRest = map constrain candRaw+ inputRest = declareIdxConstraints tres+ -- polarity detection + cand = detectPols candRest+ -- building the automaton+ in makePolAut cand tsem allExtraPols+\end{code}++\section{The automaton itself - outline}+\label{polarity:overview}++We start with the controller function (the general architecture) and+detail the individual steps in the following sections. The basic+architecture is as follows:++\begin{enumerate}+\item Build a seed automaton (section \ref{sec:seed_automaton}).+\item For each polarity key, elaborate the + automaton with the polarity information for that key+ (section \ref{sec:automaton_construction}) and minimise+ the automaton (section \ref{sec:automaton_pruning}).+\end{enumerate}++The above process can be thought of as a more efficient way of+constructing an automaton for each polarity key, minimising said+automaton, and then taking their intersection. In any case, +we return everything a tuple with (1) a list of the automota that+were created (2) the final automaton (3) a possibly modified+input semantics. The first item is only neccesary for debugging; only+the last two are important.++Note: +\begin{itemize}+\item the \fnparam{extraPol} argument is a map containing any initial+ values for polarity keys. This is useful to impose external filters+ like ``I only want expressions where the object is topicalised''. +\item to recuperate something useful from these automaton, it might+ be helpful to call \fnref{automatonPaths} on it.+\end{itemize}++\begin{code}+makePolAut :: [TagElem] -> Sem -> PolMap -> PolResult+makePolAut candsRaw tsemRaw extraPol =+ let -- polarity items+ ksCands = concatMap ((Map.keys).tpolarities) cands+ ksExtra = Map.keys extraPol+ ks = sortBy (flip compare) $ nub $ ksCands ++ ksExtra+ -- perform index counting+ (tsem, cands') = fixPronouns (tsemRaw,candsRaw)+ cands = setTidnums cands'+ -- sorted semantics (for more efficient construction)+ sortedsem = sortSemByFreq tsem cands + -- the seed automaton+ smap = buildColumns cands sortedsem + seed = buildSeedAut smap sortedsem+ -- building and remembering the automata + build k xs = (k,aut,prune aut):xs+ where aut = buildPolAut k initK (thd3 $ head xs)+ initK = Map.findWithDefault (ival 0) k extraPol+ res = foldr build [("(seed)",seed,prune seed)] ks+ in (reverse res, seed, thd3 $ head res, tsem)+\end{code}++% ====================================================================+\section{Polarity automaton}+\label{sec:polarity_automaton}+% ====================================================================++We construct a finite state automaton for each polarity key that is in+the set of trees. It helps to imagine a table where each column+corresponds to a single proposition. ++\begin{center}+\begin{tabular}{|c|c|c|}+\hline+\semexpr{gift(g)} & \semexpr{cost(g,x)} & \semexpr{high(x)} \\+\hline+\natlang{the gift} \color{blue}{+1np} & +\natlang{the cost of} & +\natlang{is high} \color{red}{-1np} \\+%+\natlang{the present} \color{blue}{+1np} & +\natlang{costs} \color{red}{-1np} & +\natlang{a lot} \\+%+&& \natlang{much} \\+\hline+\end{tabular}+\end{center}++Each column (proposition) has a different number of cells which+corresponds to the lexical ambiguity for that proposition, more+concretely, the number of candidate trees for that proposition. The+\jargon{polarity automaton} describes the different ways we can traverse+the table from column to column, choosing a cell to pass through at each+step and accumulating polarity along the way. Each state represents the+polarity at a column and each transition represents the tree we chose to+get there. All transitions from one columns $i$ to the next $i+1$ that+lead to the same accumulated polarity lead to the same state. ++% ----------------------------------------------------------------------+\subsection{Columns}+% ----------------------------------------------------------------------++We build the columns for the polarity automaton as follows. Given a+input semantics \texttt{sem} and a list of trees \texttt{cands}, we+group the trees by the first literal of sem that is part of their tree+semantics. ++Note: this is not the same function as Tags.mapBySem! The fact that we+preserve the order of the input semantics is important for our handling+of multi-literal semantics and for semantic frequency sorting.++\begin{code}+buildColumns :: (TagItem t) => [t] -> Sem -> Map.Map Pred [t] +buildColumns cands [] = + Map.singleton emptyPred e + where e = filter (null.tgSemantics) cands++buildColumns cands (l:ls) = + let matchfn t = l `elem` tgSemantics t+ (match, cands2) = partition matchfn cands+ next = buildColumns cands2 ls+ in Map.insert l match next+\end{code}++% ----------------------------------------------------------------------+\subsection{Initial Automaton}+\label{sec:seed_automaton}+% ----------------------------------------------------------------------++We first construct a relatively trivial polarity automaton without any+polarity effects. Each state except the start state corresponds+to a literal in the target semantics, and the transitions to a state +consist of the trees whose semantics is subsumed by that literal. ++\begin{code}+buildSeedAut :: SemMap -> Sem -> PolAut+buildSeedAut cands tsem = + let start = polstart []+ hasZero (x,y) = x <= 0 && y >= 0+ isFinal (PolSt c _ pols) = + c == length tsem && all hasZero pols+ initAut = NFA + { startSt = start+ , isFinalSt = Just isFinal+ , finalStList = []+ , states = [[start]]+ , transitions = Map.empty }+ in nubAut $ buildSeedAut' cands tsem 1 initAut++-- for each literal...+buildSeedAut' :: SemMap -> Sem -> Int -> PolAut -> PolAut +buildSeedAut' _ [] _ aut = aut +buildSeedAut' cands (l:ls) i aut = + let -- previously created candidates + prev = head $ states aut+ -- candidates that match the target semantics+ tcands = Map.findWithDefault [] l cands+ -- create the next batch of states+ fn st ap = buildSeedAutHelper tcands l i st ap+ (newAut,newStates) = foldr fn (aut,[]) prev+ next = (nub newStates):(states aut)+ -- recursive step to the next literal+ in buildSeedAut' cands ls (i+1) (newAut { states = next })++-- for each candidate corresponding to literal l...+buildSeedAutHelper :: [TagElem] -> Pred -> Int -> PolState -> (PolAut,[PolState]) -> (PolAut,[PolState])+buildSeedAutHelper cs l i st (aut,prev) =+ let -- get the extra semantics from the last state+ (PolSt _ ex1 _) = st+ -- candidates that match the target semantics and which+ -- do not overlap the extra baggage semantics+ tcand = [ Just t | t <- cs+ , isEmptyIntersect ex1 (tsemantics t) ]+ -- add the transitions out of the current state + addT tr (a,n) = (addTrans a st tr st2, st2:n)+ where + st2 = PolSt i (delete l $ ex1 ++ ex2) []+ ex2 = case tr of + Nothing -> [] + Just tr_ -> tsemantics tr_+ in if (l `elem` ex1) + then addT Nothing (aut,prev)+ else foldr addT (aut,prev) tcand +\end{code}++% ----------------------------------------------------------------------+\subsection{Construction}+\label{sec:automaton_construction}+\label{sec:automaton_intersection}+% ----------------------------------------------------------------------++The goal is to construct a polarity automaton which accounts for a+given polarity key $k$. The basic idea is that given +literals $p_1..p_n$ in the target semantics, we create a start state,+calculate the states/transitions to $p_1$ and succesively calculate+the states/transitions from proposition $p_x$ to $p_{x+1}$ for all+$1 < x < n$. ++The ultimate goal is to construct an automaton that accounts for +multiple polarity keys. The simplest approach would be to +calculate a seperate automaton for each key, prune them all and +then intersect the pruned automaton together, but we can do much +better than that. Since the pruned automata are generally much+smaller in size, we perform an iterative intersection by using +a previously pruned automaton as the skeleton for the current +automaton. This is why we don't pass any literals or candidates+to the construction step; it takes them directly from the previous+automaton. See also section \ref{sec:seed_automaton} for the seed +automaton that you can use when there is no ``previous automaton''.++\begin{code}+buildPolAut :: String -> Interval -> PolAut -> PolAut +buildPolAut k initK skelAut =+ let concatPol p (PolSt pr b pol) = PolSt pr b (p:pol)+ newStart = concatPol initK $ startSt skelAut+ --+ initAut = skelAut + { startSt = newStart+ , states = [[newStart]]+ , transitions = Map.empty }+ -- cand' = observe "candidate map" cand + in nubAut $ buildPolAut' k (transitions skelAut) initAut +\end{code}++Our helper function looks at a single state in the skeleton automaton+and at one of the states in the new automaton which correspond to it.+We use the transitions from the old automaton to determine which states+to construct. Note: there can be more than one state in the automaton+which corresponds to a state in the old automaton. This is because we+are looking at a different polarity key, so that whereas two candidates+automaton may transition to the same state in the old automaton, their+polarity effects for the new key will make them diverge in the new+automaton. ++\begin{code}+buildPolAut' :: String -> PolTransFn -> PolAut -> PolAut+-- for each literal... (this is implicit in the automaton state grouping)+buildPolAut' fk skeleton aut = + let -- previously created candidates + prev = head $ states aut + -- create the next batch of states+ fn st ap = buildPolAutHelper fk skeleton st ap+ (newAut,newStates) = foldr fn (aut,Set.empty) prev+ next = (Set.toList $ newStates):(states aut)+ -- recursive step to the next literal+ in if Set.null newStates+ then aut+ else buildPolAut' fk skeleton (newAut { states = next })++-- given a previously created state...+buildPolAutHelper :: String -> PolTransFn -> PolState -> (PolAut,Set.Set PolState) -> (PolAut,Set.Set PolState)+buildPolAutHelper fk skeleton st (aut,prev) =+ let -- reconstruct the skeleton state used to build st + PolSt pr ex (po1:skelpo1) = st+ skelSt = PolSt pr ex skelpo1+ -- for each transition out of the current state+ -- nb: a transition is (next state, [labels to that state])+ trans = Map.toList $ Map.findWithDefault Map.empty skelSt skeleton+ result = foldr addT (aut,prev) trans+ -- . for each label to the next state st2+ addT (oldSt2,trs) (a,n) = foldr (addTS oldSt2) (a,n) trs+ -- .. calculate a new state and add a transition to it+ addTS skel2 tr (a,n) = (addTrans a st tr st2, Set.insert st2 n)+ where st2 = newSt tr skel2+ --+ newSt :: Maybe TagElem -> PolState -> PolState+ newSt t skel2 = PolSt pr2 ex2 (po2:skelPo2)+ where + PolSt pr2 ex2 skelPo2 = skel2 + po2 = po1 !+! (Map.findWithDefault (ival 0) fk pol)+ pol = case t of Nothing -> Map.empty + Just t2 -> tpolarities t2+ in result +\end{code}++% ----------------------------------------------------------------------+\subsection{Pruning}+\label{sec:automaton_pruning}+% ----------------------------------------------------------------------++Any path through the automaton which does not lead to final+polarity of zero sum can now be eliminated. We do this by stepping+recursively backwards from the final states: ++\begin{code}+prune :: PolAut -> PolAut+prune aut = + let theStates = states aut+ final = finalSt aut+ -- (remember that states is a list of lists) + lastStates = head theStates + nextStates = tail theStates + nonFinal = (lastStates \\ final)+ -- the helper function will rebuild the state list+ firstAut = aut { states = [] }+ pruned = prune' (nonFinal:nextStates) firstAut + -- re-add the final state!+ statesPruned = states pruned+ headPruned = head statesPruned+ tailPruned = tail statesPruned+ in if (null theStates) + then aut+ else pruned { states = (headPruned ++ final) : tailPruned } +\end{code}++The pruning algorithm takes as arguments a list of states to process.+Among these, any state which does not have outgoing transitions is+placed on the blacklist. We remove all transitions to the blacklist and+all states that only transition to the blacklist, and then we repeat+pruning, with a next batch of states. ++Finally, we return the pruned automaton. Note: in order for this to+work, it is essential that the final states are *not* included in the+list of states to process.++\begin{code}+prune' :: [[PolState]] -> PolAut -> PolAut+prune' [] oldAut = oldAut { states = reverse $ states oldAut }+prune' (sts:next) oldAut = + let -- calculate the blacklist+ oldT = transitions oldAut+ oldSt = states oldAut+ transFrom st = Map.lookup st oldT+ blacklist = filter (isNothing.transFrom) sts+ -- given a st: filter out all transitions to the blacklist+ allTrans = Map.toList $ transitions oldAut+ -- delete all transitions to the blacklist+ miniTrim = Map.filterWithKey (\k _ -> not (k `elem` blacklist))+ -- extra cleanup: delete from map states that only transition to the blacklist+ trim = Map.filterWithKey (\k m -> not (k `elem` blacklist || Map.null m))+ -- execute the kill and miniKill filters+ newT = trim $ Map.fromList [ (st2, miniTrim m) | (st2,m) <- allTrans ]+ -- new list of states and new automaton+ newSts = sts \\ blacklist+ newAut = oldAut { transitions = newT,+ states = newSts : oldSt }+ {- + -- debugging code+ debugstr = "blacklist: [\n" ++ debugstr' ++ "]"+ debugstr' = concat $ intersperse "\n" $ map showSt blacklist+ showSt (PolSt pr ex po) = showPr pr ++ showEx ex ++ showPo po+ showPr (_,pr,_) = pr ++ " " + showPo po = concat $ intersperse "," $ map show po+ showEx ex = if (null ex) then "" else (showSem ex)+ -}+ -- recursive step+ in if null blacklist+ then oldAut { states = (reverse oldSt) ++ (sts:next) }+ else prune' next newAut +\end{code}++% ====================================================================+\section{Zero-literal semantics}+\label{sec:multiuse}+\label{semantic_weights}+\label{sec:nullsem}+\label{sec:co-anchors}+% ====================================================================++Lexical items with a \jargon{null semantics} typically correspond to+functions words: complementisers \natlang{(John likes \textbf{to}+read.)}, subcategorised prepositions \natlang{(Mary accuses John+\textbf{of} cheating.)}. Such items need not be lexical items at all.+We can exploit TAG's support for trees with multiple anchors, by+treating them as co-anchors to some primary lexical item. The English+infinitival \natlang{to}, for example, can appear in the tree+\tautree{to~take} as \koweytree{s(comp(to),v(take),np$\downarrow$)}. ++On the other hand, pronouns have a \jargon{zero-literal} semantics, one+which is not null, but which consists only of a variable index. For+example, the pronoun \natlang{she} in (\ref{ex:pronoun_pol_she}) has+semantics \semexpr{s} and in (\ref{ex:pronoun_pol_control}),+\natlang{he} has the semantics \semexpr{j}. ++{\footnotesize+\eenumsentence{\label{ex:pronoun_pol} +\item \label{ex:pronoun_pol_sue} +\semexpr{joe(j), sue(s), book(b), lend(l,j,b,s), boring(b) } +\\ \natlang{Joe lends Sue a boring book.}++% Note for visually impaired readers: ignore anything with \color{white}. +% It is used as a form of indentation to help sighted users. +\item \label{ex:pronoun_pol_she} +\semexpr{joe(j), {\color{white}sue(s),} book(b), lend(l,j,b,s), boring(b) } +\\ \natlang{Joe lends her a boring book.}+}+\eenumsentence{\label{ex:pronoun_pol_control} +\item[{\color{white}a.}] \label{ex:pronoun_pol_control_inf}+\semexpr{joe(j), sue(s), leave(l,j), promise(p,j,s,l)}+\\ \natlang{Joe promises Sue to leave.}+\\ or \natlang{Joe promises Sue that he would leave.} +}}++In figure \ref{fig:polarity_automaton_zerolit_bad}, we compare the+construction of polarity automata for (\ref{ex:pronoun_pol_sue}, left)+and (\ref{ex:pronoun_pol_she}, right). Building an automaton for+(\ref{ex:pronoun_pol_she}) fails because \tautree{sue} is not available+to cancel the negative polarities for \tautree{lends}; instead, a+pronoun must be used to take its place. The problem is that the+selection of a lexical items is only triggered when the construction+algorithm visits one of its semantic literals. Since pronoun semantics+have zero literals, they are \emph{never} selected. Making pronouns+visible to the construction algorithm would require us to count the+indices from the input semantics. Each index refers to an entity. This+entity must be ``consumed'' by a syntactic functor (e.g. a verb) and+``provided'' by a syntactic argument (e.g. a noun).++%\footnote{This also holds true for sentences like \natlang{Joe sings+%badly and Sue sings well}, \semexpr{sing(s1,j) good(s1), sing(s2,m),+%bad(s2)} because each usage of \natlang{sings} actually corresponds to a+%different lexical item, \tautree{sings1} with the semantics+%\semexpr{sings(s1,j)} and \tautree{sings2} with \semexpr{sings(s2,m)}.}. ++\begin{figure}[htpb]+\begin{center}+\includegraphics[scale=0.25]{images/zeroaut-noun.pdf}+\includegraphics[scale=0.25]{images/zeroaut-sans.pdf}+\end{center}+\vspace{-0.4cm}+\caption{Difficulty with zero-literal semantics.}+\label{fig:polarity_automaton_zerolit_bad}+\end{figure}++We make this explicit by annotating the semantics of the lexical input+(that is the set of lexical items selected on the basis of the input+semantics) with a form of polarities. Roughly, nouns provide+indices\footnote{except for predicative nouns, which like verbs, are+semantic functors} ($+$), modifiers leave them unaffected, and verbs+consume them ($-$). Predicting pronouns is then a matter of counting+the indices. If the positive and negative indices cancel each other+out, no pronouns are required. If there are more negative indices than+positive ones, then as many pronouns are required as there are negative+excess indices. In the table below, we show how the example semantics+above may be annotated and how many negative excess indices result:++\begin{center}+{\footnotesize+\begin{tabular}{|l|r|r|r|}+\hline+\multicolumn{1}{|c|}{\bf semantics} & +{\bf \tt b} &+{\bf \tt j} & +{\bf \tt s} \\ +\hline+\semexpr{joe(+j) sue(+s) book(+b) lend(l,-j,-b,-s) boring(b)} & +\semexpr{0} &+\semexpr{0} &+\semexpr{0} \\+\hline+\semexpr{joe(+j) {\color{white}sue(+s)} book(+b) lend(l,-j,-b,-s) boring(b)} & +\semexpr{0} &+\semexpr{0} &+\semexpr{1} \\+\hline+\semexpr{joe(+j) sue(+s) leave(l,-j,-s) promise(p,{\color{white}-}j,-s,l)} &+\semexpr{0} &+\semexpr{0} & +\semexpr{0} \\ +\hline+\semexpr{joe(+j) sue(+s) leave(l,-j,-s) promise(p,-j,-s,l)} &+\semexpr{0} &+\semexpr{1} & +\semexpr{0} \\ +\hline+\end{tabular}+}+\end{center}++Counting surplus indices allows us to establish the number of pronouns+used and thus gives us the information needed to build polarity+automata. We implement this by introducing a virtual literal for+negative excess index, and having that literal be realised by pronouns.+Building the polarity automaton as normal yields lexical combinations+with the required number of pronouns, as in figure+\ref{fig:polarity_automaton_zerolit}. ++\begin{figure}[htpb]+\begin{center}+\includegraphics[scale=0.25]{images/zeroaut-pron.pdf}+\end{center}+\vspace{-0.4cm}+\caption{Constructing a polarity automaton with zero-literal semantics.}+\label{fig:polarity_automaton_zerolit}+\end{figure}++\label{different_sem_annotations}+The sitation is more complicated where the lexical input+contains lexical items with different annotations for the same+semantics. For instance, the control verb \natlang{promise} has two+forms: one which solicits an infinitive as in \natlang{promise to+leave}, and one which solicits a declarative clause as in+\natlang{promise that he would leave}. This means two different counts+of subject index \semexpr{j} in (\ref{ex:pronoun_pol_control}) : zero+for the form that subcategorises for the infinitive, or one for the+declarative. But to build a single automaton, these counts must be+reconciled, i.e., how many virtual literals do we introduce for+\semexpr{j}, zero or one? The answer is to introduce enough virtual+literals to satisfy the largest demand, and then use the multi-literal+extension to support alternate forms with a smaller demand. To handle+example (\ref{ex:pronoun_pol_control}), we introduce one virtual literal+for \semexpr{j} so that the declarative form can be produced, and treat+the soliciting \natlang{promise} as though its semantics includes that+literal along with its regular semantics (figure+\ref{fig:polarity_automaton_zerolit_promise}). In other words, the+infinitive-soliciting form is treated as if it already fulfils the role+of a pronoun, and does not need one in its lexical combination.++\begin{figure}[htpb]+\begin{center}+\includegraphics[scale=0.25]{images/zeroaut-promise.pdf}+\end{center}+\vspace{-0.4cm}+\caption{Constructing a polarity automaton with zero-literal semantics.}+\label{fig:polarity_automaton_zerolit_promise}+\end{figure}++We insert pronouns into the input semantics using the following process:+\begin{enumerate}+\item For each literal in the input semantics, establish the+ smallest charge for each of its semantic indices.+\item Cancel out the polarities for every index in the input+ semantics.+\item Compensate for any uncancelled negative polarities by an+ adding an additional literal to the input semantics -- a pronoun --+ for every negative charge.+\item Finally, deal with the problem of lexical items who require fewer+ pronouns than predicted by inserting the excess pronouns in their extra+ literal semantics (see page \pageref{different_sem_annotations})+\end{enumerate}++\begin{code}+type PredLite = (String,[GeniVal]) -- handle is head of arg list +type SemWeightMap = Map.Map PredLite SemPols++-- | Returns a modified input semantics and lexical selection in which pronouns+-- are properly accounted for.+fixPronouns :: (Sem,[TagElem]) -> (Sem,[TagElem])+fixPronouns (tsem,cands) = + let -- part 1 (for each literal get smallest charge for each idx)+ getpols :: TagElem -> [ (PredLite,SemPols) ]+ getpols x = zip [ (show p, h:as) | (h,p,as) <- tsemantics x ] (tsempols x)+ sempols :: [ (PredLite,SemPols) ]+ sempols = concatMap getpols cands+ usagemap :: SemWeightMap + usagemap = Map.fromListWith (zipWith min) sempols+ -- part 2 (cancel sem polarities)+ chargemap :: Map.Map GeniVal Int -- index to charge + chargemap = Map.fromListWith (+) $ concatMap clump $ Map.toList usagemap+ where clump ((_,is),ps) = zip is ps+ -- part 3 (adding extra semantics)+ indices = concatMap fn (Map.toList chargemap) + where fn (i,c) = replicate (0-c) i+ -- the extra columns + extraSem = map indexPred indices+ tsem2 = sortSem (tsem ++ extraSem)+ -- zero-literal semantic items to realise the extra columns + zlit = filter (null.tsemantics) cands+ cands2 = (cands \\ zlit) ++ (concatMap fn indices)+ where fn i = map (tweak i) zlit+ tweak i x = assignIndex i $ x { tsemantics = [indexPred i] }+ -- part 4 (insert excess pronouns in tree sem)+ comparefn :: GeniVal -> Int -> Int -> [GeniVal]+ comparefn i ct cm = if (cm < ct) then extra else []+ where maxNeeded = Map.findWithDefault 0 i chargemap -- cap the number added+ extra = replicate (min (0 - maxNeeded) (ct - cm)) i+ comparePron :: (PredLite,SemPols) -> [GeniVal]+ comparePron (lit,c1) = concat $ zipWith3 comparefn idxs c1 c2+ where idxs = snd lit+ c2 = Map.findWithDefault [] lit usagemap+ addextra :: TagElem -> TagElem+ addextra c = c { tsemantics = sortSem (sem ++ extra) }+ where sem = tsemantics c+ extra = map indexPred $ concatMap comparePron (getpols c)+ cands3 = map addextra cands2+ in (tsem2, cands3)++-- | Builds a fake semantic predicate that the index counting mechanism uses to+-- represent extra columns.+indexPred :: GeniVal -> Pred+indexPred x = (x, GAnon, [])++-- Returns True if the given literal was introduced by the index counting mechanism+isExtraCol :: Pred -> Bool+isExtraCol (_,GAnon,[]) = True+isExtraCol _ = False+\end{code}++\paragraph{assignIndex} is a useful way to restrict the behaviour of+null semantic items like pronouns using the information generated by+the index counting mechanism. The problem with null semantic items +is that their indices are not set, which means that they could+potentially combine with any other tree. To make things more +efficient, we can set the index of these items and thus reduce the+number of spurious combinations. ++Notes+\begin{itemize}+%\item These combinations could produce false results if the+%input has to use multiple pronouns. For example, if you wanted to say+%something like \natlang{John promises Mary to convince Paul to give her+% his book}, these combinations could instead produce \natlang{give him+% \textbf{her} book}.+\item This function works by FS unification on the root node of the+ tree with the \fs{\it idx:i\\}. If unification is not possible, + we simply return the tree as is.+\item This function renames the tree by appending the index to its name+\end{itemize}++\begin{code}+assignIndex :: GeniVal -> TagElem -> TagElem +assignIndex i te =+ let idxfs = [ (__idx__, i) ]+ oldt = ttree te+ oldr = root oldt+ tfup = gup oldr+ --+ in case unifyFeat tfup idxfs of+ Nothing -> te+ Just (gup2, sub) -> replace sub $ te { ttree = newt }+ where newt = rootUpd oldt $ oldr { gup = gup2 }+\end{code}+++% ====================================================================+\section{Further optimisations}+% ====================================================================++\subsection{Lexical filtering} \label{fn:detectIdxConstraints}++Lexical filtering allows the user to constrain the lexical selection+to only those items that contain a certain property, for example, the+realising an item as a cleft.++The idea is that the user provides an input like+\verb$idxconstraints:[cleft:j]$,+which means that the lexical selection must include exactly one tree+with the property cleft:j in its interface. This mechanism works as+pre-processing step after lexical selection and before polarity+automaton construction, in conjuction with the ExtraPolarities+mechanism. What we do is++\begin{enumerate}+\item Preprocess the lexically selected trees; any tree which has a+ a desired property (e.g. cleft:j) in its interface is assigned+ a positive polarity for that property (+cleft:j)+\item Add all the index constraints as negative extra polarities (-cleft:j)+\end{enumerate}++Note: we assume the index constraints and interface are sorted; also, we+prefix the index constraint polarities with a ``.'' because they are likely to+be very powerful filters and we would like them to be used first.++\begin{code}+detectIdxConstraints :: Flist -> Flist -> PolMap +detectIdxConstraints cs interface =+ let matches = intersect cs interface+ matchStr = map showIdxConstraint matches+ in Map.fromList $ zip matchStr ((repeat.ival) 1)++declareIdxConstraints :: Flist -> PolMap+declareIdxConstraints = Map.fromList . (map declare) where+ declare c = (showIdxConstraint c, minusone)+ minusone = ival (-1)++showIdxConstraint :: AvPair -> String+showIdxConstraint = ('.' :) . showAv+\end{code}++\subsection{Automatic detection}++Automatic detection is not an optimisation in itself, but a means to+make grammar development with polarities more convenient.++\paragraph{Which attributes should we use?} Our detection process looks for+attributes which are defined on \emph{all} subst and root nodes of the+lexically selected items. Note that this should typically give you the+\verb!cat! and \verb!idx! polarities.++\begin{code}+detectPolFeatures :: [TagElem] -> [String]+detectPolFeatures tes =+ let -- only initial trees need be counted; in aux trees, the+ -- root node is implicitly canceled by the foot node+ rfeats, sfeats :: [Flist]+ rfeats = map (gdown.root.ttree) $ filter (\t -> ttype t == Initial) tes+ sfeats = [ concat s | s <- map substTops tes, (not.null) s ]+ --+ attrs :: Flist -> [String]+ attrs avs = [ a | (a,v) <- avs, isConst v ]+ theAttributes = map attrs $ rfeats ++ sfeats+ in if null tes then [] else foldr1 intersect theAttributes++-- FIXME: temporary HACKY code - delete me as soon as possible (written+-- 2006-03-30+--+-- only initial trees need be counted; in aux trees, the+-- root node is implicitly canceled by the foot node+detectSansIdx :: [TagElem] -> [TagElem]+detectSansIdx =+ let rfeats t = (gdown.root.ttree) t+ feats t | ttype t == Initial = concat $ (rfeats t) : (substTops t)+ feats t = concat $ substTops t+ attrs avs = [ a | (a,v) <- avs, isConst v ]+ hasIdx t = __idx__ `elem` (attrs.feats $ t) || (ttype t /= Initial && (null $ substTops t))+ in filter (not.hasIdx)+\end{code}++\paragraph{The polarity values}+First the simplified explanation: we assign every tree with a $-1$ charge for+every category for every substitution node it has. Additionally, we assign+every initial tree with a $+1$ charge for the category of its root node. So+for example, the tree s(n$\downarrow$, cl$\downarrow$, v(aime), n$\downarrow$)+should have the following polarities: s +1, cl -1, n -2. These charges are+added to any that previously been defined in the grammar.++Now what really happens: we treat automaton polarities as intervals, not +as single integers! For the most part, nothing changes from the simplified+explanation. Where we added a $-1$ charge before, we now add a $(-1,-1)$+charge. Similarly, we where added a $+1$ charge, we now add $(1,1)$. So+what's the point of all this? It helps us deal with atomic disjunction.++\subparagraph{Atomic disjunction} Say we encounter a substitution node +whose category is either cl or n. What we do is add the polarities+$cl (-1,0), n (-1,0)$ which means that there are anywhere from -1 to +0 cl, and for n. +FIXME: What kind of sucks about all this though is that this slightly worsens+the filter because it allows for both cl and n to be $-1$ (or $0$) at the same+time. It would be nice to have some kind of mutual exclusion working.++\begin{code}+detectPols :: [TagElem] -> [TagElem]+detectPols = map detectPols'++detectPols' :: TagElem -> TagElem+detectPols' te =+ let otherFeats = [] --, __idx__ ]+ feats = __cat__ : otherFeats+ --+ rootdown = (gdown.root.ttree) te+ rootup = (gup.root.ttree) te+ rstuff :: [[String]]+ rstuff = getval __cat__ rootup -- cat is special, see below+ ++ (concatMap (\v -> getval v rootdown) otherFeats)+ -- re:above, cat it is considered global to the whole tree+ -- to be robust, we grab it from the top feature+ substuff :: [[String]]+ substuff = concatMap (\v -> concatMap (getval v) (substTops te)) feats+ --+ -- substs nodes only+ commonPols :: [ (String,Interval) ]+ commonPols = polarise (-1) substuff+ -- substs and roots+ pols :: [ (String,Interval) ]+ pols = case ttype te of+ Initial -> commonPols ++ polarise 1 rstuff+ _ -> commonPols+ --+ oldfm = tpolarities te+ in te { tpolarities = foldr addPol oldfm pols }++__cat__, __idx__ :: String+__cat__ = "cat"+__idx__ = "idx"++getval :: String -> Flist -> [[String]]+getval att fl =+ case [ v | (a,v) <- fl, a == att ] of+ [] -> error $ "[polarities] No instances of " ++ att ++ " in " ++ showFlist fl ++ "."+ vs -> if all isConst vs+ then map (prefixWith att . fromGConst) vs+ else error $ "[polarities] Not all values for feature " ++ att ++ " are instantiated."++toZero :: Int -> Interval+toZero x | x < 0 = (x, 0)+ | otherwise = (0, x)++prefixWith :: String -> [String] -> [String]+prefixWith att = map (\x -> att ++ ('_' : x))++polarise :: Int -> [[String]] -> [ (String, Interval) ]+polarise i = concatMap fn+ where+ fn [x] = [ (x, one) ]+ fn amb = for amb $ \x -> (x, oneZero)+ one = ival i+ oneZero = toZero i++for :: [a] -> (a -> b) -> [b]+for = flip map++substTops :: TagElem -> [Flist]+substTops t = [ gup gn | gn <- (flatten.ttree) t, gtype gn == Subs ]+\end{code}++\subsection{Chart sharing}++Chart sharing is based on the idea that instead of performing a +seperate generation task for each automaton path, we should do+single generation task, but annotate each tree with set of the+automata paths it appears on. We then allow trees on the+same paths to be compared only if they are on the same path.+Note: chart sharing involves some mucking around with the generation+engine (see page \pageref{fn:Builder:preInit})++\begin{code}+-- | Given a list of paths (i.e. a list of list of trees)+-- return a list of trees such that each tree is annotated with the paths it+-- belongs to.+detectPolPaths :: [[TagElem]] -> [(TagElem,BitVector)]+detectPolPaths paths = + let pathFM = detectPolPaths' Map.empty 0 paths+ lookupTr k = Map.findWithDefault 0 k pathFM+ in map (\k -> (k, lookupTr k)) $ Map.keys pathFM++type PolPathMap = Map.Map TagElem BitVector+detectPolPaths' :: PolPathMap -> Int -> [[TagElem]] -> PolPathMap ++detectPolPaths' accFM _ [] = accFM+detectPolPaths' accFM counter (path:ps) = + let currentBits = shiftL 1 counter -- shift counter times the 1 bit+ fn f [] = f+ fn f (t:ts) = fn (Map.insertWith (.|.) t currentBits f) ts + newFM = fn accFM path+ in detectPolPaths' newFM (counter+1) ps++-- | Render the list of polarity automaton paths as a string+showPolPaths :: BitVector -> String+showPolPaths paths =+ let pathlist = showPolPaths' paths 1+ in concat $ intersperse ", " $ map show pathlist++showPolPaths' :: BitVector -> Int -> [Int] +showPolPaths' 0 _ = []+showPolPaths' bv counter = + if b then (counter:next) else next+ where b = testBit bv 0+ next = showPolPaths' (shiftR bv 1) (counter + 1)+\end{code}++\subsection{Semantic sorting}++To minimise the number of states in the polarity automaton, we could+also sort the literals in the target semantics by the number of+corresponding lexically selected items. The idea is to delay branching+as much as possible so as to mimimise the number of states in the+automaton.++Let's take a hypothetical example with two semantic literals:+bar (having two trees with polarties 0 and +1).+foo (having one tree with polarity -1) and+If we arbitrarily explored bar before foo (no semantic sorting), the+resulting automaton could look like this:++\begin{verbatim}+ bar foo+(0)--+---(0)------(-1)+ | + +---(1)------(0)+\end{verbatim}++With semantic sorting, we would explore foo before bar because foo has+fewer items and is less likely to branch. The resulting automaton+would have fewer states.++\begin{verbatim}+ foo bar+(0)-----(-1)--+---(-1)+ | + +---(0)+\end{verbatim}++The hope is that this would make the polarity automata a bit+faster to build, especially considering that we are working over+multiple polarity keys. ++Note: we have to take care to count each literal for each lexical+entry's semantics or else the multi-literal semantic code will choke.++\begin{code}+sortSemByFreq :: Sem -> [TagElem] -> Sem+sortSemByFreq tsem cands = + let counts = map lenfn tsem + lenfn l = length $ filter fn cands + where fn x = l `elem` (tsemantics x)+ -- note: we introduce an extra hack to push+ -- index-counted extra columns to the end; just for UI reasons+ sortfn a b + | isX a && isX b = compare (snd a) (snd b)+ | isX a = GT+ | isX b = LT+ | otherwise = compare (snd a) (snd b)+ where isX = isExtraCol.fst + sorted = sortBy sortfn $ zip tsem counts + in (fst.unzip) sorted +\end{code}++% ----------------------------------------------------------------------+\section{Types}+% ----------------------------------------------------------------------++\begin{code}+type SemMap = Map.Map Pred [TagElem]+type PolMap = Map.Map String Interval ++-- | Adds a new polarity item to a 'PolMap'. If there already is a polarity+-- for that item, it is summed with the new polarity.+addPol :: (String,Interval) -> PolMap -> PolMap+addPol (p,c) m = Map.insertWith (!+!) p c m++-- | Ensures that all states and transitions in the polarity automaton+-- are unique. This is a slight optimisation so that we don't have to+-- repeatedly check the automaton for state uniqueness during its+-- construction, but it is essential that this check be done after+-- construction+nubAut :: (Ord ab, Ord st) => NFA st ab -> NFA st ab +nubAut aut = + aut {+ transitions = Map.map (\e -> Map.map nub e) (transitions aut)+ }+\end{code}++\subsection{Polarity NFA}++We can define the polarity automaton as a NFA, or a five-tuple +$(Q, \Sigma, \delta, q_0, q_n)$ such that ++\begin{enumerate}+\item $Q$ is a set of states, each state being a tuple $(i,e,p)$ where $i$+is an integer (representing a single literal in the target semantics), +$e$ is a list of extra literals+which are known by the state, and $p$ is a polarity.+\item $\Sigma$ is the union of the sets of candidate trees for all+propositions+\item $q_0$ is the start state $(0,[0,0])$ which does not correspond to any+propositions and is used strictly as a starting point.+\item $q_n$ is the final state $(n,[x,y])$ which corresponds to the last+proposition, with polarity $x \leq 0 \leq y$.+\item $\delta$ is the transition function between states, which we+define below.+\end{enumerate}++Note: +\begin{itemize}+\item For convenience during automaton intersection, we actually define+ the states as being $(i, [(p_x,p_y)])$ where $[(p_x,p_y)]$ is a list of+ polarity intervals. +\item We use integer $i$ for each state instead of literals directly,+ because it is possible for the target semantics to contain the + same literal twice (at least, with the index counting mechanism+ in place)+\end{itemize}++\begin{code}+data PolState = PolSt Int [Pred] [(Int,Int)] + -- ^ position in the input semantics, extra semantics, + -- polarity interval+ deriving (Eq)+type PolTrans = TagElem+type PolAut = NFA PolState PolTrans+type PolTransFn = Map.Map PolState (Map.Map PolState [Maybe PolTrans])++instance Show PolState+ where show (PolSt pr ex po) = show pr ++ " " ++ showSem ex ++ show po+-- showPred pr ++ " " ++ showSem ex ++ show po++instance Ord PolState where+ compare (PolSt pr1 ex1 po1) (PolSt pr2 ex2 po2) = + let prC = compare pr1 pr2+ expoC = compare (ex1,po1) (ex2,po2)+ in if (prC == EQ) then expoC else prC+\end{code}++We include also some fake states which are useful for general+housekeeping during the main algortihms.++\begin{code}+fakestate :: Int -> [Interval] -> PolState+fakestate s pol = PolSt s [] pol --PolSt (0, s, [""]) [] pol++-- | an initial state for polarity automata+polstart :: [Interval] -> PolState+polstart pol = fakestate 0 pol -- fakestate "START" pol+\end{code}++% ----------------------------------------------------------------------+\section{Display code}+\label{sec:display_pol}+% ----------------------------------------------------------------------++\begin{code}+-- | 'showLite' is like Show but it's only used for debugging+-- TODO: is this true?+class ShowLite a where+ showLite :: a -> String++instance (ShowLite a) => ShowLite [a] where+ showLite x = "[" ++ (concat $ intersperse ", " $ map showLite x) ++ "]"+instance (ShowLite a, ShowLite b) => ShowLite (a,b) where+ showLite (x,y) = "(" ++ (showLite x) ++ "," ++ (showLite y) ++ ")"++instance ShowLite Int where showLite = show +instance ShowLite Char where showLite = show +\end{code}++%\begin{code}+%instance (Show st, ShowLite ab) => ShowLite (NFA st ab) where+% showLite aut = +% concatMap showTrans $ toList (transitions aut)+% where showTrans ((st1, x), st2) = show st1 ++ showArrow x +% ++ show st2 ++ "\n"+% showArrow x = " --" ++ showLite x ++ "--> " +% -- showSt (PolSt pr po) = show po+%\end{code}++\begin{code}+instance ShowLite TagElem where+ showLite = idname ++{-+-- | Display a SemMap in human readable text.+showLiteSm :: SemMap -> String+showLiteSm sm = + concatMap showPair $ toList sm + where showPair (pr, cs) = showPred pr ++ "\t: " ++ showPair' cs ++ "\n"+ showPair' [] = ""+ showPair' (te:cs) = tlIdname te ++ "[" ++ showLitePm (tpolarities te) ++ "]"+ ++ " " ++ showPair' cs +-}++-- | Display a PolMap in human-friendly text.+-- The advantage is that it displays fewer quotation marks.+showLitePm :: PolMap -> String+showLitePm pm = + let showPair (f, pol) = showInterval pol ++ f + in concat $ intersperse " " $ map showPair $ Map.toList pm+\end{code}
+ src/NLP/GenI/Simple/SimpleBuilder.lhs view
@@ -0,0 +1,1205 @@+% GenI surface realiser+% Copyright (C) 2005 Carlos Areces and Eric Kow+%+% This program is free software; you can redistribute it and/or+% modify it under the terms of the GNU General Public License+% as published by the Free Software Foundation; either version 2+% of the License, or (at your option) any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; if not, write to the Free Software+% Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.++\chapter{SimpleBuilder}+\label{cha:SimpleBuilder}++A SimpleBuilder is a Builder which constructs derived trees using a+simple agenda control mechanism and two-phase realisation (substitution+before adjunction). There is no packing strategy whatsoever; each chart+item is a derived tree.++\begin{code}+{-# LANGUAGE LiberalTypeSynonyms #-}+module NLP.GenI.Simple.SimpleBuilder (+ -- Types+ Agenda, AuxAgenda, Chart, SimpleStatus, SimpleState,+ SimpleItem(..),++ -- From SimpleStatus+ simpleBuilder_1p, simpleBuilder_2p, simpleBuilder,+ theAgenda, theAuxAgenda, theChart, theResults,+ initSimpleBuilder,+ addToAgenda, addToChart,+ genconfig,++#ifndef DISABLE_GUI+ SimpleGuiItem(..),+ theTrash, unpackResult,+#endif+ )+where+\end{code}+++\ignore{+\begin{code}+import Control.Monad (when, liftM2)+import Control.Monad.State+ (get, put, modify, gets, runState, execStateT)++import Data.List+ (partition, delete, foldl', unfoldr, sortBy)+import Data.Maybe (isJust, isNothing)+import Data.Ord (comparing)+import Data.Bits+import qualified Data.Map as Map+import Data.Tree++import NLP.GenI.Statistics (Statistics)++import NLP.GenI.Automaton ( automatonPaths, NFA(..), addTrans )+import NLP.GenI.Btypes+ ( Ptype(Initial,Auxiliar)+ , Replacable(..), replaceOneAsMap+ , GNode(..), NodeName+ , root, foot+ , plugTree, spliceTree+ , unifyFeat, Flist, Subst, mergeSubst+ )+import NLP.GenI.Builder (+ incrCounter, num_iterations, num_comparisons, chart_size,+ SemBitMap, defineSemanticBits, semToBitVector, bitVectorToSem,+ DispatchFilter, (>-->), condFilter, nullFilter,+ semToIafMap, IafAble(..), IafMap, fromUniConst, getIdx,+ recalculateAccesibility, iafBadSem, ts_iafFailure,+ )+import qualified NLP.GenI.Builder as B++import NLP.GenI.Tags (TagElem, TagSite(TagSite),+ tagLeaves, tidnum,+ ttree, ttype, tsemantics,+ detectSites,+ TagDerivation,+ ts_rootFeatureMismatch,+ )+import NLP.GenI.Configuration+import NLP.GenI.General+ ( BitVector, mapMaybeM, mapTree', geniBug, preTerminals, )++#ifndef DISABLE_GUI+import NLP.GenI.Btypes ( GType(Other), sortSem, Sem, gnnameIs )+import NLP.GenI.General ( repList, )+import NLP.GenI.Tags ( idname,+ ts_synIncomplete, ts_semIncomplete, ts_tbUnificationFailure,+ )+#endif+\end{code}+}++% --------------------------------------------------------------------+\section{The Builder interface}+% --------------------------------------------------------------------++Here is our implementation of Builder.++\begin{code}+type SimpleBuilder = B.Builder SimpleStatus SimpleItem Params+simpleBuilder_2p, simpleBuilder_1p :: SimpleBuilder+simpleBuilder_2p = simpleBuilder True+simpleBuilder_1p = simpleBuilder False++simpleBuilder :: Bool -> SimpleBuilder+simpleBuilder twophase = B.Builder+ { B.init = initSimpleBuilder twophase+ , B.step = if twophase then generateStep_2p else generateStep_1p+ , B.stepAll = B.defaultStepAll (simpleBuilder twophase)+ , B.finished = \s -> (null.theAgenda) s && (not twophase || step s == Auxiliar)+ , B.unpack = unpackResults.theResults+ , B.partial = unpackResults.partialResults+ }+\end{code}++% --------------------------------------------------------------------+\section{Key types}+% --------------------------------------------------------------------++\begin{code}+type Agenda = [SimpleItem]+type AuxAgenda = [SimpleItem]+type Chart = [SimpleItem]+#ifndef DISABLE_GUI+type Trash = [SimpleItem]+#endif+\end{code}++\subsection{SimpleState and SimpleStatus}++The \fnreflite{SimpleState} is a state monad where the state being+thread through is a \fnreflite{SimpleStatus}. The two are named+deliberately alike to indicate their close relationship.++To prevent confusion, we ought to keep a somewhat consistent naming+scheme across the builders: FooState for the state monad, FooStatus for+the state monad's ``contents'', and FooItem for the chart items+manipulated.++Note the theTrash is not actually essential to the operation of the+generator; it is for pratical debugging of grammars. Instead of+trees dissapearing off the face of the debugger; they go into the+trash where the user can inspect them and try to figure out why they+went wrong.++\begin{code}+type SimpleState a = B.BuilderState SimpleStatus a++data SimpleStatus = S+ { theAgenda :: Agenda+ , theAuxAgenda :: AuxAgenda+ , theChart :: Chart+#ifndef DISABLE_GUI+ , theTrash :: Trash+#endif+ , theResults :: [SimpleItem]+ , theIafMap :: IafMap -- for index accessibility filtering+ , tsem :: BitVector+ , step :: Ptype+ , gencounter :: Integer+ , genconfig :: Params+ -- we keep a SemBitMap strictly to help display the semantics+ , semBitMap :: SemBitMap+ }+ deriving Show+\end{code}++\subsubsection{SimpleStatus updaters}++\begin{code}+addToAgenda :: SimpleItem -> SimpleState ()+addToAgenda te = do+ modify $ \s -> s{theAgenda = te:(theAgenda s) }++updateAgenda :: Agenda -> SimpleState ()+updateAgenda a = do+ modify $ \s -> s{theAgenda = a}++addToAuxAgenda :: SimpleItem -> SimpleState ()+addToAuxAgenda te = do+ s <- get+ -- each new tree gets a unique id... this makes comparisons faster+ let counter = (gencounter s) + 1+ te2 = te { siId = counter }+ put s{gencounter = counter,+ theAuxAgenda = te2:(theAuxAgenda s) }++addToChart :: SimpleItem -> SimpleState ()+addToChart te = do+ modify $ \s -> s { theChart = te:(theChart s) }+ incrCounter chart_size 1++#ifndef DISABLE_GUI+addToTrash :: SimpleItem -> String -> SimpleState ()+addToTrash te err = do+ let te2 = modifyGuiStuff (\g -> g { siDiagnostic = err:(siDiagnostic g) }) te+ modify $ \s -> s { theTrash = te2 : (theTrash s) }+#endif++addToResults :: SimpleItem -> SimpleState ()+addToResults te = do+ modify $ \s -> s { theResults = te : (theResults s) }+\end{code}++\subsection{SimpleItem}++\begin{code}+data SimpleItem = SimpleItem+ { siId :: ChartId+ --+ , siSubstnodes :: ![TagSite]+ , siAdjnodes :: ![TagSite]+ --+ , siSemantics :: !BitVector+ , siPolpaths :: !BitVector+ -- for generation sans semantics+ -- , siAdjlist :: [(String,Integer)] -- (node name, auxiliary tree id)+ -- for index accesibility filtering (one-phase only)+ , siAccesible :: [ String ] -- it's acc/inacc/undetermined+ , siInaccessible :: [ String ] -- that's why you want both+ --+ -- | actually: a set of pre-terminals and their leaves+ , siLeaves :: [(String, B.UninflectedDisjunction)]+ , siDerived :: Tree String+ , siRoot :: TagSite+ , siFoot :: Maybe TagSite+ --+ , siPendingTb :: [ TagSite ] -- only for one-phase+ -- how was this item produced?+ , siDerivation :: TagDerivation+#ifndef DISABLE_GUI+ -- for the debugger only+ , siGuiStuff :: SimpleGuiItem+#endif+ } deriving Show++#ifndef DISABLE_GUI+-- | Things whose only use is within the graphical debugger+data SimpleGuiItem = SimpleGuiItem+ { siHighlight :: [String] -- ^ nodes to highlight+ , siNodes :: [GNode] -- ^ actually a set+ -- if there are things wrong with this item, what?+ , siDiagnostic :: [String]+ , siFullSem :: Sem+ , siIdname :: String+ } deriving Show++modifyGuiStuff :: (SimpleGuiItem -> SimpleGuiItem) -> SimpleItem -> SimpleItem+modifyGuiStuff fn i = i { siGuiStuff = fn . siGuiStuff $ i }+#endif++type ChartId = Integer++instance Replacable SimpleItem where+ replaceMap s i =+ i { siSubstnodes = replaceMap s (siSubstnodes i)+ , siAdjnodes = replaceMap s (siAdjnodes i)+ , siLeaves = replaceMap s (siLeaves i)+ , siRoot = replaceMap s (siRoot i)+ , siFoot = replaceMap s (siFoot i)+ , siPendingTb = replaceMap s (siPendingTb i)+#ifndef DISABLE_GUI+ , siGuiStuff = replaceMap s (siGuiStuff i)+#endif+ }+ replaceOne = replaceOneAsMap++#ifndef DISABLE_GUI+instance Replacable SimpleGuiItem where+ replaceMap s i = i { siNodes = replaceMap s (siNodes i) }+ replaceOne = replaceOneAsMap+#endif+\end{code}++\begin{code}+{-# INLINE closedAux #-}++-- | True if the chart item has no open substitution nodes+closed :: SimpleItem -> Bool+closed = null.siSubstnodes++-- | True if the chart item is an auxiliary tree+aux :: SimpleItem -> Bool+aux = isJust . siFoot++-- | True if both 'closed' and 'aux' are True+closedAux :: SimpleItem -> Bool+closedAux x = (aux x) && (closed x)++adjdone :: SimpleItem -> Bool+adjdone = null.siAdjnodes++siInitial :: SimpleItem -> Bool+siInitial = isNothing . siFoot+\end{code}++% --------------------------------------------------------------------+\section{Initialisation}+% --------------------------------------------------------------------++\begin{code}+-- | Creates an initial SimpleStatus.+initSimpleBuilder :: Bool -> B.Input -> Params -> (SimpleStatus, Statistics)+initSimpleBuilder twophase input config =+ let cands = map (initSimpleItem bmap) $ B.inCands input+ (sem,_,_) = B.inSemInput input+ bmap = defineSemanticBits sem+ -- FIXME: I don't know if this matters for one-phase+ -- because of on-the-fly tb unification (in 2p), we+ -- need an initial tb step that only addresses the+ -- nodes with null adjunction constraints+ simpleDp = if twophase then simpleDispatch_2p+ else simpleDispatch_1p (isIaf config)+ initialDp = dpTbFailure >--> simpleDp+ --+ initS = S{ theAgenda = []+ , theAuxAgenda = []+ , theChart = []+#ifndef DISABLE_GUI+ , theTrash = []+#endif+ , theResults = []+ , semBitMap = bmap+ , tsem = semToBitVector bmap sem+ , theIafMap = semToIafMap sem+ , step = Initial+ , gencounter = toInteger $ length cands+ , genconfig = config }+ --+ in B.unlessEmptySem input config $+ runState (execStateT (mapM initialDp cands) initS) (B.initStats config)+++initSimpleItem :: SemBitMap -> (TagElem, BitVector) -> SimpleItem+initSimpleItem bmap (teRaw,pp) =+ let (te,tlite) = renameNodesWithTidnum teRaw in+ case detectSites (ttree te) of+ (snodes,anodes,nullAdjNodes) -> setIaf $ SimpleItem+ { siId = tidnum te+ , siSemantics = semToBitVector bmap (tsemantics te)+ , siSubstnodes = snodes+ , siAdjnodes = anodes+ , siPolpaths = pp+ -- for index accesibility filtering+ , siAccesible = [] -- see below+ , siInaccessible = []+ -- for generation sans semantics+ -- , siAdjlist = []+ , siLeaves = tagLeaves te+ , siDerived = tlite+ , siRoot = ncopy.root $ theTree+ , siFoot = if ttype te == Initial then Nothing+ else Just . ncopy.foot $ theTree+ , siDerivation = []+ -- note: see comment in initSimpleBuilder re: tb unification+ , siPendingTb = nullAdjNodes+ --+#ifndef DISABLE_GUI+ , siGuiStuff = initSimpleGuiItem te+#endif+ }+ where setIaf i = i { siAccesible = iafNewAcc i }+ theTree = ttree te++#ifndef DISABLE_GUI+initSimpleGuiItem :: TagElem -> SimpleGuiItem+initSimpleGuiItem te = SimpleGuiItem+ { siHighlight = []+ , siNodes = flatten.ttree $ te+ , siDiagnostic = []+ , siFullSem = tsemantics te+ , siIdname = idname te }+#endif++renameNodesWithTidnum :: TagElem -> (TagElem, Tree NodeName)+renameNodesWithTidnum te =+ ( te { ttree = mapTree' renameNode theTree }+ , mapTree' newName theTree )+ where theTree = ttree te+ renameNode n = n { gnname = newName n }+ newName n = gnname n ++ "-" ++ tidstr+ tidstr = show . tidnum $ te+\end{code}++% --------------------------------------------------------------------+\section{Generate}+% --------------------------------------------------------------------++\subsection{One-phase generation}++This is a standard chart-and-agenda mechanism, where each iteration+consists of picking an item off the agenda and combining it with+elements from the chart.++\begin{code}+generateStep_1p :: SimpleState ()+generateStep_1p =+ do isDone <- gets (null.theAgenda)+ iaf <- gets (isIaf.genconfig)+ let dispatch = mapM (simpleDispatch_1p iaf)+ if isDone+ then return ()+ else do incrCounter num_iterations 1+ given <- selectGiven+ -- do both substitution and adjunction+ applySubstitution1p given >>= dispatch+ passiveAdjunction1p given >>= dispatch+ activeAdjunction1p given >>= dispatch+ sansAdjunction1p given >>= dispatch+ -- determine which of the res should go in the agenda+ -- (monadic state) and which should go in the result (res')+ addToChart given+\end{code}++\subsection{Two-phase generation}++Following \cite{carroll1999ecg}, we could also separate realisation into+two distinct phases. This requires that we maintain two seperate+agendas and process them sequentially, one loop after the other. See+\fnref{switchToAux} for details.++\begin{itemize}+\item If both Agenda and AuxAgenda are empty then there is nothing to do,+ otherwise, if Agenda is empty then we switch to the application of the+ Adjunction rule.+\item After the rule is applied we classify solutions into those that are complete+ and cover the semantics and those that don't. The first ones are returned+ and added to the result, while the others are sent back to Agenda.+\item Notice that if we are applying the Substitution rule then the+ current agenda item is added to the chart, otherwise it is deleted.+\end{itemize}++\begin{code}+generateStep_2p :: SimpleState ()+generateStep_2p = do+ nir <- gets (null.theAgenda)+ curStep <- gets step+ -- this check may seem redundant with generate, but it's needed+ -- to protect against a user who calls generateStep_2p on a finished+ -- state+ if (nir && curStep == Auxiliar)+ then return ()+ else do incrCounter num_iterations 1+ -- this triggers exactly once in the whole process+ if nir+ then switchToAux+ else generateStep_2p'++generateStep_2p' :: SimpleState ()+generateStep_2p' =+ do -- choose an item from the agenda+ given <- selectGiven+ -- have we triggered the switch to aux yet?+ curStep <- gets step+ -- do either substitution or adjunction+ res <- if (curStep == Initial)+ then applySubstitution given+ else liftM2 (++) (sansAdjunction2p given) (applyAdjunction2p given)++ -- determine which of the res should go in the agenda+ -- (monadic state) and which should go in the result (res')+ mapM simpleDispatch_2p res+ -- put the given into the chart untouched+ if (curStep == Initial)+ then addToChart given+ else when (adjdone given) $ trashIt given+\end{code}++\subsection{Helpers for the generateSteps}++\begin{code}+trashIt :: SimpleItem -> SimpleState ()+#ifdef DISABLE_GUI+trashIt _ = return ()+#else+trashIt item =+ do s <- get+ let bmap = semBitMap s+ itemSem = siSemantics item+ inputSem = tsem s+ reason = if inputSem == itemSem+ then "unknown reason!"+ else ts_semIncomplete $ bitVectorToSem bmap $ inputSem `xor` itemSem+ addToTrash item reason+#endif++-- | Arbitrarily selects and removes an element from the agenda and+-- returns it.+selectGiven :: SimpleState SimpleItem+selectGiven = do+ agenda <- gets theAgenda+ case agenda of+ [] -> geniBug "null agenda in selectGiven"+ (a:atail) -> updateAgenda atail >> return a+\end{code}++\subsection{Switching phases}++\fnlabel{switchToAux} When all substitutions has been done, tags with+substitution nodes still open are deleted, then the auxiliars tags are put in+Chart and the (initial) tags in the repository are moved into the Agenda. The+step is then changed to Auxiliary++\begin{code}+switchToAux :: SimpleState ()+switchToAux = do+ st <- get+ let chart = theChart st+ config = genconfig st+ -- You might be wondering why we ignore the auxiliary trees in the+ -- chart; this is because all the syntactically complete auxiliary+ -- trees have already been filtered away by calls to classifyNew+ initialT = filter siInitial chart+ res1@(compT1, incompT1) =+ partition (null.siSubstnodes) initialT+ --+ auxAgenda = theAuxAgenda st+ (compT2, incompT2) =+ if semfiltered config+ then semfilter (tsem st) auxAgenda compT1+ else res1+ --+ compT = compT2+ put st{ theAgenda = []+ , theAuxAgenda = []+ , theChart = auxAgenda+ , step = Auxiliar}+ -- the root cat filter by Claire+ let switchFilter =+ if rootcatfiltered config+ then dpRootFeatFailure2 >--> dpToAgenda+ else dpToAgenda+ mapM switchFilter compT+ -- toss the syntactically incomplete stuff in the trash+#ifndef DISABLE_GUI+ mapM (\t -> addToTrash t ts_synIncomplete) incompT1+ mapM (\t -> addToTrash t "sem-filtered") incompT2+#endif+ return ()+\end{code}++\subsubsection{SemFilter Optimisation}+\label{sec:semfilter}++The purpose of the semantic filter optimisation is to take full+advantage of Carroll's delayed adjunction. Consider the semantics+\semexpr{def(m), poor(m), brokenhearted(m), man(m), def(w), woman(w),+beautiful(w), heartless(w), rejects(w,m)}. At the switchToAux step, we+are left with the initial trees \natlang{man}, \natlang{woman}, \natlang{the+ woman rejects the man}.++It would be nice to filter out the structures \natlang{man} and \natlang{woman}+since we know that they are not going to be semantically complete even with+adjunction. More precisely, on the switch to adjunction, we do the following:++\begin{itemize}+\item Take the union of the semantics of all auxiliary trees; which+ we call $\phi^*$+\item Delete any initial tree with semantics $\phi^s$ such that+ $\phi^s \cup \phi^*$ is not the target semantics+\end{itemize}++In other words, we delete all initial trees that cannot produce a semantically+complete result even with the help of auxiliary trees.++FIXME: comment 2006-04-18: sem filter each polarity path separately (this is+more aggressive; it gives us much more filtering)++\begin{code}+semfilter :: BitVector -> [SimpleItem] -> [SimpleItem] -> ([SimpleItem], [SimpleItem])+semfilter inputsem auxs initial =+ let auxsem x = foldl' (.|.) 0 [ siSemantics a | a <- auxs, siPolpaths a .&. siPolpaths x /= 0 ]+ -- lite, here, means sans auxiliary semantics+ notjunk x = (siSemantics x) .&. inputsemLite == inputsemLite+ where inputsemLite = inputsem `xor` (auxsem x)+ -- note that we can't just compare against siSemantics because+ -- that would exclude trees that have stuff in the aux semantics+ -- which would be overzealous+ in partition notjunk initial+\end{code}++% --------------------------------------------------------------------+\section{Operations}+% --------------------------------------------------------------------++We implement the two TAG operations, substitution and adjunction, below.+These are the only two operations we have, because we're working with a+very simple builder that constructs derived trees.++% --------------------------------------------------------------------+\subsection{Substitution}+\label{sec:substitution}+% --------------------------------------------------------------------++\paragraph{applySubstitution} Given a SimpleItem it returns the list of all+possible substitutions between it and the elements in Chart++\begin{code}+applySubstitution :: SimpleItem -> SimpleState ([SimpleItem])+applySubstitution item =+ do gr <- lookupChart item+ active <- mapM (\x -> iapplySubst True item x) gr+ passive <- mapM (\x -> iapplySubst True x item) gr+ let res = concat $ active ++ passive+ incrCounter num_comparisons (2 * (length gr))+ return res++applySubstitution1p :: SimpleItem -> SimpleState ([SimpleItem])+applySubstitution1p item =+ do gr <- lookupChart item+ active <- if adjdone item then return []+ else mapM (\x -> iapplySubst False item x) gr+ passive <- mapM (\x -> iapplySubst False x item) $ filter adjdone gr+ let res = concat $ active ++ passive+ incrCounter num_comparisons (2 * (length gr))+ return res++-- | Note: returns ONE possible substitution (the head node)+-- of the first in the second. As all substitutions nodes should+-- be substituted we force substitution in order.+iapplySubst :: Bool -> SimpleItem -> SimpleItem -> SimpleState [SimpleItem]+iapplySubst twophase item1 item2 | siInitial item1 && closed item1 = {-# SCC "applySubstitution" #-}+ case siSubstnodes item2 of+ [] -> return []+ ((TagSite n fu fd nOrigin) : stail) ->+ let doIt =+ do -- Maybe monad+ let r@(TagSite rn ru rd rOrigin) = siRoot item1+ (newU, subst1) <- unifyFeat ru fu+ (newD, subst2) <- unifyFeat (replace subst1 rd)+ (replace subst1 fd)+ let subst = mergeSubst subst1 subst2+ nr = TagSite rn newU newD rOrigin+ adj1 = nr : (delete r $ siAdjnodes item1)+ adj2 = siAdjnodes item2+#ifdef DISABLE_GUI+ item1g = item1+#else+ item1g = item1 { siGuiStuff = g2 }+ where g2 = g { siNodes = repList (gnnameIs rn) newRoot (siNodes g) }+ g = siGuiStuff item1+ -- gui stuff+ newRoot g = g { gup = newU, gdown = newD, gtype = Other }+#endif+ let pending = if twophase then []+ else nr : ((siPendingTb item1) ++ (siPendingTb item2))+ return $! replace subst $ combineSimpleItems [rn] item1g $+ item2 { siSubstnodes = stail ++ (siSubstnodes item1)+ , siAdjnodes = adj2 ++ adj1+ , siDerived = plugTree (siDerived item1) n (siDerived item2)+ , siDerivation = addToDerivation 's' (item1g,rOrigin) (item2,nOrigin,n)+ , siLeaves = (siLeaves item1) ++ (siLeaves item2)+ , siPendingTb = pending+ }+ in case doIt of+ Nothing -> return []+ Just x -> do incrCounter "substitutions" 1+ return [x]+iapplySubst _ _ _ = return []+\end{code}++% --------------------------------------------------------------------+\subsection{Adjunction}+\label{sec:adjunction}+\label{sec:ordered_adjunction}+\label{sec:foot_constraint}+% ---------------------------------------------------------------++\paragraph{applyAdjunction2p} Given a SimpleItem, it returns the list of all+possible adjunctions between it and the elements in Chart.+The Chart contains Auxiliars, while SimpleItem is an Initial++Note: as of 13 april 2005 - only uses ordered adjunction as described in+\cite{kow04a}+\begin{code}+applyAdjunction2p :: SimpleItem -> SimpleState ([SimpleItem])+applyAdjunction2p item = {-# SCC "applyAdjunction2p" #-}+ do gr <-lookupChart item+ incrCounter num_comparisons (length gr)+ mapMaybeM (\a -> tryAdj True a item) gr++passiveAdjunction1p :: SimpleItem -> SimpleState [SimpleItem]+passiveAdjunction1p item | closed item && siInitial item =+ do gr <- lookupChart item+ mapMaybeM (\a -> tryAdj False a item) $ filter validAux gr+passiveAdjunction1p _ = return []++activeAdjunction1p :: SimpleItem -> SimpleState [SimpleItem]+activeAdjunction1p item | validAux item =+ do gr <- lookupChart item+ mapMaybeM (\p -> tryAdj False item p) $ filter (\x -> siInitial x && closed x) gr+activeAdjunction1p _ = return []++validAux :: SimpleItem -> Bool+validAux t = closedAux t && adjdone t++tryAdj :: Bool -> SimpleItem -> SimpleItem -> SimpleState (Maybe SimpleItem)+tryAdj twophase aItem pItem =+ do case iapplyAdjNode twophase aItem pItem of+ Just x -> do incrCounter "adjunctions" 1+ return $ Just x+ Nothing -> return Nothing+\end{code}++Note that in the one-phase variant of non-adjunction, we can't do top/bot+unification on the fly, because afaik we can't tell that a node will never+be revisited. One example of this is if you try to adjoin into the root++\begin{code}+-- | Ignore the next adjunction node+sansAdjunction1p, sansAdjunction2p :: SimpleItem -> SimpleState [SimpleItem]+sansAdjunction1p item | closed item =+ case siAdjnodes item of+ [] -> return []+ (ahead : atail) ->+ return $ [item { siAdjnodes = atail+ , siPendingTb = ahead : (siPendingTb item) } ]+sansAdjunction1p _ = return []++-- | Ignore the next adjunction node+sansAdjunction2p item | closed item =+ case siAdjnodes item of+ [] -> return []+ (TagSite gn t b o: atail) -> do+ -- do top/bottom unification on the node+ case unifyFeat t b of+ Nothing ->+#ifndef DISABLE_GUI+ do addToTrash (modifyGuiStuff (\g -> g { siHighlight = [gn] }) item)+ ts_tbUnificationFailure+#endif+ return []+ Just (tb,s) ->+ let item1 = if isRootOf item gn+ then item { siRoot = TagSite gn tb [] o }+ else item+#ifdef DISABLE_GUI+ item2 = item1+#else+ item2 = modifyGuiStuff (constrainAdj gn tb) item1+#endif+ in return $! [replace s $! item2 { siAdjnodes = atail }]+sansAdjunction2p _ = return []+\end{code}++The main work for adjunction is done in the helper function below+(see also figure \ref{fig:adjunction}).+Auxiliary tree \texttt{te1} has a root node \texttt{r} and a foot+node \texttt{f}. Main tree \texttt{te2} has an adjunction site \texttt{an}.+The resulting tree \texttt{res} is a result of splicing \texttt{te1} into+\texttt{te2}. We replace \texttt{s} with the nodes \texttt{anr} and+\texttt{anf} (which are the results of unifying \texttt{an} with \texttt{r}+ and \texttt{f} respectively).++\begin{figure}+\begin{center}+\includegraphics[scale=0.5]{images/adjunction.pdf}+\label{fig:adjunction}+\caption{iapplyAdjNode}+\end{center}+\end{figure}++In addition to the trees proper, we have to consider that each tree has+a list with a copy of its adjunction sites. The adjunction list of the+result (\texttt{adjnodes res}) should then contain \texttt{adjnodes te1}+and \texttt{adjnodes te2}, but replacing \texttt{r} and \texttt{an}+with \texttt{anr}.++\begin{code}+iapplyAdjNode :: Bool -> SimpleItem -> SimpleItem -> Maybe SimpleItem+iapplyAdjNode twophase aItem pItem = {-# SCC "iapplyAdjNode" #-}+ case siAdjnodes pItem of+ [] -> Nothing+ (TagSite an_name an_up an_down nOrigin : atail) -> do+ -- block repeated adjunctions of the same SimpleItem (for ignore semantics mode)+ -- guard $ not $ (an_name, siId aItem) `elem` (siAdjlist pItem)+ -- let's go!+ let r@(TagSite r_name r_up r_down rOrigin) = siRoot aItem -- auxiliary tree, eh?+ (TagSite f_name f_up f_down _) <- siFoot aItem -- should really be an error if fails+ (anr_up', subst1) <- unifyFeat r_up an_up+ (anf_down, subst2) <- unifyFeat (replace subst1 f_down) (replace subst1 an_down)+ let -- combined substitution list and success condition+ subst12 = mergeSubst subst1 subst2+ -- the result of unifying the t1 root and the t2 an+ anr = TagSite r_name (replace subst2 anr_up') r_down rOrigin+ let anf_up = replace subst12 f_up+ -- the new adjunction nodes+ auxlite = delete r $ siAdjnodes aItem+ newadjnodes = anr : (atail ++ auxlite)+ --+#ifdef DISABLE_GUI+ aItem2 = aItem+#else+ -- Ugh, this is horrible: this is just to make sure the GUI gets+ -- updated accordingly. The code used to be a lot simpler, but+ -- I started trying to move stuff out of the way in the interests+ -- of efficiency, and to pack as much gui-related stuff as possible+ -- into a single tuple.+ aItem2 = aItem { siGuiStuff = fixNodes $ siGuiStuff aItem }+ where fixNodes g = g { siNodes = map (setSites anr) (siNodes g) }+ setSites (TagSite n u d _) gn =+ if gnname gn == n then gn { gup = u, gdown = d }+ else gn+#endif+ rawCombined =+ combineSimpleItems [r_name, an_name] aItem2 $ pItem+ { siAdjnodes = newadjnodes+ , siLeaves = siLeaves aItem ++ siLeaves pItem+ , siDerived = spliceTree f_name (siDerived aItem) an_name (siDerived pItem)+ , siDerivation = addToDerivation 'a' (aItem,rOrigin) (pItem,nOrigin,an_name)+ -- , siAdjlist = (n, (tidnum te1)):(siAdjlist item2)+ -- if we adjoin into the root, the new root is that of the aux+ -- tree (affects 1p only)+ , siRoot = if isRootOf pItem an_name then r else siRoot pItem+ , siPendingTb =+ if twophase then []+ else (TagSite an_name anf_up anf_down nOrigin) : (siPendingTb pItem) ++ (siPendingTb aItem)+ }+ -- one phase = postpone tb unification+ -- two phase = do tb unification on the fly+ finalRes1p = return $ replace subst12 rawCombined+ finalRes2p =+ do -- tb on the former foot+ tbRes <- unifyFeat anf_up anf_down+#ifdef DISABLE_GUI+ let (_, subst3) = tbRes+ myRes = res'+#else+ let (anf_tb, subst3) = tbRes+ myRes = modifyGuiStuff (constrainAdj an_name anf_tb) res'+#endif+ -- apply the substitutions+ res' = replace (mergeSubst subst12 subst3) rawCombined+ return myRes+ -- ---------------+ if twophase then finalRes2p else finalRes1p+\end{code}++% --------------------------------------------------------------------+\subsection{Helper functions for operations}+% --------------------------------------------------------------------++\begin{code}+ncopy :: GNode -> TagSite+ncopy x = TagSite (gnname x) (gup x) (gdown x) (gorigin x)++isRootOf :: SimpleItem -> String -> Bool+isRootOf item n = n == rname+ where (TagSite rname _ _ _) = siRoot item++-- | Retrieves a list of trees from the chart which could be combined with the given agenda tree.+-- The current implementation searches for trees which+-- * do not have overlapping semantics with the given+-- * are on the some of the same polarity automaton paths as the+-- current agenda item+lookupChart :: SimpleItem -> SimpleState [SimpleItem]+lookupChart given = do+ chart <- gets theChart+ let gpaths = siPolpaths given+ gsem = siSemantics given+ return [ i | i <- chart+ -- should be on the same polarity path (chart sharing)+ , (siPolpaths i) .&. gpaths /= 0+ -- semantics should not be overlapping+ && (siSemantics i .&. gsem ) == 0+ ]++-- | Helper function for when chart operations succeed.+combineSimpleItems :: [NodeName] -- ^ nodes to highlight+ -> SimpleItem -> SimpleItem -> SimpleItem+combineSimpleItems hi item1 item2 = {-# SCC "combineSimpleItems" #-}+ item2 { siSemantics = (siSemantics item1) .|. (siSemantics item2)+ , siPolpaths = (siPolpaths item1) .&. (siPolpaths item2)+#ifndef DISABLE_GUI+ , siGuiStuff = combineSimpleGuiItems hi (siGuiStuff item1) (siGuiStuff item2)+#endif+ }++#ifndef DISABLE_GUI+combineSimpleGuiItems :: [NodeName]+ -> SimpleGuiItem -> SimpleGuiItem -> SimpleGuiItem+combineSimpleGuiItems hi item1 item2 =+ item2 { siFullSem = sortSem $ (siFullSem item1) ++ (siFullSem item2)+ , siNodes = (siNodes item1) ++ (siNodes item2)+ , siDiagnostic = (siDiagnostic item1) ++ (siDiagnostic item2)+ , siHighlight = hi+ }++constrainAdj :: String -> Flist -> SimpleGuiItem -> SimpleGuiItem+constrainAdj gn newT g =+ g { siNodes = repList (gnnameIs gn) fixIt (siNodes g) }+ where fixIt n = n { gup = newT, gdown = [], gaconstr = True }+#endif+\end{code}++\subsubsection{Derivation trees}++We make the simplifying assumption that each chart item is only used once.+This is clearly wrong if we allow for items with an empty semantics, but+since we do not actually allow such a thing, we're ok.++\begin{code}+addToDerivation :: Char+ -> (SimpleItem,String)+ -> (SimpleItem,String,String)+ -> TagDerivation+addToDerivation op (tc,tcOrigin) (tp,tpOrigin,tpSite) =+ let hp = siDerivation tp+ hc = siDerivation tc+ newnode = (op, tcOrigin, (tpOrigin, tpSite))+ in newnode:hp++hc+\end{code}++++% --------------------------------------------------------------------+\section{Dispatching new results}+% --------------------------------------------------------------------++Dispatching is the process where new chart items are assigned to one of+the trash, agenda, auxiliary agenda or chart. The item could be+modified during dispatch-time; for example, we might do top/bottom+unification on it. See \ref{sec:dispatching} for more details.++\begin{code}+type SimpleDispatchFilter = DispatchFilter SimpleState SimpleItem++simpleDispatch_2p :: SimpleDispatchFilter+simpleDispatch_2p =+ simpleDispatch (dpRootFeatFailure >--> dpToResults)+ (dpAux >--> dpToAgenda)++simpleDispatch_1p :: Bool -> SimpleDispatchFilter+simpleDispatch_1p iaf =+ simpleDispatch (dpRootFeatFailure >--> dpTbFailure >--> dpToResults)+ (maybeDpIaf >--> dpToAgenda)+ where maybeDpIaf = if iaf then dpIafFailure else nullFilter++simpleDispatch :: SimpleDispatchFilter -> SimpleDispatchFilter -> SimpleDispatchFilter+simpleDispatch resFilter nonResFilter item =+ do inputsem <- gets tsem+ let synComplete x = siInitial x && closed x && adjdone x+ semComplete x = inputsem == siSemantics x+ isResult x = synComplete x && semComplete x+ condFilter isResult resFilter nonResFilter item++dpAux, dpToAgenda :: SimpleDispatchFilter+dpTbFailure, dpRootFeatFailure, dpRootFeatFailure2, dpToResults :: SimpleDispatchFilter+dpToTrash :: String -> SimpleDispatchFilter++dpToAgenda x = addToAgenda x >> return Nothing+dpToResults x = addToResults x >> return Nothing+#ifdef DISABLE_GUI+dpToTrash _ _ = return Nothing+#else+dpToTrash m x = addToTrash x m >> return Nothing+#endif++dpAux item =+ if closedAux item+ then addToAuxAgenda item >> return Nothing+ else return $ Just item++{-+-- | Dispatches to the trash and returns Nothing if there is a tree+-- size limit in effect and the item is over that limit. The+-- tree size limit is used in 'IgnoreSemantics' mode.+dpTreeLimit item =+ do config <- gets genconfig+ case maxTrees config of+ Nothing -> return $ Just item+ Just lim -> if (length.snd.siDerivation) item > lim+ then do addToTrash item (ts_overnumTrees lim)+ return Nothing+ else return $ Just item+ where ts_overnumTrees l = "Over derivation size of " ++ (show l)+-}++-- | This is only used for the one-phase algorithm+dpTbFailure item =+ return $ if tbUnifyTree item then Just item else Nothing++-- | If the item (ostensibly a result) does not have the correct root+-- category, return Nothing; otherwise return Just item+dpRootFeatFailure = dpRootFeatFailure_ False+dpRootFeatFailure2 = dpRootFeatFailure_ True++dpRootFeatFailure_ :: Bool -> SimpleDispatchFilter+dpRootFeatFailure_ count item =+ do config <- gets genconfig+ let rootFeat = getListFlagP RootFeatureFlg config+ (TagSite _ top _ _) = siRoot item+ case unifyFeat rootFeat top of+ Nothing ->+ do when count $ incrCounter "root_feat_discards" 1+ dpToTrash (ts_rootFeatureMismatch rootFeat) item+ Just (_, s) ->+ return . Just $ replace s item+\end{code}+% --------------------------------------------------------------------+\subsection{Top and bottom unification}+% --------------------------------------------------------------------++\paragraph{tbUnifyTree} unifies the top and bottom feature structures+of each node on each tree. Note: this only determines if it is+possible to do so. Actually returning the results is possible+and even easy+(you'll have to look back into the darcs repository and unpull the+ patch from 2006-05-21T15:40:51 ``Remove top/bot unification standalone+ code.'')+but since it is only used in the one-phase algorithm and for the+graphical interface, I decided not to bother.++\begin{code}+type TbEither = Either String Subst+tbUnifyTree :: SimpleItem -> Bool+tbUnifyTree item = {-# SCC "tbUnifyTree" #-}+ case foldl tbUnifyNode (Right Map.empty) (siPendingTb item) of+ Left _ -> False+ Right _ -> True+\end{code}++Our helper function corresponds to the first unification step. It is+meant to be called from a fold. The node argument represents the+current node being explored. The Either argument holds a list of+pending substitutions and a copy of the entire tree.++There are two things going on in here:++\begin{enumerate}+\item check if unification is possible - first we apply the pending+ substitutions on the node and then we check if unification+ of the top and bottom feature structures of that node+ succeeds+\item keep track of the substitutions that need to be performed -+ any new substitutions that result from unification are+ added to the pending list+\end{enumerate}++Note that we wrap the second argument in a Maybe; this is used to+indicate that if unification suceeds or fails. We also use it to+prevent the function from doing any work if a unification failure+from a previous call has already occured.++Getting this right was a big pain in the butt, so don't go trying to+simplify this over-complicated code unless you know what you're doing.++\begin{code}+tbUnifyNode :: TbEither -> TagSite -> TbEither+tbUnifyNode (Right pending) rawSite =+ -- apply pending substitutions+ case replace pending rawSite of+ (TagSite name up down _) ->+ -- check top/bottom unification on this node+ case unifyFeat up down of+ -- stop all future iterations+ Nothing -> Left name+ -- apply any new substutions to the whole tree+ Just (_,sb) -> Right (mergeSubst pending sb)++-- if earlier we had a failure, don't even bother+tbUnifyNode (Left n) _ = Left n+\end{code}++% --------------------------------------------------------------------+\subsection{Index accesibility filtering}+\label{sec:simple:iaf}+% --------------------------------------------------------------------++Note that index accesibility filtering only makes sense for the one-phase+algorithm. See also \ref{sec:iaf} for more details about what this is.++\begin{code}+instance IafAble SimpleItem where+ iafAcc = siAccesible+ iafInacc = siInaccessible+ iafSetAcc a i = i { siAccesible = a }+ iafSetInacc a i = i { siInaccessible = a }+ iafNewAcc i =+ concatMap fromUniConst $+ concat [ getIdx up | (TagSite _ up _ _) <- siSubstnodes i ]++dpIafFailure :: SimpleDispatchFilter+dpIafFailure item | aux item = return $ Just item+dpIafFailure itemRaw =+ do s <- get+ let bmap = semBitMap s+ item = recalculateAccesibility itemRaw+ badSem = iafBadSem (theIafMap s) bmap (tsem s) siSemantics item+ inAcc = iafInacc item+ if badSem == 0+ then -- can't dispatch, but that's good!+ -- (note that we return the item with its iaf criteria updated)+ return $ Just item+ else dpToTrash (ts_iafFailure inAcc $ bitVectorToSem bmap badSem) item+\end{code}+++% --------------------------------------------------------------------+\section{Unpacking the results}+% --------------------------------------------------------------------++Unpacking the results consists of converting each result into a sentence+automaton (to take care of atomic disjunction) and reading the paths of+each automaton.++\begin{code}+unpackResults :: [SimpleItem] -> [B.Output]+unpackResults = concatMap unpackResult++unpackResult :: SimpleItem -> [B.Output]+unpackResult item =+ let leafMap :: Map.Map String B.UninflectedDisjunction+ leafMap = Map.fromList . siLeaves $ item+ lookupOrBug :: NodeName -> B.UninflectedDisjunction+ lookupOrBug k = case Map.lookup k leafMap of+ Nothing -> geniBug $ "unpackResult : could not find node " ++ k+ Just w -> w+ derivation = siDerivation item+ paths = automatonPaths . listToSentenceAut $+ [ lookupOrBug k | (k,_) <- (preTerminals . siDerived) item ]+ in zip paths (repeat derivation)+\end{code}++\subsection{Sentence automata}++\fnlabel{listToSentenceAut} converts a list of GNodes into a sentence+automaton. It's a actually pretty stupid conversion in fact. We pretty+much make a straight path through the automaton, with the only+cleverness being that we provide a different transition for each+atomic disjunction.++\begin{code}+listToSentenceAut :: [ B.UninflectedDisjunction ] -> B.SentenceAut+listToSentenceAut nodes =+ let theStart = 0+ theEnd = (length nodes) - 1+ theStates = [theStart..theEnd]+ --+ emptyAut = NFA+ { startSt = theStart+ , isFinalSt = Nothing+ , finalStList = [theEnd]+ , states = [theStates]+ , transitions = Map.empty }+ -- create a transition for each lexeme in the node to the+ -- next state...+ helper :: (Int, B.UninflectedDisjunction) -> B.SentenceAut -> B.SentenceAut+ helper (current, (lemmas, features)) aut =+ foldl' addT aut lemmas+ where+ addT a t = addTrans a current (Just (t, features)) next+ next = current + 1+ --+ in foldr helper emptyAut (zip theStates nodes)+\end{code}++% --------------------------------------------------------------------+\section{Partial results}+% --------------------------------------------------------------------++The user may ask for partial results when realisation fails. We implement this+using a greedy, full-commitment algorithm. Find the discarded result that+matches the largest part of the semantics and output that fragment. If there+are parts of the input semantics not covered by that fragment, search for the+largest chunk that covers the missing semantics. Recurse until there are no+more eligible items.++\begin{code}+partialResults :: SimpleStatus -> [SimpleItem]+#ifndef DISABLE_GUI+partialResults st = unfoldr getNext 0+ where+ inputsem = tsem st+ trash = theTrash st+ trashC = sortBy (comparing $ negate . fst) $+ map (\t -> (coverage inputsem t, t)) trash+ getNext sem = case getItems sem of+ [] -> Nothing+ (it:_) -> Just (it, siSemantics it .|. sem)+ getItems sem = [ i | (_,i) <- trashC, siSemantics i .&. sem == 0 ]++coverage :: BitVector -> SimpleItem -> Int+coverage sem it = countBits (sem .&. siSemantics it)++countBits :: Bits a => a -> Int+countBits 0 = 0+countBits bs = if testBit bs 0 then 1 + next else next+ where next = countBits (shiftR bs 1)+#else+partialResults = return []+#endif+\end{code}+
+ src/NLP/GenI/Simple/SimpleGui.lhs view
@@ -0,0 +1,200 @@+% GenI surface realiser+% Copyright (C) 2005 Carlos Areces and Eric Kow+%+% This program is free software; you can redistribute it and/or+% modify it under the terms of the GNU General Public License+% as published by the Free Software Foundation; either version 2+% of the License, or (at your option) any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; if not, write to the Free Software+% Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.++\chapter{Simple GUI}++\begin{code}+{-# OPTIONS_GHC -fno-warn-orphans #-}+module NLP.GenI.Simple.SimpleGui where+\end{code}++\ignore{+\begin{code}+import Graphics.UI.WX++import Data.IORef+import qualified Data.Map as Map++import NLP.GenI.Statistics (Statistics)++import NLP.GenI.Btypes (GNode(gnname, gup), emptyGNode, GeniVal(GConst))+import NLP.GenI.Configuration ( Params(..) )+import NLP.GenI.General ( snd3 )+import NLP.GenI.Geni ( ProgStateRef, runGeni, GeniResult )+import NLP.GenI.Graphviz ( GraphvizShow(..), gvNewline, gvUnlines )+import NLP.GenI.GuiHelper+ ( messageGui, tagViewerGui,+ debuggerPanel, DebuggerItemBar, setGvParams, GvIO, newGvRef,+ viewTagWidgets, XMGDerivation(getSourceTrees),+ )+import NLP.GenI.Tags (tsemantics, TagElem(idname, ttree), TagItem(..), emptyTE)+import NLP.GenI.GraphvizShow ( graphvizShowDerivation )++import qualified NLP.GenI.Builder as B+import qualified NLP.GenI.BuilderGui as BG+import NLP.GenI.Polarity+import NLP.GenI.Simple.SimpleBuilder+ ( simpleBuilder, SimpleStatus, SimpleItem(..), SimpleGuiItem(..)+ , unpackResult+ , theResults, theAgenda, theAuxAgenda, theChart, theTrash)+\end{code}+}++% --------------------------------------------------------------------+\section{Interface}+% --------------------------------------------------------------------++\begin{code}+simpleGui_2p, simpleGui_1p :: BG.BuilderGui+simpleGui_2p = simpleGui True+simpleGui_1p = simpleGui False++simpleGui :: Bool -> BG.BuilderGui+simpleGui twophase = BG.BuilderGui {+ BG.resultsPnl = resultsPnl twophase+ , BG.debuggerPnl = simpleDebuggerTab twophase }++resultsPnl :: Bool -> ProgStateRef -> Window a -> IO ([GeniResult], Statistics, Layout)+resultsPnl twophase pstRef f =+ do (sentences, stats, st) <- runGeni pstRef (simpleBuilder twophase)+ (lay, _, _) <- realisationsGui pstRef f (theResults st)+ return (sentences, stats, lay)+\end{code}++% --------------------------------------------------------------------+\section{Results}+\label{sec:results_gui}+% --------------------------------------------------------------------++\subsection{Derived Trees}++Browser for derived/derivation trees, except if there are no results, we show a+message box++\begin{code}+realisationsGui :: ProgStateRef -> (Window a) -> [SimpleItem]+ -> GvIO Bool (Maybe SimpleItem)+realisationsGui _ f [] =+ do m <- messageGui f "No results found"+ g <- newGvRef False [] ""+ return (m, g, return ())+realisationsGui pstRef f resultsRaw =+ do let tip = "result"+ itNlabl = map (\t -> (Just t, siToSentence t)) resultsRaw+ --+ pst <- readIORef pstRef+ -- FIXME: have to show the semantics again+ tagViewerGui pst f tip "derived" itNlabl+\end{code}++% --------------------------------------------------------------------+\section{Debugger}+\label{sec:simple_debugger_gui}+\label{fn:simpleDebugGui}+% --------------------------------------------------------------------++\begin{code}+simpleDebuggerTab :: Bool -> (Window a) -> Params -> B.Input -> String -> IO Layout+simpleDebuggerTab twophase x1 (pa@x2) =+ debuggerPanel (simpleBuilder twophase) False stToGraphviz (simpleItemBar pa)+ x1 x2+ +stToGraphviz :: SimpleStatus -> [(Maybe SimpleItem, String)]+stToGraphviz st = + let agenda = section "AGENDA" $ theAgenda st+ auxAgenda = section "AUXILIARY" $ theAuxAgenda st+ trash = section "TRASH" $ theTrash st+ chart = section "CHART" $ theChart st+ results = section "RESULTS" $ theResults st+ --+ section n i = hd : (map tlFn i)+ where hd = (Nothing, "___" ++ n ++ "___")+ tlFn x = (Just x, siToSentence x ++ (showPaths $ siPolpaths x))+ showPaths t = " (" ++ showPolPaths t ++ ")"+ in concat [ agenda, auxAgenda, chart, trash, results ]++simpleItemBar :: Params -> DebuggerItemBar Bool SimpleItem+simpleItemBar pa f gvRef updaterFn =+ do ib <- panel f []+ detailsChk <- checkBox ib [ text := "Show features"+ , checked := False ]+ viewTagLay <- viewTagWidgets ib gvRef pa+ -- handlers+ let onDetailsChk = + do isDetailed <- get detailsChk checked + setGvParams gvRef isDetailed+ updaterFn+ set detailsChk [ on command := onDetailsChk ]+ --+ return . hfloatCentre . (container ib) . row 5 $+ [ hspace 5+ , widget detailsChk+ , hglue+ , viewTagLay+ , hspace 5 ]+\end{code}++% --------------------------------------------------------------------+\section{Miscellaneous}+% -------------------------------------------------------------------++\begin{code}+instance TagItem SimpleItem where+ tgIdName = siIdname.siGuiStuff+ tgIdNum = siId+ tgSemantics = siFullSem.siGuiStuff++instance XMGDerivation SimpleItem where+ -- Note: this is XMG-related stuff+ getSourceTrees it = tgIdName it : (map snd3 . siDerivation $ it)+\end{code}++\begin{code}+instance GraphvizShow Bool SimpleItem where+ graphvizLabel f c =+ graphvizLabel f (toTagElem c) ++ gvNewline ++ (gvUnlines $ siDiagnostic $ siGuiStuff c)++ graphvizParams f c = graphvizParams f (toTagElem c)+ graphvizShowAsSubgraph f p it =+ let isHiglight n = gnname n `elem` (siHighlight.siGuiStuff) it+ info n | isHiglight n = (n, Just "red")+ | otherwise = (n, Nothing)+ in "\n// ------------------- elementary tree --------------------------\n"+ ++ graphvizShowAsSubgraph (f, info) (p ++ "TagElem") (toTagElem it)+ ++ "\n// ------------------- derivation tree --------------------------\n"+ -- derivation tree is displayed without any decoration+ ++ (graphvizShowDerivation . siDerivation $ it)++toTagElem :: SimpleItem -> TagElem+toTagElem si =+ emptyTE { idname = tgIdName si+ , tsemantics = tgSemantics si+ , ttree = fmap lookupOrBug (siDerived si) }+ where+ nodes = siNodes.siGuiStuff $ si+ nodeMap = Map.fromList $ zip (map gnname nodes) nodes+ lookupOrBug k = case Map.lookup k nodeMap of+ Nothing -> emptyGNode { gup = [ ("cat",GConst ["error looking up " ++ k]) ] }+ Just x -> x+\end{code}++\begin{code}+siToSentence :: SimpleItem -> String+siToSentence si = case unpackResult si of+ [] -> siIdname.siGuiStuff $ si+ (h:_) -> unwords . map fst . fst $ h+\end{code}
+ src/NLP/GenI/Statistics.hs view
@@ -0,0 +1,171 @@+{-# LANGUAGE FlexibleContexts #-}+----------------------------------------------------+-- --+-- Statistics.hs: --+-- Functions that collect and print out --+-- statistics --+-- --+----------------------------------------------------++{-+Copyright (C) GenI 2002-2005 (originally from HyLoRes)+Carlos Areces - areces@loria.fr - http://www.loria.fr/~areces+Daniel Gorin - dgorin@dc.uba.ar+Juan Heguiabehere - juanh@inf.unibz.it - http://www.inf.unibz.it/~juanh/+Eric Kow - kow@loria.fr - http://www.loria.fr/~kow++This program is free software; you can redistribute it and/or+modify it under the terms of the GNU General Public License+as published by the Free Software Foundation; either version 2+of the License, or (at your option) any later version.++This program is distributed in the hope that it will be useful,+but WITHOUT ANY WARRANTY; without even the implied warranty of+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+GNU General Public License for more details.++You should have received a copy of the GNU General Public License+along with this program; if not, write to the Free Software+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,+USA.+-}++module NLP.GenI.Statistics(Statistics, StatisticsState, StatisticsStateIO,+ emptyStats,++ printOutAllMetrics, printOutAllMetrics', printOutInspectionMetrics,+ showFinalStats,++ initialStatisticsStateFor,+ addMetric, addInspectionMetric, setPrintOutInterval,+ mergeMetrics,++ Metric(IntMetric), queryMetrics, updateMetrics,+ incrIntMetric, queryIntMetric, addIntMetrics,+) where++import Control.Monad.State+import Data.Maybe (mapMaybe)+import Data.List (intersperse)++-------------------------------------------+-- Statistics are collections of Metrics+-- which can be printed out (at regular intervals)+-------------------------------------------+data Statistics = Stat{metrics::[Metric],+ inspectionMetrics::[Metric],+ count::Int,+ step::Maybe Int}++type StatisticsState a = forall m. (MonadState Statistics m) => m a+type StatisticsStateIO a = forall m. (MonadState Statistics m, MonadIO m) => m a++updateMetrics :: (Metric -> Metric) -> Statistics -> Statistics+updateMetrics f stat = stat{metrics = map f (metrics stat),+ inspectionMetrics = map f (inspectionMetrics stat)}++queryMetrics :: (Metric -> Maybe a) -> Statistics -> [a]+queryMetrics f stat = (mapMaybe f (metrics stat))+ ++ (mapMaybe f (inspectionMetrics stat))++mergeMetrics :: (Metric -> Metric -> Metric) -> Statistics -> Statistics -> Statistics+mergeMetrics f s1 s2 = s1 { metrics = zipWith f (metrics s1) (metrics s2)+ , inspectionMetrics = zipWith f (inspectionMetrics s1) (inspectionMetrics s2)}++--updateStep :: Statistics -> Statistics+--updateStep s@(Stat _ [] _ _) = s+--updateStep s@(Stat _ _ _ Nothing) = s+--updateStep stat = stat{count = (count stat)+1}++needsToPrintOut :: Statistics -> Bool+needsToPrintOut (Stat _ [] _ _) = False+needsToPrintOut (Stat _ _ _ Nothing) = False+needsToPrintOut (Stat _ _ iter (Just toi)) = iter > 0 && iter `mod` toi == 0++noStats :: Statistics -> Bool+noStats (Stat [] [] _ _) = True+noStats _ = False++emptyStats :: Statistics+emptyStats = Stat{metrics=[],+ inspectionMetrics=[],+ count=0,+ step=Nothing}++--------------------------- Monadic Statistics functions follow ------------------------------+++initialStatisticsStateFor :: (MonadState Statistics m) => (m a -> Statistics -> b) -> m a -> b+initialStatisticsStateFor f = flip f emptyStats++{- | Adds a metric at the end of the list (thus,+ metrics are printed out in the order in which they were added -}+addMetric :: Metric -> StatisticsState ()+addMetric newMetric = modify (\stat -> stat{metrics = (metrics stat)++[newMetric]})++{- | Adds a metric that will be printed out at regular intervals -}+addInspectionMetric :: Metric -> StatisticsState ()+addInspectionMetric newMetric = modify (\stat -> stat{inspectionMetrics = (inspectionMetrics stat)++[newMetric]})++setPrintOutInterval :: Int -> StatisticsState ()+setPrintOutInterval i = modify (resetInterval i)+ where resetInterval 0 stat = stat{step = Nothing}+ resetInterval x stat = stat{step = Just x}++printOutAllMetrics :: StatisticsStateIO ()+printOutAllMetrics = get >>= (liftIO . printOutAllMetrics')++printOutAllMetrics' :: Statistics -> IO ()+printOutAllMetrics' stats =+ do+ unless (noStats stats) $ do+ liftIO $ putStrLn "(final statistics)"+ liftIO $ printOutList (inspectionMetrics stats ++ metrics stats)++printOutInspectionMetrics :: StatisticsStateIO ()+printOutInspectionMetrics = do+ shouldPrint <- gets needsToPrintOut+ when ( shouldPrint ) $ do+ liftIO $ putStr "(partial statistics: iteration "+ iter <- gets count+ liftIO . putStr . show $ iter+ liftIO $ putStrLn ")"+ ims <- gets inspectionMetrics+ liftIO $ printOutList ims+++printOutList :: Show a => [a] -> IO ()+printOutList ms = unless ( null ms ) $ do+ let separator = "\n----------------------------------\n"+ putStr "begin"+ putStr separator+ putStr $ concat $ intersperse separator $ map show ms+ putStr separator+ putStr "end\n"++showFinalStats :: Statistics -> String+showFinalStats stats = unlines $ map show $ metrics stats++--------------------------------------------+-- Metrics+--------------------------------------------+data Metric = IntMetric String Int++instance Show Metric where+ show (IntMetric s x) = s ++ " : " ++ (show x)++incrIntMetric :: String -> Int -> Metric -> Metric+incrIntMetric key i (IntMetric s c) | s == key = IntMetric s (c+i)+incrIntMetric _ _ m = m++queryIntMetric :: String -> Metric -> Maybe Int+queryIntMetric key (IntMetric s c) | s == key = Just c+queryIntMetric _ _ = Nothing++addIntMetrics :: Metric -> Metric -> Metric+addIntMetrics (IntMetric s1 c1) (IntMetric s2 c2) | s1 == s2 = IntMetric s1 (c1 + c2)+addIntMetrics s1 _ = s1++-- ratio :: Int -> Int -> Float+-- ratio x y = (fromIntegral x) / (fromIntegral y)+
+ src/NLP/GenI/SysGeni.lhs view
@@ -0,0 +1,93 @@+% GenI surface realiser+% Copyright (C) 2005 Carlos Areces and Eric Kow+%+% This program is free software; you can redistribute it and/or+% modify it under the terms of the GNU General Public License+% as published by the Free Software Foundation; either version 2+% of the License, or (at your option) any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; if not, write to the Free Software+% Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.++\chapter{SysGeni}++The SysGeni module mainly exists for running GenI as an application bundle+under MacOS X. We mostly re-export stuff from System.Process, but if we +are in a MacOS X application bundle, then we add \verb!../Resources/bin!+to the path for all the random crap that we ship with with GenI.++\begin{code}+{-# LANGUAGE ForeignFunctionInterface #-}+module NLP.GenI.SysGeni+where+\end{code}++\ignore{+\begin{code}+import qualified System.Process as S++import Data.List (isSuffixOf)+import System.FilePath+import System.IO (Handle)+import System.Exit (ExitCode)++#ifdef __GLASGOW_HASKELL__+import Foreign+import Foreign.C+import Control.Monad+#include "ghcconfig.h"+#endif+\end{code}+}++\section{Running a process}++\begin{code}+waitForProcess :: S.ProcessHandle -> IO ExitCode+waitForProcess = S.waitForProcess+\end{code}++But one thing special we need to do for Macs is to detect if we're+running from an application bundle. If we are, we assume that any+processes we want to run are in \texttt{../Resources/bin}.++\begin{code}+runInteractiveProcess :: String -> [String]+ -> Maybe FilePath+ -> Maybe [(String, String)]+ -> IO (Handle, Handle, Handle, S.ProcessHandle)+runInteractiveProcess cmd args x y = do+ dirname <- getProgDirName+ -- detect if we're in an .app bundle, i.e. if + -- we are running from something.app/Contents/MacOS+ let appBundle = ".app/Contents/MacOS/"+ resBinCmd = "../Resources/bin" </> cmd+ -- if we're in an .app bundle, we should prefix the+ -- path with ../Resources/bin+ let cmd2 = if appBundle `isSuffixOf` dirname + then resBinCmd else cmd+ S.runInteractiveProcess cmd2 args x y +\end{code}++\paragraph{Process helpers}++\begin{code}+foreign import ccall unsafe "getProgArgv"+ getProgArgv :: Ptr CInt -> Ptr (Ptr CString) -> IO ()++getProgDirName :: IO String+getProgDirName = + alloca $ \ p_argc ->+ alloca $ \ p_argv -> do+ getProgArgv p_argc p_argv+ argv <- peek p_argv+ s <- peekElemOff argv 0 >>= peekCString+ return $ takeDirectory s+\end{code}+
+ src/NLP/GenI/Tags.lhs view
@@ -0,0 +1,332 @@+% GenI surface realiser+% Copyright (C) 2005 Carlos Areces and Eric Kow+%+% This program is free software; you can redistribute it and/or+% modify it under the terms of the GNU General Public License+% as published by the Free Software Foundation; either version 2+% of the License, or (at your option) any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; if not, write to the Free Software+% Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.++\chapter{Tags}+\label{cha:Tags}++This module provides basic datatypes specific to Tree Adjoining Grammar+(TAG) and some low-level operations. Note that we don't handle+substitution and adjunction here; see sections \ref{sec:substitution}+and \ref{sec:adjunction} instead.++\begin{code}+module NLP.GenI.Tags(+ -- Main Datatypes+ Tags, TagElem(..), TagItem(..), TagSite(..),+ TagDerivation, emptyTE,+ ts_synIncomplete, ts_semIncomplete, ts_tbUnificationFailure,+ ts_rootFeatureMismatch,++ -- Functions from Tags+ addToTags, tagLeaves,++ -- Functions from TagElem+ setTidnums, ++ -- General functions+ mapBySem, subsumedBy, showTagSites,+ collect, detectSites+) where+\end{code}++\ignore{+\begin{code}+import qualified Data.Map as Map+import Data.Maybe (fromMaybe, listToMaybe, mapMaybe)+import Data.List (intersperse)+import Data.Tree++import NLP.GenI.Btypes (Ptype(Initial, Auxiliar), SemPols,+ GeniVal(GConst),+ GNode(gup, glexeme, gnname, gaconstr, gdown, gtype, gorigin),+ GType(Subs), Flist,+ Replacable(..), replaceOneAsMap,+ Collectable(..), Idable(..),+ Sem, Pred, emptyPred, + emptyGNode,+ showFlist, showPairs, showSem, lexemeAttributes,+ )+import NLP.GenI.General (groupByFM, preTerminals)+\end{code}+}++% ----------------------------------------------------------------------+\section{Tags}+% ----------------------------------------------------------------------++\begin{code}+-- | An anchored grammar.+-- The grammar associates a set of semantic predicates to a list of trees each.+type Tags = Map.Map String [TagElem] ++-- | 'addTags' @tags key elem@ adds @elem@ to the the list of elements associated+-- to the key+addToTags :: Tags -> String -> TagElem -> Tags+addToTags t k e = Map.insertWith (++) k [e] t+\end{code}++% ----------------------------------------------------------------------+\section{TagElem}+% ----------------------------------------------------------------------++Final types used for the combined macros + lexicon. We assume that+a two trees are the same iff they have the same tidnum. To make this+work, we assign each tree with a unique id during the process of+combining macros with lexicon (see section \ref{sec:combine_macros}).++\begin{code}+data TagSite = TagSite { tsName :: !String+ , tsUp :: !Flist+ , tsDown :: !Flist+ , tsOrigin :: !String+ }+ deriving (Show, Eq, Ord)++data TagElem = TE {+ idname :: String,+ ttreename :: String,+ tidnum :: Integer,+ ttype :: !Ptype,+ ttree :: Tree GNode,+ tsemantics :: Sem,+ -- optimisation stuff+ -- (polarity key to charge interval)+ tpolarities :: Map.Map String (Int,Int), + tinterface :: Flist, -- for idxconstraints (pol)+ ttrace :: [String],+ tsempols :: [SemPols]+ }+ deriving (Show, Eq)+\end{code}++A TAG derivation history consists of a list of 3-tuples representing the+operation (s for substitution, a for adjunction), the name of the child tree,+the name of the parent tree and the node affected.++\begin{code}+type TagDerivation = [ (Char, String, (String, String)) ]+\end{code}++\begin{code}+instance Ord TagElem where+ compare t1 t2 = + case (ttype t1, ttype t2) of+ (Initial, Initial) -> compareId + (Initial, Auxiliar) -> LT+ (Auxiliar, Initial) -> GT+ (Auxiliar, Auxiliar) -> compareId + _ -> error "TagElem compare not exhaustively defined"+ where compareId = compare (tidnum t1) (tidnum t2)++instance Replacable TagElem where+ replaceMap s te =+ te { tinterface = replaceMap s (tinterface te)+ , ttree = replaceMap s (ttree te)+ , tsemantics = replaceMap s (tsemantics te) }+ replaceOne = replaceOneAsMap++instance Replacable TagSite where+ replaceMap s (TagSite n fu fd o) = TagSite n (replaceMap s fu) (replaceMap s fd) o+ replaceOne s (TagSite n fu fd o) = TagSite n (replaceOne s fu) (replaceOne s fd) o++instance Collectable TagElem where+ collect t = (collect $ tinterface t) . (collect $ ttree t) + . (collect $ tsemantics t)++instance Idable TagElem where+ idOf = tidnum+\end{code}++\begin{code}+emptyTE :: TagElem+emptyTE = TE { idname = "",+ ttreename = "",+ tidnum = -1,+ ttype = Initial,+ ttree = Node emptyGNode [],+ tsemantics = [], + tpolarities = Map.empty,+ tsempols = [],+ tinterface = [],+ ttrace = []+ }++-- | Given a tree(GNode) returns a list of substitution or adjunction+-- nodes, as well as remaining nodes with a null adjunction constraint.+detectSites :: Tree GNode -> ([TagSite], [TagSite], [TagSite])+detectSites t =+ ( sites isSub -- for substitution+ , sites (not.gaconstr) -- for adjunction+ , sites constrButNotSub -- for neither+ )+ where+ ns = flatten t+ sites match = [ TagSite (gnname n) (gup n) (gdown n) (gorigin n) | n <- ns, match n ]+ isSub n = gtype n == Subs+ constrButNotSub n = gaconstr n && (not $ isSub n)+\end{code}++\subsection{Unique ID}++TagElem comparison relies exclusively on \fnparam{tidnum}, so you must+ensure that every TagElem you use has a unique ID. We provide two+helpful functions for this. These are most likely useful \emph{between}+lexical selection and generation proper, because during generation+proper, you can simply keep a counter within a State monad to assign+unique IDs to new TagElems.++Note that we also label each node of the tree with its elementary tree+name and with the unique ID. This helps us to build derivation trees+correctly++\begin{code}+-- | Assigns a unique id to each element of this list, that is, an integer+-- between 1 and the size of the list.+setTidnums :: [TagElem] -> [TagElem]+setTidnums xs = zipWith (\c i -> setOrigin $ c {tidnum = i}) xs [1..]++setOrigin :: TagElem -> TagElem+setOrigin te = te { ttree = fmap setLabel . ttree $ te }+ where setLabel g = g { gorigin = idname te ++ ":" ++ (show.tidnum) te }+\end{code}++% ----------------------------------------------------------------------+\section{TAG Item}+% ----------------------------------------------------------------------++\begin{code}+-- | 'TagItem' is a generalisation of 'TagElem'.+class TagItem t where + tgIdName :: t -> String+ tgIdNum :: t -> Integer+ tgSemantics :: t -> Sem++instance TagItem TagElem where+ tgIdName = idname+ tgIdNum = tidnum+ tgSemantics = tsemantics+\end{code}++% ----------------------------------------------------------------------+\section{Map by sem}+% ----------------------------------------------------------------------++\begin{code}+-- | Sorts trees into a Map.Map organised by the first literal of their+-- semantics. This is useful in at least three places: the polarity+-- optimisation, the gui display code, and code for measuring the efficiency+-- of GenI. Note: trees with a null semantics are filed under an empty+-- predicate, if any.+mapBySem :: (TagItem t) => [t] -> Map.Map Pred [t]+mapBySem ts = + let gfn t = case tgSemantics t of+ [] -> emptyPred+ (x:_) -> x+ in groupByFM gfn ts++-- | 'subsumedBy' @cs ts@ determines if the candidate semantics @cs@ is+-- subsumed by the proposition semantics @ts@. Notice how the proposition+-- semantics is only a single item where as the candidate semantics is a+-- list.+--+-- We assume+--+-- * most importantly that @cs@ has already its semantics instatiated+-- (all variables assigned)+--+-- * @cs@ and @ts@ are sorted+--+-- * the list in each element of cs and ts is itself sorted +subsumedBy :: Sem -> Pred -> Bool +subsumedBy [] _ = False +subsumedBy ((ch, cp, cla):cl) (th, tp,tla)+ | (ch == th) && (cp == tp) && (cla == tla) = True + -- if we haven't yet overshot, try for the next one+ | cp < tp = subsumedBy cl (th, tp, tla)+ | otherwise = False+\end{code}++% ----------------------------------------------------------------------+\section{Extracting sentences}+% ----------------------------------------------------------------------++Normally, extracting the sentences from a TAG tree would just consist of+reading its leaves. But if you want the generator to return inflected+forms instead of just lemmas, you also need to return the relevant+features for each leaf. In TAG, or at least our use of it, the features+come from the \emph{pre-terminal} nodes, that is, not the leaves+themselves but their parents. Another bit of trickiness: because of+atomic disjunction, leaves might have more than one value, so we can't+just return a String lemma but a list of String, one for each+possibility.++\begin{code}+type UninflectedDisjunction = ([String], Flist)++tagLeaves :: TagElem -> [ (String, UninflectedDisjunction) ]+tagLeaves te = [ (gnname pt, (getLexeme t, gup pt)) | (pt,t) <- preTerminals . ttree $ te ]++-- | Try in order: lexeme, lexeme attributes, node name+getLexeme :: GNode -> [String]+getLexeme node =+ case glexeme node of+ [] -> fromMaybe [gnname node] $ firstMaybe grab lexemeAttributes+ lexs -> lexs+ where+ grab la =+ let match (a, (GConst v)) | a == la = Just v+ match _ = Nothing+ in firstMaybe match guppy+ guppy = gup node++firstMaybe :: (a -> Maybe b) -> [a] -> Maybe b+firstMaybe fn = listToMaybe . mapMaybe fn++\end{code}++% ----------------------------------------------------------------------+\section{Debugging}+% ----------------------------------------------------------------------++\begin{code}+-- Useful for debugging adjunction and substitution nodes+showTagSites :: [TagSite] -> String+showTagSites sites = concat $ intersperse "\n " $ map fn sites+ where+ fn (TagSite n t b o) =+ concat . intersperse "/" $ [ n, showPairs t, showPairs b, o ]+\end{code}++% ----------------------------------------------------------------------+\section{Diagnostic messages}+% ----------------------------------------------------------------------++Diagnostic messages let us know why a TAG tree is not returned as a result.+Whenever GenI decides to discard a tree, it sets the tdiagnostic field of +the TagElem so that the person using a debugger can find out what went wrong.++\begin{code}+ts_synIncomplete, ts_tbUnificationFailure :: String+ts_synIncomplete = "syntactically incomplete"+ts_tbUnificationFailure = "top/bot unification failure"++ts_rootFeatureMismatch :: Flist -> String+ts_rootFeatureMismatch good = "root feature does not unify with " ++ showFlist good++ts_semIncomplete :: [Pred] -> String+ts_semIncomplete sem = "semantically incomplete - missing: " ++ showSem sem+\end{code}
+ src/NLP/GenI/Test.hs view
@@ -0,0 +1,36 @@+-- ----------------------------------------------------------------------+-- GenI surface realiser+-- Copyright (C) 2009 Eric Kow+--+-- This program is free software; you can redistribute it and/or+-- modify it under the terms of the GNU General Public License+-- as published by the Free Software Foundation; either version 2+-- of the License, or (at your option) any later version.+--+-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; if not, write to the Free Software+-- Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.+-- ----------------------------------------------------------------------++-- TODO: use somebody else's test framework... do not let this grow into a+-- custom monstrosity.++module NLP.GenI.Test where++import Test.QuickCheck ( quickCheck )+import NLP.GenI.Btypes++runTests :: IO ()+runTests =+ do putStrLn $ header "unification"+ putStrLn "unification is symmetrical" >> quickCheck prop_unify_sym+ putStrLn "everything unifies with underscore" >> quickCheck prop_unify_anon+ putStrLn "everything unifies with itself" >> quickCheck prop_unify_self+ where+ bar = replicate 72 '='+ header x = unlines [bar,x,bar]
+ src/NLP/GenI/unused/Predictors.lhs view
@@ -0,0 +1,315 @@+% GenI surface realiser+% Copyright (C) 2005 Carlos Areces and Eric Kow+%+% This program is free software; you can redistribute it and/or+% modify it under the terms of the GNU General Public License+% as published by the Free Software Foundation; either version 2+% of the License, or (at your option) any later version.+%+% This program is distributed in the hope that it will be useful,+% but WITHOUT ANY WARRANTY; without even the implied warranty of+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+% GNU General Public License for more details.+%+% You should have received a copy of the GNU General Public License+% along with this program; if not, write to the Free Software+% Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.++\chapter{Predictor Optimisation}++One optimisation is to annotate the macros with a set of+\jargon{predictors}. This allows macros to predict that they will+combine with certain (usually) null-semantic trees. For example, a+common noun would predict that it needs a determiner. ++\begin{code}+module Predictors +where+\end{code}++\begin{code}+import Debug.Trace+import qualified Data.Map as Map+import Data.List (nub, sortBy, groupBy, intersect)+import Monad (when, ap, foldM)+import MonadState (get, put)++import Bfuncs (Sem, Flist, AvPair, showSem, showAv, isVar)+import Tags (TagElem(TE), emptyTE, idname, tsemantics, substnodes, + derivation, tpredictors, drawTagTrees)+import Configuration (defaultParams)+import Mstate (MS, Gstats, initGstats, addGstats, initMState,+ runState, genstats, + incrNumcompar, incrSzchart, incrGeniter,+ renameTagElem,+ addToInitRep, + getGenRep, lookupGenRep, genRepToList, addListToGenRep,+ iapplySubstNode,+ nullInitRep, getInitRep, genrep, getSem, selectGiven)++import Polarity (showLite)+\end{code}++% ----------------------------------------------------------------------+\section{Optimisation}+% ----------------------------------------------------------------------++We attempt substitution between macro and any predictors that it has.+Whenever we succeed, we can pass the combined tree as a candidate.+Whenever we fail, we have to pass both the macro and its predictors.+This is basically an indirect means of adding some kind of indexing to+the generator's chart.++Note: there are actually two cases here. For those predictors that+we can substitute into the macro, we return the resulting tree and+discard the predictor. ++\begin{code}+optimisePredictors :: [[TagElem]] -> PredictorMap -> ([[TagElem]], Gstats)+optimisePredictors cands predictmap =+ let trees = nub $ concat cands + -- calculate predicted trees+ sumup = foldr addGstats initGstats + optTree t = optimisePredictors' predictmap t+ optPath p = (r, sumup s)+ where (r,s) = unzip $ map optTree p + (res, stats) = optPath trees+ treemap = Map.fromList $ zip trees res+ -- replace trees with their predicted equivalents+ repTree t = lookupWithDefaultFM treemap [t] t + repPath p = concatMap repTree p+ {- repPath p = trace ("\n==============\npath\n=============\n" ++ drawTagTrees l) l+ where l = repPath' p -}+ in (map repPath cands, stats)+\end{code}++\paragraph{optimisePredictors'} is a helper function that tries to+fulfill as many of a tree's predictors as possible. Any predictors+it cannot use are also returned so that they can be passed to the +generator proper.++\begin{code}+optimisePredictors' :: PredictorMap -> TagElem -> ([TagElem], Gstats)+optimisePredictors' predictmap te =+ let -- grab the predictors (helper fns)+ isneg _ e = e < 0 + predictors t = Map.keys $ filterFM isneg $ tpredictors t+ ptrees t = concatMap fn (predictors t)+ where fn = lookupWithDefaultFM predictmap []+ -- generate+ tePtrees = ptrees te+ initSt = initMState tePtrees [te] (tsemantics te) defaultParams+ (res', st) = runState miniGenerate initSt+ -- pick the trees with the largest derivation history+ derSz = length.snd.derivation+ cmpDerSz t1 t2 = compare (derSz t2) (derSz t1) -- note the inversion + sameDerSz t1 t2 = (derSz t2) == (derSz t1) + groupedres = groupBy sameDerSz $ sortBy cmpDerSz res' + -- return the results+ result = head groupedres -- trace ("\n==============\nresults for " ++ idname te ++ "\n=============\n" ++ drawTagTrees res) $ + rejects = concatMap ptrees result+ stats = genstats st+ --+ debugstr = "\n===================" + ++ "\noptimising " ++ showLite te + ++ "\nptrees: " ++ showLite (tePtrees)+ ++ "\n==================== "+ errormsg = "Geni: Predictors.optimisePredictors' is broken"+ in case () of _ | null tePtrees -> ([te], stats)+ | null groupedres -> error errormsg + | otherwise -> (result ++ rejects, stats)+\end{code}++% ----------------------------------------------------------------------+\subsection{miniGenerate}+% ----------------------------------------------------------------------++miniGenerate is a lightweight version of the generator which operates +on the following principles: ++\begin{enumerate}+\item There is one primary tree (chart) and some secondary trees + (agenda), which should not be confused with auxiliary trees+\item All operations are performed between the primary+ tree and the secondary trees, that is, you won't+ have any interaction between secondary trees+\item The primary tree may substitute with or be + substituted any number of secondary trees+\item Secondary trees may only be used once+\end{enumerate}++It is used as a helper function for optimisePredictors. ++\begin{code}+miniGenerate :: MS [TagElem]+miniGenerate = do + nir <- nullInitRep+ gr <- getGenRep+ if nir then return (concat $ elems gr) else do + incrGeniter 1+ tsem <- getSem+ -- choose a secondary tree from the agenda+ given <- selectGiven+ -- perform any substitutions + chart <- lookupGenRep given + let (res', cost') = unzip $ map (timidSubstitution given) chart+ res = concat res'+ cost = foldr (+) 0 cost' + incrSzchart (length res)+ incrNumcompar cost+ -- add any succesful results to the chart+ st <- get+ put st { genrep = addListToGenRep gr res }+ miniGenerate+\end{code}++\paragraph{timidSubstitution} attempts to perform substitution between+input trees $te_1$ and $te_2$. This is meant strictly to be a helper+function for optimisePredictors, so we'll have a somewhat conservative+and quirky behaviour:+\begin{itemize}+\item If there are no ways to perform substitution, we return the empty+list+\item If there is exactly one way to perform substitution+(either $te_1$ into $te_2$ or vice versa), we+return that substitution. +\item If there is more than one way to do it, we return the empty list.+This is because the situation is ambiguous and could lead to unpredictable+results (see section \ref{sec:optimisePredictors_tricky})+\end{itemize}++This is somewhat similar to MState's applySubstitution, except that we+rule out the case of multiple results, and that we do not require the+substitution nodes to be in any particular order.++\begin{code}+timidSubstitution :: TagElem -> TagElem -> ([TagElem],Int)+timidSubstitution te1 te2 = + let tesem = tsemantics te1+ -- we only substitute tags with no overlaping semantics+ notOverlap = null $ intersect (tsemantics te2) tesem+ -- we rename tags to do a proper substitution+ rte1 = renameTagElem 'A' te1+ rte2 = renameTagElem 'B' te2+ -- perform the substitution+ subst t1 t2 = concatMap (iapplySubstNode t1 t2) $ substnodes t2+ res' = (subst rte1 rte2) ++ (subst rte2 rte1)+ res = if (length res' == 1) then res' else []+ -- measuring efficiency+ cost = fn te1 + fn te2 + where fn t = length $ substnodes t + in if notOverlap then (res, cost) else ([], 0)+\end{code}++\subsection{Trickiness in optimisePredictors} +\label{sec:optimisePredictors_tricky}++Rejecting ambiguous substitutions is crucial to the idea that+secondary trees may only be used once.++Consider the trees for \textit{the N, enemy of N, friend}.+The idea is that we eventually want to generate \textit {the enemy of the+friend}, so the result of optimisePredictors should ideally be something like:+\textit{the friend, the enemy of N} ++But this isn't so easy to achieve. In fact, if we tried to achieve+the above result, we would instead get a highly undesirable result +like this \textit{the friend, the enemy of the N} ++Do you see why the above result is bad? It is because now there is+no way to substitute friend into that noun-substitution node. To+avoid this sort of over-ambitiousness, we avoid ambiguous cases where a +tree could both substitute into or be substituted into another. So we+get a less optimal, but much safer result \textit{the friend, enemy of, +the}:++% ----------------------------------------------------------------------+\section{Cleanup}+% ----------------------------------------------------------------------++\paragraph{fillPredictors} This is neccesary when either the+predictor optimisation is disabled or if there are some+predictor substitutions which do not succeed. It takes a list of paths+and inserts all required predictors on the paths.++\begin{code}+fillPredictors :: [[TagElem]] -> PredictorMap -> [[TagElem]]+fillPredictors paths predictmap =+ let isneg _ pol = pol < 0 + getP = lookupWithDefaultFM predictmap []+ predictors te = Map.keys $ filterFM isneg $ tpredictors te+ addP te = te : (concatMap getP $ predictors te)+ in map (\p -> nub $ concatMap addP p) paths+\end{code}++% --------------------------------------------------------------------+\section{Instatiation of predictors}+% --------------------------------------------------------------------++We combine the predictors from the lexicon and macros. The idea is+to do this in a way which lets the grammar writer be lazy while having+as simple and predictable a behaviour as possible. Any predictors that+the lexicon has must correspond to some variable predictor in the+macros, so if I say in the lexicon that a tree as predictor+$+vsup:avoir$ there had better be a $+vsup:X$ in the macros to back it+up.++\begin{code}+combinePredictors tt le = + let -- fn to add an item to the predictors map+ addP (fv,c) fm = addToFM_C (+) fm fv c+ -- lexicon predictors + lpr = sort $ ipredictors le+ -- tree predictors (variable vs constant predictors)+ tpr = sort $ ptpredictors tt+ isVpr ((f,v),_) = (not $ null v) && isVar v+ (varPr,constPr) = partition isVpr tpr+ constPrFm = foldr addP Map.empty constPr+ -- separating the charges from the fv+ (lfv, lc) = unzip lpr + (vfv, vc) = unzip varPr+ -- the unification+ unify [] [] = []+ unify ((tf,tv):tnext) ((lf,lv):lnext) + | tf /= lf = error errmsg+ | isVar lv = error errvlex+ | isVar tv = (lf,lv):(unify (substFlist' tnext (tv,lv)) lnext)+ | lv == tv = (lf,lv):(unify tnext lnext)+ | otherwise = error errmsg+ unification = unify vfv lfv + -- error messages in case things don't line up+ errmsg = "Word '" ++ (iword le) ++ "' does not correctly " + ++ " instantiate the variable predictors in tree " + ++ (itreename le) + ++ "\n Tree predictors: " ++ (show $ map fst varPr) + ++ "\n Word predictors: " ++ (show $ map fst lpr)+ ++ "\n Hint: only the variable predictors should be instantiated" + errvlex = "Word '" ++ (iword le) ++ "' contains variable " + ++ " predictors in " ++ (show $ map fst lpr)+ in if (lc == vc) -- note: this implies list equality+ then foldr addP constPrFm $ zip unification lc + else error errmsg+\end{code}+++% ----------------------------------------------------------------------+\section{PredictorMap}+% ----------------------------------------------------------------------++We create a map between predictors and the trees that provide them.++\begin{code}+type PredictorMap = Map AvPair [TagElem]+\end{code}++\begin{code}+mapByPredictors :: [TagElem] -> PredictorMap +mapByPredictors trees = foldr mapByPredictors' Map.empty trees ++mapByPredictors' :: TagElem -> PredictorMap -> PredictorMap +mapByPredictors' tree fm = + let ispos _ pol = (pol > 0)+ predictors = Map.keys $ filterFM ispos $ tpredictors tree+ addp p f = addToFM_C (++) f p [tree]+ in foldr addp fm predictors +\end{code}