GenI 0.17.4 → 0.20
raw patch · 52 files changed
+4364/−5431 lines, 52 filesdep +jsondep +paralleldep +splitdep ~HUnitdep ~basedep ~binarysetup-changedPVP ok
version bump matches the API change (PVP)
Dependencies added: json, parallel, split, test-framework, test-framework-hunit, test-framework-quickcheck, uniplate, utf8-string
Dependency ranges changed: HUnit, base, binary, containers, filepath, mtl, parsec, process
API changes (from Hackage documentation)
- NLP.GenI.Btypes: class Replacable a
- NLP.GenI.Btypes: instance [overlap ok] (Replacable a) => Replacable (Maybe a)
- NLP.GenI.Btypes: instance [overlap ok] (Replacable a) => Replacable (Tree a)
- NLP.GenI.Btypes: instance [overlap ok] (Replacable a) => Replacable (Ttree a)
- NLP.GenI.Btypes: instance [overlap ok] (Replacable a) => Replacable [a]
- NLP.GenI.Btypes: instance [overlap ok] Arbitrary GTestString
- NLP.GenI.Btypes: instance [overlap ok] Arbitrary GTestString2
- NLP.GenI.Btypes: instance [overlap ok] Arbitrary GeniVal
- NLP.GenI.Btypes: instance [overlap ok] Collectable (String, GeniVal)
- NLP.GenI.Btypes: instance [overlap ok] Data GeniVal
- NLP.GenI.Btypes: instance [overlap ok] Eq GeniVal
- NLP.GenI.Btypes: instance [overlap ok] Ord GeniVal
- NLP.GenI.Btypes: instance [overlap ok] Replacable (String, ([String], Flist))
- NLP.GenI.Btypes: instance [overlap ok] Replacable AvPair
- NLP.GenI.Btypes: instance [overlap ok] Replacable GNode
- NLP.GenI.Btypes: instance [overlap ok] Replacable GeniVal
- NLP.GenI.Btypes: instance [overlap ok] Replacable ILexEntry
- NLP.GenI.Btypes: instance [overlap ok] Replacable Pred
- NLP.GenI.Btypes: instance [overlap ok] Show GeniVal
- NLP.GenI.Btypes: instance [overlap ok] Typeable GeniVal
- NLP.GenI.Btypes: prop_unify_anon :: [GeniVal] -> Bool
- NLP.GenI.Btypes: prop_unify_self :: [GeniVal] -> Property
- NLP.GenI.Btypes: prop_unify_sym :: [GeniVal] -> [GeniVal] -> Property
- NLP.GenI.Btypes: replaceMap :: (Replacable a) => Map String GeniVal -> a -> a
- NLP.GenI.Btypes: replaceOne :: (Replacable a) => (String, GeniVal) -> a -> a
- NLP.GenI.Btypes: replaceOneAsMap :: (Replacable a) => (String, GeniVal) -> a -> a
- NLP.GenI.Btypes: type AvPair = (String, GeniVal)
- NLP.GenI.Builder: addCounters :: Statistics -> Statistics -> Statistics
- NLP.GenI.Builder: type UninflectedDisjunction = ([String], Flist)
- NLP.GenI.Builder: type UninflectedSentence = [UninflectedWord]
- NLP.GenI.Builder: type UninflectedWord = (String, Flist)
- NLP.GenI.CkyEarley.CkyBuilder: CkyItem :: GNode -> TagElem -> [GeniVal] -> BitVector -> BitVector -> Maybe ChartId -> BitVector -> ChartId -> RoutingMap -> [CkyItem] -> [GeniVal] -> SemBitMap -> TreeSide -> [String] -> [ChartOperation] -> [String] -> [String] -> [TagSite] -> CkyItem
- NLP.GenI.CkyEarley.CkyBuilder: S :: Agenda -> Chart -> Trash -> BitVector -> IafMap -> Integer -> Params -> [CKY_InferenceRule] -> (CkyItem -> CkyState (Maybe CkyItem)) -> [CkyItem] -> Integer -> CkyStatus
- NLP.GenI.CkyEarley.CkyBuilder: bitVectorToSem :: SemBitMap -> BitVector -> Sem
- NLP.GenI.CkyEarley.CkyBuilder: ciAccesible :: CkyItem -> [String]
- NLP.GenI.CkyEarley.CkyBuilder: ciAdjDone :: CkyItem -> Bool
- NLP.GenI.CkyEarley.CkyBuilder: ciAdjPoint :: CkyItem -> Maybe ChartId
- NLP.GenI.CkyEarley.CkyBuilder: ciDerivation :: CkyItem -> [ChartOperation]
- NLP.GenI.CkyEarley.CkyBuilder: ciDiagnostic :: CkyItem -> [String]
- NLP.GenI.CkyEarley.CkyBuilder: ciId :: CkyItem -> ChartId
- NLP.GenI.CkyEarley.CkyBuilder: ciInaccessible :: CkyItem -> [String]
- NLP.GenI.CkyEarley.CkyBuilder: ciInitialSem :: CkyItem -> BitVector
- NLP.GenI.CkyEarley.CkyBuilder: ciNode :: CkyItem -> GNode
- NLP.GenI.CkyEarley.CkyBuilder: ciOrigVariables :: CkyItem -> [GeniVal]
- NLP.GenI.CkyEarley.CkyBuilder: ciPayload :: CkyItem -> [CkyItem]
- NLP.GenI.CkyEarley.CkyBuilder: ciPolpaths :: CkyItem -> BitVector
- NLP.GenI.CkyEarley.CkyBuilder: ciRoot :: CkyItem -> Bool
- NLP.GenI.CkyEarley.CkyBuilder: ciRouting :: CkyItem -> RoutingMap
- NLP.GenI.CkyEarley.CkyBuilder: ciSemBitMap :: CkyItem -> SemBitMap
- NLP.GenI.CkyEarley.CkyBuilder: ciSemantics :: CkyItem -> BitVector
- NLP.GenI.CkyEarley.CkyBuilder: ciSourceTree :: CkyItem -> TagElem
- NLP.GenI.CkyEarley.CkyBuilder: ciSubstnodes :: CkyItem -> [TagSite]
- NLP.GenI.CkyEarley.CkyBuilder: ciTreeSide :: CkyItem -> TreeSide
- NLP.GenI.CkyEarley.CkyBuilder: ciVariables :: CkyItem -> [GeniVal]
- NLP.GenI.CkyEarley.CkyBuilder: ckyBuilder :: CkyBuilder
- NLP.GenI.CkyEarley.CkyBuilder: data CkyItem
- NLP.GenI.CkyEarley.CkyBuilder: data CkyStatus
- NLP.GenI.CkyEarley.CkyBuilder: earleyBuilder :: CkyBuilder
- NLP.GenI.CkyEarley.CkyBuilder: emptySentenceAut :: SentenceAut
- NLP.GenI.CkyEarley.CkyBuilder: extractDerivations :: CkyStatus -> CkyItem -> [Tree (ChartId, String)]
- NLP.GenI.CkyEarley.CkyBuilder: findId :: CkyStatus -> ChartId -> Maybe CkyItem
- NLP.GenI.CkyEarley.CkyBuilder: genAutCounter :: CkyStatus -> Integer
- NLP.GenI.CkyEarley.CkyBuilder: genconfig :: CkyStatus -> Params
- NLP.GenI.CkyEarley.CkyBuilder: gencounter :: CkyStatus -> Integer
- NLP.GenI.CkyEarley.CkyBuilder: instance [overlap ok] Eq TreeSide
- NLP.GenI.CkyEarley.CkyBuilder: instance [overlap ok] IafAble CkyItem
- NLP.GenI.CkyEarley.CkyBuilder: instance [overlap ok] Show CKY_InferenceRule
- NLP.GenI.CkyEarley.CkyBuilder: instance [overlap ok] Show ChartOperation
- NLP.GenI.CkyEarley.CkyBuilder: mAutomatonPaths :: (Ord st, Ord ab) => Maybe (NFA st ab) -> [[ab]]
- NLP.GenI.CkyEarley.CkyBuilder: mJoinAutomata :: Maybe SentenceAut -> Maybe SentenceAut -> Maybe SentenceAut
- NLP.GenI.CkyEarley.CkyBuilder: theAgenda :: CkyStatus -> Agenda
- NLP.GenI.CkyEarley.CkyBuilder: theChart :: CkyStatus -> Chart
- NLP.GenI.CkyEarley.CkyBuilder: theDispatcher :: CkyStatus -> CkyItem -> CkyState (Maybe CkyItem)
- NLP.GenI.CkyEarley.CkyBuilder: theIafMap :: CkyStatus -> IafMap
- NLP.GenI.CkyEarley.CkyBuilder: theResults :: CkyStatus -> [CkyItem]
- NLP.GenI.CkyEarley.CkyBuilder: theRules :: CkyStatus -> [CKY_InferenceRule]
- NLP.GenI.CkyEarley.CkyBuilder: theTrash :: CkyStatus -> Trash
- NLP.GenI.CkyEarley.CkyBuilder: tsemVector :: CkyStatus -> BitVector
- NLP.GenI.CkyEarley.CkyBuilder: type ChartId = Integer
- NLP.GenI.CkyEarley.CkyBuilder: unpackItemToAuts :: CkyStatus -> CkyItem -> SentenceAutPairMaybe
- NLP.GenI.Configuration: CkyBuilder :: BuilderType
- NLP.GenI.Configuration: EarleyBuilder :: BuilderType
- NLP.GenI.Configuration: IgnoreSemanticsFlg :: () -> IgnoreSemanticsFlg
- NLP.GenI.Configuration: MaxTreesFlg :: Int -> MaxTreesFlg
- NLP.GenI.Configuration: MorphLexiconFlg :: FilePath -> MorphLexiconFlg
- NLP.GenI.Configuration: RootCatFiltered :: Optimisation
- NLP.GenI.Configuration: data IgnoreSemanticsFlg
- NLP.GenI.Configuration: data MaxTreesFlg
- NLP.GenI.Configuration: data MorphLexiconFlg
- NLP.GenI.Configuration: instance [overlap ok] Eq IgnoreSemanticsFlg
- NLP.GenI.Configuration: instance [overlap ok] Eq MaxTreesFlg
- NLP.GenI.Configuration: instance [overlap ok] Eq MorphLexiconFlg
- NLP.GenI.Configuration: instance [overlap ok] Show IgnoreSemanticsFlg
- NLP.GenI.Configuration: instance [overlap ok] Show MaxTreesFlg
- NLP.GenI.Configuration: instance [overlap ok] Show MorphLexiconFlg
- NLP.GenI.Configuration: instance [overlap ok] Typeable IgnoreSemanticsFlg
- NLP.GenI.Configuration: instance [overlap ok] Typeable MaxTreesFlg
- NLP.GenI.Configuration: instance [overlap ok] Typeable MorphLexiconFlg
- NLP.GenI.Configuration: isIaf :: Params -> Bool
- NLP.GenI.Configuration: polarised :: Params -> Bool
- NLP.GenI.Configuration: rootcatfiltered :: Params -> Bool
- NLP.GenI.Configuration: semfiltered :: Params -> Bool
- NLP.GenI.Configuration: treatStandardArgs :: [String] -> IO Params
- NLP.GenI.Configuration: treatStandardArgsWithParams :: [String] -> Params -> IO Params
- NLP.GenI.General: comparing :: (Ord b) => (a -> b) -> (a -> a -> Ordering)
- NLP.GenI.General: equating :: (Eq b) => (a -> b) -> (a -> a -> Bool)
- NLP.GenI.General: wordsBy :: (Eq a) => a -> [a] -> [[a]]
- NLP.GenI.Geni: morphlex :: ProgState -> Maybe [(String, String, Flist)]
- NLP.GenI.Geni: type GeniResult = (String, Derivation)
- NLP.GenI.GeniParsers: geniMorphLexicon :: Parser [MorphLexEntry]
- NLP.GenI.Morphology: attachMorphHelper :: Flist -> TagElem -> TagElem
- NLP.GenI.Morphology: inflectSentenceUsingLex :: MorphLexicon -> [UninflectedDisjunction] -> [String]
- NLP.GenI.Morphology: inflectSentencesUsingLex :: MorphLexicon -> [[UninflectedDisjunction]] -> [[String]]
- NLP.GenI.Morphology: inflectWordUsingLex :: MorphLexicon -> UninflectedDisjunction -> [String]
- NLP.GenI.Morphology: singleton :: a -> [a]
- NLP.GenI.Morphology: type MorphLexicon = [(String, String, Flist)]
- NLP.GenI.Morphology: type UninflectedDisjunction = (String, Flist)
- NLP.GenI.Simple.SimpleBuilder: instance [overlap ok] Replacable SimpleItem
- NLP.GenI.Simple.SimpleBuilder: theAuxAgenda :: SimpleStatus -> AuxAgenda
- NLP.GenI.Statistics: addInspectionMetric :: Metric -> StatisticsState ()
- NLP.GenI.Statistics: addIntMetrics :: Metric -> Metric -> Metric
- NLP.GenI.Statistics: mergeMetrics :: (Metric -> Metric -> Metric) -> Statistics -> Statistics -> Statistics
- NLP.GenI.Statistics: printOutAllMetrics :: StatisticsStateIO ()
- NLP.GenI.Statistics: printOutAllMetrics' :: Statistics -> IO ()
- NLP.GenI.Statistics: printOutInspectionMetrics :: StatisticsStateIO ()
- NLP.GenI.Statistics: setPrintOutInterval :: Int -> StatisticsState ()
- NLP.GenI.Tags: instance [overlap ok] Replacable TagElem
- NLP.GenI.Tags: instance [overlap ok] Replacable TagSite
+ NLP.GenI.Btypes: AvPair :: String -> GeniVal -> AvPair
+ NLP.GenI.Btypes: avAtt :: AvPair -> String
+ NLP.GenI.Btypes: avVal :: AvPair -> GeniVal
+ NLP.GenI.Btypes: class DescendGeniVal a
+ NLP.GenI.Btypes: data AvPair
+ NLP.GenI.Btypes: descendGeniVal :: (DescendGeniVal a) => (GeniVal -> GeniVal) -> a -> a
+ NLP.GenI.Btypes: instance [overlap ok] (DescendGeniVal a) => DescendGeniVal (Map k a)
+ NLP.GenI.Btypes: instance [overlap ok] (DescendGeniVal a) => DescendGeniVal (String, a)
+ NLP.GenI.Btypes: instance [overlap ok] Biplate (Maybe Sem) GeniVal
+ NLP.GenI.Btypes: instance [overlap ok] Biplate (Tree GNode) GeniVal
+ NLP.GenI.Btypes: instance [overlap ok] Biplate (Ttree GNode) GeniVal
+ NLP.GenI.Btypes: instance [overlap ok] Biplate (Ttree String) GeniVal
+ NLP.GenI.Btypes: instance [overlap ok] Biplate AvPair GeniVal
+ NLP.GenI.Btypes: instance [overlap ok] Biplate GNode GeniVal
+ NLP.GenI.Btypes: instance [overlap ok] Biplate ILexEntry GeniVal
+ NLP.GenI.Btypes: instance [overlap ok] Biplate Pred GeniVal
+ NLP.GenI.Btypes: instance [overlap ok] Collectable AvPair
+ NLP.GenI.Btypes: instance [overlap ok] Data AvPair
+ NLP.GenI.Btypes: instance [overlap ok] DescendGeniVal (Ttree GNode)
+ NLP.GenI.Btypes: instance [overlap ok] DescendGeniVal ([String], Flist)
+ NLP.GenI.Btypes: instance [overlap ok] DescendGeniVal AvPair
+ NLP.GenI.Btypes: instance [overlap ok] DescendGeniVal GNode
+ NLP.GenI.Btypes: instance [overlap ok] DescendGeniVal ILexEntry
+ NLP.GenI.Btypes: instance [overlap ok] DescendGeniVal Pred
+ NLP.GenI.Btypes: instance [overlap ok] Eq AvPair
+ NLP.GenI.Btypes: instance [overlap ok] Ord AvPair
+ NLP.GenI.Btypes: instance [overlap ok] Show AvPair
+ NLP.GenI.Btypes: instance [overlap ok] Typeable AvPair
+ NLP.GenI.BtypesBinary: instance [overlap ok] Binary AvPair
+ NLP.GenI.Builder: LemmaPlus :: String -> Flist -> LemmaPlus
+ NLP.GenI.Builder: UninflectedDisjunction :: [String] -> Flist -> UninflectedDisjunction
+ NLP.GenI.Builder: data LemmaPlus
+ NLP.GenI.Builder: data UninflectedDisjunction
+ NLP.GenI.Builder: instance [overlap ok] Biplate UninflectedDisjunction GeniVal
+ NLP.GenI.Builder: instance [overlap ok] Collectable UninflectedDisjunction
+ NLP.GenI.Builder: instance [overlap ok] Data UninflectedDisjunction
+ NLP.GenI.Builder: instance [overlap ok] DescendGeniVal UninflectedDisjunction
+ NLP.GenI.Builder: instance [overlap ok] Eq LemmaPlus
+ NLP.GenI.Builder: instance [overlap ok] JSON LemmaPlus
+ NLP.GenI.Builder: instance [overlap ok] Ord LemmaPlus
+ NLP.GenI.Builder: instance [overlap ok] Show LemmaPlus
+ NLP.GenI.Builder: instance [overlap ok] Show UninflectedDisjunction
+ NLP.GenI.Builder: instance [overlap ok] Typeable UninflectedDisjunction
+ NLP.GenI.Builder: lexicalSelection :: Derivation -> [String]
+ NLP.GenI.Builder: lpFeats :: LemmaPlus -> Flist
+ NLP.GenI.Builder: lpLemma :: LemmaPlus -> String
+ NLP.GenI.Builder: parsecToJSON :: (Monad m) => String -> CharParser () b -> String -> m b
+ NLP.GenI.Builder: type LemmaPlusSentence = [LemmaPlus]
+ NLP.GenI.Configuration: DetectPolaritiesFlg :: (Set PolarityAttr) -> DetectPolaritiesFlg
+ NLP.GenI.Configuration: DumpDerivationFlg :: () -> DumpDerivationFlg
+ NLP.GenI.Configuration: EarlyNa :: Optimisation
+ NLP.GenI.Configuration: RankingConstraintsFlg :: FilePath -> RankingConstraintsFlg
+ NLP.GenI.Configuration: VersionFlg :: () -> VersionFlg
+ NLP.GenI.Configuration: basicSections :: [OptSection]
+ NLP.GenI.Configuration: data DetectPolaritiesFlg
+ NLP.GenI.Configuration: data DumpDerivationFlg
+ NLP.GenI.Configuration: data RankingConstraintsFlg
+ NLP.GenI.Configuration: data VersionFlg
+ NLP.GenI.Configuration: instance [overlap ok] Eq DetectPolaritiesFlg
+ NLP.GenI.Configuration: instance [overlap ok] Eq DumpDerivationFlg
+ NLP.GenI.Configuration: instance [overlap ok] Eq RankingConstraintsFlg
+ NLP.GenI.Configuration: instance [overlap ok] Eq VersionFlg
+ NLP.GenI.Configuration: instance [overlap ok] Show DetectPolaritiesFlg
+ NLP.GenI.Configuration: instance [overlap ok] Show DumpDerivationFlg
+ NLP.GenI.Configuration: instance [overlap ok] Show RankingConstraintsFlg
+ NLP.GenI.Configuration: instance [overlap ok] Show VersionFlg
+ NLP.GenI.Configuration: instance [overlap ok] Typeable DetectPolaritiesFlg
+ NLP.GenI.Configuration: instance [overlap ok] Typeable DumpDerivationFlg
+ NLP.GenI.Configuration: instance [overlap ok] Typeable RankingConstraintsFlg
+ NLP.GenI.Configuration: instance [overlap ok] Typeable VersionFlg
+ NLP.GenI.Configuration: optionsSections :: [OptSection]
+ NLP.GenI.Configuration: usage :: [OptSection] -> String -> String
+ NLP.GenI.Geni: CompleteResult :: ResultType
+ NLP.GenI.Geni: GeniResult :: LemmaPlusSentence -> [String] -> Derivation -> [GeniLexSel] -> Int -> [OtViolation] -> ResultType -> GeniResult
+ NLP.GenI.Geni: PartialResult :: ResultType
+ NLP.GenI.Geni: data GeniResult
+ NLP.GenI.Geni: data ResultType
+ NLP.GenI.Geni: grDerivation :: GeniResult -> Derivation
+ NLP.GenI.Geni: grLemmaSentence :: GeniResult -> LemmaPlusSentence
+ NLP.GenI.Geni: grLexSelection :: GeniResult -> [GeniLexSel]
+ NLP.GenI.Geni: grRanking :: GeniResult -> Int
+ NLP.GenI.Geni: grRealisations :: GeniResult -> [String]
+ NLP.GenI.Geni: grResultType :: GeniResult -> ResultType
+ NLP.GenI.Geni: grViolations :: GeniResult -> [OtViolation]
+ NLP.GenI.Geni: instance [overlap ok] Eq GeniLexSel
+ NLP.GenI.Geni: instance [overlap ok] Eq GeniResult
+ NLP.GenI.Geni: instance [overlap ok] Eq ResultType
+ NLP.GenI.Geni: instance [overlap ok] JSON GeniLexSel
+ NLP.GenI.Geni: instance [overlap ok] JSON GeniResult
+ NLP.GenI.Geni: instance [overlap ok] JSON ResultType
+ NLP.GenI.Geni: instance [overlap ok] Ord GeniLexSel
+ NLP.GenI.Geni: instance [overlap ok] Ord GeniResult
+ NLP.GenI.Geni: instance [overlap ok] Ord ResultType
+ NLP.GenI.Geni: lemmaSentenceString :: GeniResult -> String
+ NLP.GenI.Geni: loadRanking :: ProgStateRef -> IO ()
+ NLP.GenI.Geni: prettyResult :: ProgState -> GeniResult -> String
+ NLP.GenI.Geni: ranking :: ProgState -> OtRanking
+ NLP.GenI.Geni: readRanking :: Bool -> FilePath -> IO OtRanking
+ NLP.GenI.Geni: warnings :: ProgState -> [String]
+ NLP.GenI.GeniParsers: geniWord :: Parser String
+ NLP.GenI.GeniParsers: parseFromFile :: Parser a -> SourceName -> IO (Either ParseError a)
+ NLP.GenI.GeniVal: Failure :: UnificationResult
+ NLP.GenI.GeniVal: GAnon :: GeniVal
+ NLP.GenI.GeniVal: GConst :: [String] -> GeniVal
+ NLP.GenI.GeniVal: GTestString :: String -> GTestString
+ NLP.GenI.GeniVal: GTestString2 :: String -> GTestString2
+ NLP.GenI.GeniVal: GVar :: String -> GeniVal
+ NLP.GenI.GeniVal: SuccessRep :: String -> GeniVal -> UnificationResult
+ NLP.GenI.GeniVal: SuccessSans :: GeniVal -> UnificationResult
+ NLP.GenI.GeniVal: class DescendGeniVal a
+ NLP.GenI.GeniVal: data GeniVal
+ NLP.GenI.GeniVal: data UnificationResult
+ NLP.GenI.GeniVal: descendGeniVal :: (DescendGeniVal a) => (GeniVal -> GeniVal) -> a -> a
+ NLP.GenI.GeniVal: fromGConst :: GeniVal -> [String]
+ NLP.GenI.GeniVal: fromGTestString :: GTestString -> String
+ NLP.GenI.GeniVal: fromGTestString2 :: GTestString2 -> String
+ NLP.GenI.GeniVal: fromGVar :: GeniVal -> String
+ NLP.GenI.GeniVal: instance [overlap ok] (Functor f, DescendGeniVal a) => DescendGeniVal (f a)
+ NLP.GenI.GeniVal: instance [overlap ok] Arbitrary GTestString
+ NLP.GenI.GeniVal: instance [overlap ok] Arbitrary GTestString2
+ NLP.GenI.GeniVal: instance [overlap ok] Arbitrary GeniVal
+ NLP.GenI.GeniVal: instance [overlap ok] Data GeniVal
+ NLP.GenI.GeniVal: instance [overlap ok] DescendGeniVal GeniVal
+ NLP.GenI.GeniVal: instance [overlap ok] Eq GeniVal
+ NLP.GenI.GeniVal: instance [overlap ok] NFData GeniVal
+ NLP.GenI.GeniVal: instance [overlap ok] Ord GeniVal
+ NLP.GenI.GeniVal: instance [overlap ok] Show GeniVal
+ NLP.GenI.GeniVal: instance [overlap ok] Typeable GeniVal
+ NLP.GenI.GeniVal: instance [overlap ok] Uniplate GeniVal
+ NLP.GenI.GeniVal: isAnon :: GeniVal -> Bool
+ NLP.GenI.GeniVal: isConst :: GeniVal -> Bool
+ NLP.GenI.GeniVal: isVar :: GeniVal -> Bool
+ NLP.GenI.GeniVal: mergeSubst :: Subst -> Subst -> Subst
+ NLP.GenI.GeniVal: newtype GTestString
+ NLP.GenI.GeniVal: newtype GTestString2
+ NLP.GenI.GeniVal: prependToSubst :: (String, GeniVal) -> Subst -> Subst
+ NLP.GenI.GeniVal: prop_unify_anon :: [GeniVal] -> Bool
+ NLP.GenI.GeniVal: prop_unify_self :: [GeniVal] -> Property
+ NLP.GenI.GeniVal: prop_unify_sym :: [GeniVal] -> [GeniVal] -> Property
+ NLP.GenI.GeniVal: qc_not_empty_GConst :: GeniVal -> Bool
+ NLP.GenI.GeniVal: replace :: (DescendGeniVal a) => Subst -> a -> a
+ NLP.GenI.GeniVal: replaceList :: (DescendGeniVal a) => [(String, GeniVal)] -> a -> a
+ NLP.GenI.GeniVal: replaceMapG :: Subst -> GeniVal -> GeniVal
+ NLP.GenI.GeniVal: replaceOne :: (DescendGeniVal a) => (String, GeniVal) -> a -> a
+ NLP.GenI.GeniVal: replaceOneG :: (String, GeniVal) -> GeniVal -> GeniVal
+ NLP.GenI.GeniVal: type Subst = Map String GeniVal
+ NLP.GenI.GeniVal: unify :: (Monad m) => [GeniVal] -> [GeniVal] -> m ([GeniVal], Subst)
+ NLP.GenI.GeniVal: unifyOne :: GeniVal -> GeniVal -> UnificationResult
+ NLP.GenI.Morphology: LemmaPlus :: String -> Flist -> LemmaPlus
+ NLP.GenI.Morphology: data LemmaPlus
+ NLP.GenI.Morphology: lpFeats :: LemmaPlus -> Flist
+ NLP.GenI.Morphology: lpLemma :: LemmaPlus -> String
+ NLP.GenI.Morphology: type LemmaPlusSentence = [LemmaPlus]
+ NLP.GenI.OptimalityTheory: NegativeC :: String -> OtConstraint
+ NLP.GenI.OptimalityTheory: NegativeConjC :: [String] -> OtConstraint
+ NLP.GenI.OptimalityTheory: PositiveC :: String -> OtConstraint
+ NLP.GenI.OptimalityTheory: RankedOtConstraint :: Int -> OtConstraint -> RankedOtConstraint
+ NLP.GenI.OptimalityTheory: data OtConstraint
+ NLP.GenI.OptimalityTheory: data OtViolation
+ NLP.GenI.OptimalityTheory: data RankedOtConstraint
+ NLP.GenI.OptimalityTheory: instance [overlap ok] Eq LexItem
+ NLP.GenI.OptimalityTheory: instance [overlap ok] Eq OtConstraint
+ NLP.GenI.OptimalityTheory: instance [overlap ok] Eq OtViolation
+ NLP.GenI.OptimalityTheory: instance [overlap ok] Eq RankedOtConstraint
+ NLP.GenI.OptimalityTheory: instance [overlap ok] Eq RankedOtConstraint2
+ NLP.GenI.OptimalityTheory: instance [overlap ok] JSON OtConstraint
+ NLP.GenI.OptimalityTheory: instance [overlap ok] JSON OtViolation
+ NLP.GenI.OptimalityTheory: instance [overlap ok] JSON RankedOtConstraint
+ NLP.GenI.OptimalityTheory: instance [overlap ok] Ord LexItem
+ NLP.GenI.OptimalityTheory: instance [overlap ok] Ord OtViolation
+ NLP.GenI.OptimalityTheory: instance [overlap ok] Ord RankedOtConstraint
+ NLP.GenI.OptimalityTheory: instance [overlap ok] Ord RankedOtConstraint2
+ NLP.GenI.OptimalityTheory: instance [overlap ok] Show LexItem
+ NLP.GenI.OptimalityTheory: instance [overlap ok] Show OtConstraint
+ NLP.GenI.OptimalityTheory: instance [overlap ok] Show OtViolation
+ NLP.GenI.OptimalityTheory: instance [overlap ok] Show RankedOtConstraint
+ NLP.GenI.OptimalityTheory: otWarnings :: Macros -> OtRanking -> [OtViolation] -> [String]
+ NLP.GenI.OptimalityTheory: prettyRank :: Int -> String
+ NLP.GenI.OptimalityTheory: prettyViolations :: GetTraces -> Bool -> [OtViolation] -> String
+ NLP.GenI.OptimalityTheory: rankResults :: GetTraces -> (a -> Derivation) -> OtRanking -> [a] -> [OtResult a]
+ NLP.GenI.OptimalityTheory: type GetTraces = String -> [String]
+ NLP.GenI.OptimalityTheory: type OtRanking = [[OtConstraint]]
+ NLP.GenI.OptimalityTheory: type OtResult x = (Int, x, [OtViolation])
+ NLP.GenI.PolarityTypes: PolarityKey :: String -> PolarityKey
+ NLP.GenI.PolarityTypes: RestrictedPolarityAttr :: String -> String -> PolarityAttr
+ NLP.GenI.PolarityTypes: SimplePolarityAttr :: String -> PolarityAttr
+ NLP.GenI.PolarityTypes: _rpkCat :: PolarityAttr -> String
+ NLP.GenI.PolarityTypes: data PolarityAttr
+ NLP.GenI.PolarityTypes: fromPolarityKey :: PolarityKey -> String
+ NLP.GenI.PolarityTypes: instance [overlap ok] Data PolarityKey
+ NLP.GenI.PolarityTypes: instance [overlap ok] Eq PolarityAttr
+ NLP.GenI.PolarityTypes: instance [overlap ok] Eq PolarityKey
+ NLP.GenI.PolarityTypes: instance [overlap ok] Ord PolarityAttr
+ NLP.GenI.PolarityTypes: instance [overlap ok] Ord PolarityKey
+ NLP.GenI.PolarityTypes: instance [overlap ok] Show PolarityAttr
+ NLP.GenI.PolarityTypes: instance [overlap ok] Show PolarityKey
+ NLP.GenI.PolarityTypes: instance [overlap ok] Typeable PolarityAttr
+ NLP.GenI.PolarityTypes: instance [overlap ok] Typeable PolarityKey
+ NLP.GenI.PolarityTypes: newtype PolarityKey
+ NLP.GenI.PolarityTypes: readPolarityAttrs :: String -> Set PolarityAttr
+ NLP.GenI.PolarityTypes: rpkAtt :: PolarityAttr -> String
+ NLP.GenI.PolarityTypes: spkAtt :: PolarityAttr -> String
+ NLP.GenI.Simple.SimpleBuilder: SimpleGuiItem :: [String] -> [GNode] -> [String] -> Sem -> String -> SimpleGuiItem
+ NLP.GenI.Simple.SimpleBuilder: data SimpleGuiItem
+ NLP.GenI.Simple.SimpleBuilder: instance [overlap ok] Biplate (String, UninflectedDisjunction) GeniVal
+ NLP.GenI.Simple.SimpleBuilder: instance [overlap ok] Biplate SimpleGuiItem GeniVal
+ NLP.GenI.Simple.SimpleBuilder: instance [overlap ok] Biplate SimpleItem GeniVal
+ NLP.GenI.Simple.SimpleBuilder: instance [overlap ok] Data SimpleGuiItem
+ NLP.GenI.Simple.SimpleBuilder: instance [overlap ok] DescendGeniVal (String, UninflectedDisjunction)
+ NLP.GenI.Simple.SimpleBuilder: instance [overlap ok] DescendGeniVal SimpleGuiItem
+ NLP.GenI.Simple.SimpleBuilder: instance [overlap ok] DescendGeniVal SimpleItem
+ NLP.GenI.Simple.SimpleBuilder: instance [overlap ok] Show GenerationPhase
+ NLP.GenI.Simple.SimpleBuilder: instance [overlap ok] Show SimpleGuiItem
+ NLP.GenI.Simple.SimpleBuilder: instance [overlap ok] Typeable SimpleGuiItem
+ NLP.GenI.Simple.SimpleBuilder: siDiagnostic :: SimpleGuiItem -> [String]
+ NLP.GenI.Simple.SimpleBuilder: siFullSem :: SimpleGuiItem -> Sem
+ NLP.GenI.Simple.SimpleBuilder: siGuiStuff :: SimpleItem -> SimpleGuiItem
+ NLP.GenI.Simple.SimpleBuilder: siHighlight :: SimpleGuiItem -> [String]
+ NLP.GenI.Simple.SimpleBuilder: siIdname :: SimpleGuiItem -> String
+ NLP.GenI.Simple.SimpleBuilder: siNodes :: SimpleGuiItem -> [GNode]
+ NLP.GenI.Simple.SimpleBuilder: step :: SimpleStatus -> GenerationPhase
+ NLP.GenI.Simple.SimpleBuilder: theHoldingPen :: SimpleStatus -> AuxAgenda
+ NLP.GenI.Simple.SimpleBuilder: theTrash :: SimpleStatus -> Trash
+ NLP.GenI.Simple.SimpleBuilder: unpackResult :: SimpleItem -> [Output]
+ NLP.GenI.Statistics: instance [overlap ok] JSON Statistics
+ NLP.GenI.Tags: DerivationStep :: Char -> String -> String -> String -> DerivationStep
+ NLP.GenI.Tags: data DerivationStep
+ NLP.GenI.Tags: dsChild :: DerivationStep -> String
+ NLP.GenI.Tags: dsOp :: DerivationStep -> Char
+ NLP.GenI.Tags: dsParent :: DerivationStep -> String
+ NLP.GenI.Tags: dsParentSite :: DerivationStep -> String
+ NLP.GenI.Tags: instance [overlap ok] Biplate (Maybe TagSite) GeniVal
+ NLP.GenI.Tags: instance [overlap ok] Biplate TagElem GeniVal
+ NLP.GenI.Tags: instance [overlap ok] Biplate TagSite GeniVal
+ NLP.GenI.Tags: instance [overlap ok] Data TagElem
+ NLP.GenI.Tags: instance [overlap ok] Data TagSite
+ NLP.GenI.Tags: instance [overlap ok] DescendGeniVal TagElem
+ NLP.GenI.Tags: instance [overlap ok] DescendGeniVal TagSite
+ NLP.GenI.Tags: instance [overlap ok] Eq DerivationStep
+ NLP.GenI.Tags: instance [overlap ok] JSON DerivationStep
+ NLP.GenI.Tags: instance [overlap ok] Ord DerivationStep
+ NLP.GenI.Tags: instance [overlap ok] Show DerivationStep
+ NLP.GenI.Tags: instance [overlap ok] Typeable TagElem
+ NLP.GenI.Tags: instance [overlap ok] Typeable TagSite
- NLP.GenI.Btypes: GVar :: !String -> GeniVal
+ NLP.GenI.Btypes: GVar :: String -> GeniVal
- NLP.GenI.Btypes: alphaConvert :: (Collectable a, Replacable a) => String -> a -> a
+ NLP.GenI.Btypes: alphaConvert :: (Collectable a, DescendGeniVal a) => String -> a -> a
- NLP.GenI.Btypes: alphaConvertById :: (Collectable a, Replacable a, Idable a) => a -> a
+ NLP.GenI.Btypes: alphaConvertById :: (Collectable a, DescendGeniVal a, Idable a) => a -> a
- NLP.GenI.Btypes: replace :: (Replacable a) => Subst -> a -> a
+ NLP.GenI.Btypes: replace :: (DescendGeniVal a) => Subst -> a -> a
- NLP.GenI.Btypes: replaceList :: (Replacable a) => [(String, GeniVal)] -> a -> a
+ NLP.GenI.Btypes: replaceList :: (DescendGeniVal a) => [(String, GeniVal)] -> a -> a
- NLP.GenI.Builder: type Output = (UninflectedSentence, Derivation)
+ NLP.GenI.Builder: type Output = (LemmaPlusSentence, Derivation)
- NLP.GenI.Builder: type SentenceAut = NFA Int UninflectedWord
+ NLP.GenI.Builder: type SentenceAut = NFA Int LemmaPlus
- NLP.GenI.Configuration: ExtraPolaritiesFlg :: (Map String Interval) -> ExtraPolaritiesFlg
+ NLP.GenI.Configuration: ExtraPolaritiesFlg :: (Map PolarityKey Interval) -> ExtraPolaritiesFlg
- NLP.GenI.Geni: ST :: Params -> Macros -> Lexicon -> MorphFn -> Maybe [(String, String, Flist)] -> SemInput -> String -> [TestCase] -> [String] -> ProgState
+ NLP.GenI.Geni: ST :: Params -> Macros -> Lexicon -> MorphFn -> SemInput -> String -> [TestCase] -> OtRanking -> [String] -> [String] -> ProgState
- NLP.GenI.Geni: loadTestSuite :: ProgStateRef -> IO ()
+ NLP.GenI.Geni: loadTestSuite :: ProgStateRef -> IO [TestCase]
- NLP.GenI.Geni: type Selector = ProgState -> IO ([TagElem], [ILexEntry])
+ NLP.GenI.Geni: type Selector = ProgStateRef -> IO ([TagElem], [ILexEntry])
- NLP.GenI.GeniParsers: geniPolarities :: Parser (Map String Interval)
+ NLP.GenI.GeniParsers: geniPolarities :: Parser (Map PolarityKey Interval)
- NLP.GenI.Morphology: inflectSentencesUsingCmd :: String -> [[UninflectedDisjunction]] -> IO [[String]]
+ NLP.GenI.Morphology: inflectSentencesUsingCmd :: String -> [LemmaPlusSentence] -> IO [(LemmaPlusSentence, [String])]
- NLP.GenI.Morphology: sansMorph :: [(String, Flist)] -> [String]
+ NLP.GenI.Morphology: sansMorph :: LemmaPlusSentence -> [String]
- NLP.GenI.Polarity: buildAutomaton :: SemInput -> [TagElem] -> Flist -> PolMap -> PolResult
+ NLP.GenI.Polarity: buildAutomaton :: Set PolarityAttr -> Flist -> PolMap -> SemInput -> [TagElem] -> PolResult
- NLP.GenI.Polarity: detectPols :: [TagElem] -> [TagElem]
+ NLP.GenI.Polarity: detectPols :: Set PolarityAttr -> [TagElem] -> [TagElem]
- NLP.GenI.Polarity: type AutDebug = (String, PolAut, PolAut)
+ NLP.GenI.Polarity: type AutDebug = (PolarityKey, PolAut, PolAut)
- NLP.GenI.Simple.SimpleBuilder: SimpleItem :: ChartId -> ![TagSite] -> ![TagSite] -> !BitVector -> !BitVector -> [String] -> [String] -> [(String, UninflectedDisjunction)] -> Tree String -> TagSite -> Maybe TagSite -> [TagSite] -> TagDerivation -> SimpleItem
+ NLP.GenI.Simple.SimpleBuilder: SimpleItem :: ChartId -> [TagSite] -> [TagSite] -> BitVector -> BitVector -> [String] -> [String] -> [(String, UninflectedDisjunction)] -> Tree String -> TagSite -> Maybe TagSite -> [TagSite] -> TagDerivation -> SimpleGuiItem -> SimpleItem
- NLP.GenI.Simple.SimpleBuilder: siAdjnodes :: SimpleItem -> ![TagSite]
+ NLP.GenI.Simple.SimpleBuilder: siAdjnodes :: SimpleItem -> [TagSite]
- NLP.GenI.Simple.SimpleBuilder: siPolpaths :: SimpleItem -> !BitVector
+ NLP.GenI.Simple.SimpleBuilder: siPolpaths :: SimpleItem -> BitVector
- NLP.GenI.Simple.SimpleBuilder: siSemantics :: SimpleItem -> !BitVector
+ NLP.GenI.Simple.SimpleBuilder: siSemantics :: SimpleItem -> BitVector
- NLP.GenI.Simple.SimpleBuilder: siSubstnodes :: SimpleItem -> ![TagSite]
+ NLP.GenI.Simple.SimpleBuilder: siSubstnodes :: SimpleItem -> [TagSite]
- NLP.GenI.Statistics: type StatisticsStateIO a = forall m. (MonadState Statistics m, MonadIO m) => m a
+ NLP.GenI.Statistics: type StatisticsState a = forall m. (MonadState Statistics m) => m a
- NLP.GenI.Tags: TE :: String -> String -> Integer -> !Ptype -> Tree GNode -> Sem -> Map String (Int, Int) -> Flist -> [String] -> [SemPols] -> TagElem
+ NLP.GenI.Tags: TE :: String -> String -> Integer -> Ptype -> Tree GNode -> Sem -> Map PolarityKey (Int, Int) -> Flist -> [String] -> [SemPols] -> TagElem
- NLP.GenI.Tags: TagSite :: !String -> !Flist -> !Flist -> !String -> TagSite
+ NLP.GenI.Tags: TagSite :: String -> Flist -> Flist -> String -> TagSite
- NLP.GenI.Tags: tpolarities :: TagElem -> Map String (Int, Int)
+ NLP.GenI.Tags: tpolarities :: TagElem -> Map PolarityKey (Int, Int)
- NLP.GenI.Tags: tsDown :: TagSite -> !Flist
+ NLP.GenI.Tags: tsDown :: TagSite -> Flist
- NLP.GenI.Tags: tsName :: TagSite -> !String
+ NLP.GenI.Tags: tsName :: TagSite -> String
- NLP.GenI.Tags: tsOrigin :: TagSite -> !String
+ NLP.GenI.Tags: tsOrigin :: TagSite -> String
- NLP.GenI.Tags: tsUp :: TagSite -> !Flist
+ NLP.GenI.Tags: tsUp :: TagSite -> Flist
- NLP.GenI.Tags: ttype :: TagElem -> !Ptype
+ NLP.GenI.Tags: ttype :: TagElem -> Ptype
- NLP.GenI.Tags: type TagDerivation = [(Char, String, (String, String))]
+ NLP.GenI.Tags: type TagDerivation = [DerivationStep]
Files
- GenI.cabal +41/−36
- INSTALL +12/−8
- NEWS +61/−0
- Setup.hs +117/−0
- Setup.lhs +0/−68
- etc/macstuff/Info.plist +5/−5
- etc/macstuff/macosx-app +0/−114
- etc/stupidmorph.pl +85/−0
- examples/demo/README +0/−0
- examples/ej/lexicon +0/−0
- examples/ej/suite +0/−0
- examples/xmg-example/Makefile +3/−3
- examples/xmg-example/grammar/demo-corpus-latin1.txt +0/−6
- examples/xmg-example/grammar/demo-corpus-utf8.txt +6/−0
- examples/xmg-example/lexicon/demo-lemma-latin1.lex +0/−113
- examples/xmg-example/lexicon/demo-lemma-utf8.lex +113/−0
- examples/xmg-example/lexicon/demo-morph-latin1.mph +0/−24
- examples/xmg-example/lexicon/demo-morph-utf8.mph +24/−0
- examples/xmg-example/suite +0/−0
- src/EnableGUI.hs +0/−38
- src/MainGeni.lhs +53/−15
- src/NLP/GenI/Automaton.hs +136/−0
- src/NLP/GenI/Automaton.lhs +0/−140
- src/NLP/GenI/Btypes.lhs +134/−365
- src/NLP/GenI/BtypesBinary.hs +5/−0
- src/NLP/GenI/Builder.lhs +107/−25
- src/NLP/GenI/CkyEarley/CkyBuilder.lhs +0/−1244
- src/NLP/GenI/CkyEarley/CkyGui.lhs +0/−456
- src/NLP/GenI/Configuration.lhs +170/−152
- src/NLP/GenI/Console.hs +73/−75
- src/NLP/GenI/General.hs +7/−19
- src/NLP/GenI/Geni.lhs +286/−166
- src/NLP/GenI/GeniParsers.lhs +559/−413
- src/NLP/GenI/GeniShow.lhs +2/−2
- src/NLP/GenI/GeniVal.hs +309/−0
- src/NLP/GenI/GraphvizShow.lhs +10/−10
- src/NLP/GenI/Gui.lhs +141/−163
- src/NLP/GenI/GuiHelper.hs +750/−0
- src/NLP/GenI/GuiHelper.lhs +0/−860
- src/NLP/GenI/Morphology.lhs +84/−69
- src/NLP/GenI/OptimalityTheory.lhs +390/−0
- src/NLP/GenI/Polarity.lhs +119/−80
- src/NLP/GenI/PolarityTypes.hs +44/−0
- src/NLP/GenI/Regression.hs +83/−0
- src/NLP/GenI/Simple/SimpleBuilder.lhs +238/−219
- src/NLP/GenI/Simple/SimpleGui.lhs +22/−14
- src/NLP/GenI/Statistics.hs +21/−87
- src/NLP/GenI/SysGeni.hs +77/−0
- src/NLP/GenI/SysGeni.lhs +0/−93
- src/NLP/GenI/Tags.lhs +70/−22
- src/NLP/GenI/Test.hs +7/−12
- src/NLP/GenI/unused/Predictors.lhs +0/−315
GenI.cabal view
@@ -1,5 +1,5 @@ Name: GenI-Version: 0.17.4+Version: 0.20 License: GPL License-file: LICENSE Author: Carlos Areces and Eric Kow@@ -33,52 +33,60 @@ examples/promettre/morphinfo, examples/promettre/suite, examples/xmg-example/grammar/Arguments.mg,- examples/xmg-example/grammar/demo-corpus-latin1.txt,+ examples/xmg-example/grammar/demo-corpus-utf8.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/lexicon/demo-lemma-utf8.lex,+ examples/xmg-example/lexicon/demo-morph-utf8.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+ etc/stupidmorph.pl,+ etc/macstuff/Info.plist,+ etc/macstuff/wxmac.icns -extra-source-files: src/EnableGUI.hs- src/NLP/GenI/SysGeni.lhs+extra-source-files: src/NLP/GenI/SysGeni.hs 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+ src/NLP/GenI/Simple/SimpleGui.lhs,+ src/NLP/GenI/Gui.lhs+ src/NLP/GenI/GraphvizShow.lhs,+ src/NLP/GenI/GuiHelper.hs+ src/NLP/GenI/Console.hs,+ src/NLP/GenI/Graphviz.hs+ src/NLP/GenI/BuilderGui.lhs,+ src/NLP/GenI/GraphvizShowPolarity.lhs,+ src/NLP/GenI/Regression.hs Flag gui description: Build with a graphical user interface default: False -Flag splitBase- description: Choose the new smaller, split-up base package.- Flag static Description: Build a static binary Default: False Library Build-depends: parsec >= 2 && < 3,+ base >= 3 && < 4,+ containers >= 0.1,+ process > 1, QuickCheck >= 1.2 && < 2,+ json >= 0.4.3 && < 0.5, 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+ parallel >= 1.1 && < 1.2,+ binary >= 0.5.0.2 && < 0.6,+ split >= 0.1.1 && < 0.2,+ test-framework-hunit >= 0.2.4 && < 0.3,+ test-framework-quickcheck >= 0.2.4 && < 0.3,+ test-framework >= 0.2.4 && < 0.3,+ utf8-string >= 0.3.5 && < 0.4,+ uniplate >= 1.2.0 && < 1.3 if !flag(gui) cpp-options: -DDISABLE_GUI@@ -92,36 +100,33 @@ NLP.GenI.General, NLP.GenI.GeniParsers, NLP.GenI.GeniShow,+ NLP.GenI.GeniVal, NLP.GenI.Tags, NLP.GenI.Morphology,- NLP.GenI.Polarity, NLP.GenI.Automaton,+ NLP.GenI.OptimalityTheory,+ NLP.GenI.Polarity,+ NLP.GenI.PolarityTypes,+ NLP.GenI.Automaton, NLP.GenI.Statistics, NLP.GenI.Builder,- NLP.GenI.Simple.SimpleBuilder, NLP.GenI.CkyEarley.CkyBuilder,+ 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+ Ghc-prof-options: -auto-all Executable geni Main-Is: MainGeni.lhs 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,- process > 1, directory > 1, containers >= 0.1- else- Build-Depends: base < 3-+ Ghc-options: -Wall -O2+ Ghc-prof-options: -auto-all+ Build-Depends: directory > 1,+ filepath >= 1.1.0 && < 1.2 if flag(gui) Build-Depends: wx >= 0.10.3 && < 0.12 else
INSTALL view
@@ -1,17 +1,21 @@ Requirements ------------- * ghc 6.8 or 6.10+ * 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) +GUI requirements+----------------+Only needed if you want the graphical interface:+ * wxWidgets 2.8+ * wxHaskell 0.11+ * Graphviz+ Building GenI -------------- 1. obtain cabal-install--2. cabal configure- cabal build- cabal install+2. run: cabal install+3. if you want the graphical interface, try+ cabal install -fgui+ but ONLY AFTER you succeed in installing the text version :-) For more details, see http://trac.haskell.org/GenI
NEWS view
@@ -1,3 +1,35 @@+GenI 0.20, 29 Sep 2009+----------------------+- NON BACKWARD-COMPATIBLE CHANGES++ * GHC 6.8 or higher now required (GHC 6.10 + Haskell Platform preferred)+ * Input files now *assumed* to be UTF-8 encoded+ (output still ISO8859-1; hopefully locale-based in GHC 6.12)+ * Interface between GenI and morphological realiser now uses a JSON format.+ * --macros renamed to --schemata (still -m)+ * Rootfeat optimisation now mandatory (flag no longer recognised)++- NEW FUNCTIONALITY++ * new flag --version now works and reports version from Cabal file+ * new flag --dump dumps derivation output to stdout in JSON format+ * new flag --ranking foo ranks outputs according to OT-style constraints expressed in foo+ * --batchdir now works with --testcase+ * --batchdir now dumps derivations and statistics in JSON format+ * --batchdir now works with instructions files+ * --metrics always expands 'default' to the default metrics+ (in addition to any other statistics you request)++- QUALITY ASSURANCE++ * Reference manual in docs/genimanual.pdf (make docs)+ * cabal install -fgui and cabal install produce the same underlying library+ only the executable is different+ * Easier GUI installation for MacOS X (application bundles now built+ automatically; no manual intervention required)+ * Adopted odd/even convention to distinguish devel builds from stable ones+ (eg. GenI 0.21.x is devel whereas GenI 0.20.x is stable)+ GenI 0.17.4, 6 Apr 2009 ----------------------- * GHC 6.6 support@@ -17,3 +49,32 @@ * Better help text * Baked-in unit testing (geni --unit-test)++GenI 0.10, 11 Dec 2006+----------------------+* Performance much improved after Haskell profiling+* Pre-compiled grammars option, geniserver (buggy)++GenI 0.9, 8 Dec 2006+--------------------+* Switched to new syntax: variables now must be prefixed with '?'+* Switched from Alex/Happy to Parsec+* Added API documentation via haddock++GenI 0.8, 16 Sep 2005+---------------------+* Support for latin-1 characters using Unicode wxhaskell+* Added a configure script and an install target in Makefile+* Changed from use of configuration files to use of command line switches+* Full configuration GUI+* Implementation of atomic disjunction+* --ignore-semantics mode (Jackie Lai)++GenI 0.7, 8 June 2005+---------------------+* Upgraded to ghc 6.4, wxwidgets 2.6, wxhaskell 0.9.4++GenI 0.6, 3 May 2005+---------------------+* User interface fixes (Window size bugs)+* Interface with Yannick Parmientier's selection module
+ Setup.hs view
@@ -0,0 +1,117 @@+{-# LANGUAGE CPP #-}++import Control.Monad (foldM_, forM_)+import Data.Maybe ( fromMaybe )+import System.Cmd+import System.Exit+import System.Info (os)+import System.FilePath+import System.Directory ( doesFileExist, copyFile, removeFile, createDirectoryIfMissing )++import Distribution.PackageDescription+import Distribution.Simple.Setup+import Distribution.Simple+import Distribution.Simple.LocalBuildInfo++#ifndef WIN32+import System.Posix.Files (fileMode, getFileStatus, setFileMode,+ ownerExecuteMode, groupExecuteMode, otherExecuteMode)+import Data.Bits ( (.|.) )+#endif++main :: IO ()+main = defaultMainWithHooks $ addMacHook simpleUserHooks+ where+ addMacHook h =+ case os of+ "darwin" -> h { postInst = appBundleHook } -- is it OK to treat darwin as synonymous with MacOS X?+ _ -> h++appBundleHook :: Args -> InstallFlags -> PackageDescription -> LocalBuildInfo -> IO ()+appBundleHook _ _ pkg localb =+ forM_ exes $ \app ->+ do createAppBundle theBindir (buildDir localb </> app </> app)+ customiseAppBundle (appBundlePath theBindir app) app+ `catch` \err -> putStrLn $ "Warning: could not customise bundle for " ++ app ++ ": " ++ show err+ removeFile (theBindir </> app)+ createAppBundleWrapper theBindir app+ where+ theBindir = bindir $ absoluteInstallDirs pkg localb NoCopyDest+ exes = fromMaybe (map exeName $ executables pkg) mRestrictTo++-- ----------------------------------------------------------------------+-- helper code for application bundles+-- ----------------------------------------------------------------------++-- | 'createAppBundle' @d p@ - creates an application bundle in @d@+-- for program @p@, assuming that @d@ already exists and is a directory.+-- Note that only the filename part of @p@ is used.+createAppBundle :: FilePath -> FilePath -> IO ()+createAppBundle dir p =+ do createDirectoryIfMissing False $ bundle+ createDirectoryIfMissing True $ bundleBin+ createDirectoryIfMissing True $ bundleRsrc+ copyFile p (bundleBin </> takeFileName p)+ where+ bundle = appBundlePath dir p+ bundleBin = bundle </> "Contents/MacOS"+ bundleRsrc = bundle </> "Contents/Resources"++-- | 'createAppBundleWrapper' @d p@ - creates a script in @d@ that calls+-- @p@ from the application bundle @d </> takeFileName p <.> "app"@+createAppBundleWrapper :: FilePath -> FilePath -> IO ()+createAppBundleWrapper bindir p =+ do writeFile scriptFile scriptTxt+ makeExecutable scriptFile+ where+ scriptFile = bindir </> takeFileName p+ scriptTxt = "`dirname $0`" </> appBundlePath "." p </> "Contents/MacOS" </> takeFileName p ++ " \"$@\""++appBundlePath :: FilePath -> FilePath -> FilePath+appBundlePath dir p = dir </> takeFileName p <.> "app"++-- ----------------------------------------------------------------------+-- utilities+-- ----------------------------------------------------------------------++makeExecutable :: FilePath -> IO ()+#ifdef WIN32+makeExecutable = const (return ())+#else+makeExecutable f =+ do st <- getFileStatus f+ let m = fileMode st+ m2 = m .|. ownerExecuteMode .|. groupExecuteMode .|. otherExecuteMode+ setFileMode f m2+#endif++-- ----------------------------------------------------------------------+-- customisations+-- ----------------------------------------------------------------------++-- | Put here IO actions needed to add any fancy things (eg icons)+-- you want to your application bundle.+customiseAppBundle :: FilePath -- ^ app bundle path+ -> FilePath -- ^ full path to original binary+ -> IO ()+customiseAppBundle bundleDir p =+ case takeFileName p of+ "geni" ->+ do hasRez <- doesFileExist "/Developer/Tools/Rez"+ if hasRez+ then do -- set the icon+ copyFile "etc/macstuff/Info.plist" (bundleDir </> "Contents/Info.plist")+ copyFile "etc/macstuff/wxmac.icns" (bundleDir </> "Contents/Resources/wxmac.icns")+ -- no idea what this does+ system ("/Developer/Tools/Rez -t APPL Carbon.r -o " ++ bundleDir </> "Contents/MacOS/geni")+ writeFile (bundleDir </> "PkgInfo") "APPL????"+ -- tell Finder about the icon+ system ("/Developer/Tools/SetFile -a C " ++ bundleDir </> "Contents")+ return ()+ else putStrLn "Developer Tools not found. Too bad; no fancy icons for you."+ "" -> return ()++-- | Put here the list of executables which contain a GUI. If they all+-- contain a GUI (or you don't really care that much), just put Nothing+mRestrictTo :: Maybe [String]+mRestrictTo = Just ["geni"]
− Setup.lhs
@@ -1,68 +0,0 @@-#!/usr/bin/env runhaskell--This Cabal setup script is meant to be used with programs that use the-wxHaskell toolkit. The problem is that on MacOS X, you have to post-process each GUI with the 'macosx-app' shell script (from wxhaskell) so-that it actually responds to user input instead of just sitting there-looking pretty.--> import Control.Monad (foldM_)-> import System.Cmd-> import System.Exit-> import System.Info (os)->-> import Distribution.PackageDescription-> import Distribution.Simple.Setup-> import Distribution.Simple-> import Distribution.Simple.LocalBuildInfo--Configure this stuff----------------------Put here the list of executables which contain a GUI. If they all-contain a GUI (or you don't really care that much), just put Nothing--> mRestrictTo :: Maybe [String]-> mRestrictTo = Just ["geni"]--Normally, this should just be "macosx-app"--> macosxApp :: String-> macosxApp = "etc/macstuff/macosx-app"--Nothing to configure from here on-----------------------------------Note that we assume anybody running on Darwin is running OS X, probably-not the right thing to do, but frankly... who runs Darwin anyway? Note-also that this script is equivalent to the vanilla one if you're running-on other operating systems.--> main :: IO ()-> main =-> do case os of-> "darwin" -> defaultMainWithHooks (simpleUserHooks { postBuild = macifyHook })-> _ -> defaultMain--> macifyHook _ _ pkg localb =-> foldM_ (next $ macify.binPath) ExitSuccess guiExes-> where-> allExes = map exeName $ executables pkg-> guiExes = case mRestrictTo of-> Nothing -> allExes-> Just rs -> filter (`elem` rs) allExes-> next _ x@(ExitFailure _) _ = return x-> next _ _ b = macify (binPath b)-> binPath x = buildDir localb /// x /// x--> macify :: FilePath -> IO ExitCode-> macify x = system $ unwords $ [ "chmod u+x", macosxApp, ";"-> , macosxApp, x ]--This handly little FilePath concatenation function was stolen from-darcs. Note that darcs is GPL; if this bothers you, ask David Roundy.--> (///) :: FilePath -> FilePath -> FilePath-> ""///b = b-> a///"" = a-> a///b = a ++ "/" ++ b--
etc/macstuff/Info.plist view
@@ -7,7 +7,7 @@ <key>CFBundleExecutable</key> <string>geni</string> <key>CFBundleGetInfoString</key>- <string>GenI unstable 0.8</string>+ <string>GenI</string> <key>CFBundleIconFile</key> <string>wxmac.icns</string> <key>CFBundleIdentifier</key>@@ -15,22 +15,22 @@ <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> <key>CFBundleLongVersionString</key>- <string>0.8, (c) 2005 LORIA</string>+ <string>(c) 2005-2009 LORIA</string> <key>CFBundleName</key> <string>GenI</string> <key>CFBundlePackageType</key> <string>APPL</string> <key>CFBundleShortVersionString</key>- <string>0.8</string>+ <string>0</string> <key>CFBundleSignature</key> <string>????</string> <key>CFBundleVersion</key>- <string>0.8</string>+ <string>0</string> <key>CSResourcesFileMapped</key> <true/> <key>LSRequiresCarbon</key> <true/> <key>NSHumanReadableCopyright</key>- <string>Copyright 2005 LORIA</string>+ <string>Copyright 2005-2009 LORIA</string> </dict> </plist>
− etc/macstuff/macosx-app
@@ -1,114 +0,0 @@-#!/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/stupidmorph.pl view
@@ -0,0 +1,85 @@+:+eval 'exec perl -w -S $0 ${1+"$@"}'+ if 0; ++use strict;+use JSON;++# --------------------------------------------------------------------+# idiotic morphological generator+#+# this outputs for each lemma the name of the lemma and its category+# --------------------------------------------------------------------++# --------------------------------------------------------------------+# from GenI+# --------------------------------------------------------------------++# we return a list of lists of hashes+# - each item in the outer list corresponds to a sentence+# - each item in the inner list corresponds to a word+# - each hash represents the features read from GenI plus a special "__lemma__" feature+# which holds the lemma+sub read_morph_request {+ my $json_str = shift;+ my $allR = from_json $json_str;+ my @r_sentences = (); # one request per sentence++ foreach my $sentenceR (@$allR) {+ my @r_words = ();+ foreach my $wordR (@$sentenceR) {+ my $lemma = $wordR->{lemma};+ my $featstr = $wordR->{"lemma-features"};+ $featstr =~ s/^\[//;+ $featstr =~ s/\]$//;++ my %feat = ();+ my $av = "";+ for (split(/ /, $featstr)) {+ $av = $_;+ my ($attr, $val) = split(/:/,$av);+ $feat{$attr} = $val;+ }+ $feat{"__lemma__"} = $lemma;+ push @r_words, \%feat;+ }+ push @r_sentences, \@r_words;+ }+ return \@r_sentences+}++# --------------------------------------------------------------------+# morph+# --------------------------------------------------------------------++sub morph {+ my $lemma = shift;+ my $featsR = shift;+ my $cat = $featsR->{"cat"};+ return "$lemma:$cat";+}++# --------------------------------------------------------------------+# main+# --------------------------------------------------------------------++# slurp STDIN to $buf (copied from web)+my $holdTerminator = $/;+undef $/;+my $buf = <STDIN>;+$/ = $holdTerminator;++my @output = ();+my $reqsR = read_morph_request $buf;+foreach my $sentenceR (@$reqsR) {+ my @output_words = ();+ foreach my $wordR (@$sentenceR) {+ my $inflected = morph($wordR->{"__lemma__"}, $wordR);+ push @output_words, $inflected;+ }+ my $output_sentence = join(" ",@output_words);+ my @singleton = ( $output_sentence );+ push @output, \@singleton;+}++print to_json(\@output);
examples/demo/README view
examples/ej/lexicon view
examples/ej/suite view
examples/xmg-example/Makefile view
@@ -7,8 +7,8 @@ GRAMMAR_RAW_MG:=$(GRAMMAR_DIR)/$(GRAMMAR).mg LEXICON_DIR:=lexicon-FULL_LEXICON_PREFIX:=demo-lemma-latin1-MORPH_PREFIX:=demo-morph-latin1+FULL_LEXICON_PREFIX:=demo-lemma-utf8+MORPH_PREFIX:=demo-morph-utf8 MACROS_FILE:=macros.mac @@ -20,7 +20,7 @@ GENI_MORPH:=$(LEXICON_DIR)/$(MORPH_PREFIX).gmorph GENI_SUITE:=suite -GENI_OPTIMISATIONS:='pol f-sem f-root'+GENI_OPTIMISATIONS:='pol f-sem' GENI_LEX_FLAGS:=-l $(GENI_LEXICON) -s $(GENI_SUITE) ifdef ENABLE_MORPH
− examples/xmg-example/grammar/demo-corpus-latin1.txt
@@ -1,6 +0,0 @@-Jean aime Marie.-Jean est appelé par Marie.-Marie aime Jean.-Marie appelle.-Jean qui appelle aime Marie.-ils appellent.
+ examples/xmg-example/grammar/demo-corpus-utf8.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/lexicon/demo-lemma-latin1.lex
@@ -1,113 +0,0 @@-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-lemma-utf8.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
@@ -1,24 +0,0 @@-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/lexicon/demo-morph-utf8.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
− src/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
src/MainGeni.lhs view
@@ -28,19 +28,35 @@ \ignore{ \begin{code}+import Control.Applicative ((<$>)) import Data.IORef(newIORef)-import System.Environment(getArgs)+import Data.Typeable( Typeable )+import Data.Version ( showVersion )+import System.Environment(getArgs, getProgName) +import Paths_GenI ( version )+ import NLP.GenI.Geni(emptyProgState) import NLP.GenI.Console(consoleGeni)-import NLP.GenI.Configuration (treatStandardArgs, processInstructions,- hasFlagP, BatchDirFlg(..), DisableGuiFlg(..), FromStdinFlg(..),+import NLP.GenI.Configuration (treatArgs, optionsForStandardGenI, processInstructions,+ usage, optionsSections, Params,+ hasFlagP, BatchDirFlg(..), DisableGuiFlg(..),+ DumpDerivationFlg(..), FromStdinFlg(..),+ HelpFlg(..), VersionFlg(..), TestCaseFlg(..), RegressionTestModeFlg(..), RunUnitTestFlg(..), )+import NLP.GenI.Regression (regressionGeni)+import NLP.GenI.Test (runTests) -#ifndef DISABLE_GUI-import NLP.GenI.Gui(guiGeni)+#ifdef DISABLE_GUI+import NLP.GenI.Configuration(setFlagP)+import NLP.GenI.Geni(ProgStateRef) #else+import NLP.GenI.Gui(guiGeni)+#endif++#ifdef DISABLE_GUI+guiGeni :: ProgStateRef -> IO () guiGeni = consoleGeni #endif \end{code}@@ -66,18 +82,40 @@ \begin{code} main :: IO () main = do - args <- getArgs- confArgs <- treatStandardArgs args >>= processInstructions+ pname <- getProgName+ args <- getArgs+ confArgs <- forceGuiFlag <$> (processInstructions =<< treatArgs optionsForStandardGenI args) 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+ let has :: (Typeable f, Typeable x) => (x -> f) -> Bool+ has = flip hasFlagP confArgs+ mustRunInConsole = has DumpDerivationFlg || has FromStdinFlg || has BatchDirFlg+ canRunInConsole = has TestCaseFlg+ case () of+ _ | has HelpFlg -> putStrLn (usage optionsSections pname)+ | has VersionFlg -> putStrLn ("GenI " ++ showVersion version)+ | has RunUnitTestFlg -> runTests+ | has RegressionTestModeFlg -> regressionGeni pstRef+ | mustRunInConsole -> consoleGeni pstRef+ | not (has DisableGuiFlg) -> guiGeni pstRef+ | canRunInConsole -> consoleGeni pstRef+ | otherwise -> fail $ unlines+ [ "GenI must either be run..."+ , " - with the graphical interface enabled"+ , " - in regression testing mode"+ , " - in self-diagnostic unit test mode"+ , " - with a test case specified"+ , " - with a batch directory specified or"+ , " - with --dump"+ , " - with --from-stdin"+ ]++forceGuiFlag :: Params -> Params+#ifdef DISABLE_GUI+forceGuiFlag = setFlagP DisableGuiFlg ()+#else+forceGuiFlag = id+#endif \end{code} % TODO
+ src/NLP/GenI/Automaton.hs view
@@ -0,0 +1,136 @@+-- 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.+++-- TODO: 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,+-- Maybe HaLeX?++-- | 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.+--+-- 1. 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.+--+-- 2. We model the empty an empty transition as the transition on+-- @Nothing@. All other transitions are @Just@ something.+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)+++-- | Note: you can define the final state either by setting 'isFinalSt'+-- to @Just f@ where @f@ is some function or by putting them in+-- 'finalStList'+data NFA st ab = NFA + { startSt :: st+ , isFinalSt :: Maybe (st -> Bool) -- ^ 'finalSt' will use this if defined+ , finalStList :: [st] -- ^ can be ignored if 'isFinalSt' is defined+ -- + , transitions :: Map.Map st (Map.Map st [Maybe ab])+ -- ^ there can be more than one transition between any two states+ -- and a transition could be the empty symbol+ , states :: [[st]] -- ^ if you don't care about grouping states into columns+ -- you can just dump everything in one big list+ }++-- | 'finalSt' returns all the final states of an automaton+finalSt :: NFA st ab -> [st]+finalSt aut =+ case isFinalSt aut of+ Nothing -> finalStList aut+ Just fn -> concatMap (filter fn) (states aut)++-- | 'lookupTrans' @aut st1 ab@ returns the states that @st1@ transitions+-- to via @a@.+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) ++addTrans :: (Ord ab, Ord st) =>+ NFA st ab+ -> st -- ^ from state+ -> Maybe ab -- ^ transition+ -> st -- ^ to state+ -> 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++-- | Returns all possible paths through an automaton from the+-- start state to any dead-end.+--+-- Each path is represented as a list of labels.+--+-- We assume that the automaton does not have any loops+-- in it.+automatonPaths :: (Ord st, Ord ab) => (NFA st ab) -> [[ab]]+automatonPaths aut = concatMap combinations $ map (filter (not.null)) $ automatonPathSets aut++-- | The set of all bundled paths. A bundled path is a sequence of+-- states through the automaton from the start state to any dead+-- end. Any two neighbouring states can have more than one+-- possible transition between them, so the bundles can multiply+-- out to a lot of different possible paths.+--+-- The output is a list of lists of lists:+--+-- * Each item in the outer list is a bundled path through the+-- automaton, i.e. without distinguishing between the possible+-- transitions from any two neighbouring states+--+-- * Each item in the middle list is represents the set of+-- transitions between two given neighbouring states+--+-- * Each item in the inner list represents a transition+-- between two given states+automatonPathSets :: (Ord st, Ord ab) => (NFA st ab) -> [[ [ab] ]]+automatonPathSets aut = helper (startSt aut)+ where+ transFor st = Map.toList `fmap` Map.lookup st (transitions aut)+ -- all the states you can get to from @st@ (and how to get there)+ -- (one item per state)+ helper st = maybe [] (concatMap next) $ transFor st+ next (st2, mtr) =+ case helper st2 of+ [] -> [[labels]]+ res -> map (labels :) res+ where labels = catMaybes mtr++numStates :: NFA st ab -> Int+numStates = sum . (map length) . states++numTransitions :: NFA st ab -> Int+numTransitions = sum . (map subTotal) . (Map.elems) . transitions+ where subTotal = sum . (map length) . (Map.elems)
− src/NLP/GenI/Automaton.lhs
@@ -1,140 +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{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
@@ -23,15 +23,16 @@ low-level and primitive (well, with the exception of feature structure unification, that is). +\ignore{ \begin{code}-{-# GHC_OPTIONS -fglasgow-exts #-}--- {-# LANGUAGE TypeSynonymInstances, FlexibleInstances, DeriveDataTypeable #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+{-# LANGUAGE MultiParamTypeClasses, 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(..),+ Pred, Flist, AvPair(..), GeniVal(..), Lexicon, ILexEntry(..), MorphLexEntry, Macros, Sem, LitConstr, SemInput, Subst, emptyLE, emptyGNode, emptyMacro, @@ -51,7 +52,7 @@ showFlist, showPairs, showAv, -- Other functions- Replacable(..), replaceOneAsMap,+ replace, DescendGeniVal(..), replaceList, Collectable(..), Idable(..), alphaConvert, alphaConvertById, fromGConst, fromGVar,@@ -59,25 +60,24 @@ -- 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.Maybe ( mapMaybe )+import Data.Function ( on ) 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)+import Data.Generics.PlateDirect++import NLP.GenI.General(filterTree, listRepNode, snd3, geniBug)+import NLP.GenI.GeniVal+ --instance Show (IO()) where -- show _ = "" \end{code}@@ -111,14 +111,26 @@ 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 Biplate (Ttree String) GeniVal where+ biplate (TT zps x1 x2 zint x3 zsem x4 x5) =+ plate TT ||* zps |- x1 |- x2+ ||+ zint |- x3+ |+ zsem |- x4 |- x5 +instance Biplate (Ttree GNode) GeniVal where+ biplate (TT zps x1 x2 zint x3 zsem x4 zt) =+ plate TT ||* zps |- x1 |- x2+ ||+ zint |- x3+ |+ zsem |- x4+ |+ zt++instance DescendGeniVal (Ttree GNode) where+ descendGeniVal s mt =+ mt { params = descendGeniVal s (params mt)+ , tree = descendGeniVal s (tree mt)+ , pinterface = descendGeniVal s (pinterface mt)+ , psemantics = descendGeniVal s (psemantics mt) }+ instance (Collectable a) => Collectable (Ttree a) where collect mt = (collect $ params mt) . (collect $ tree mt) . (collect $ psemantics mt) . (collect $ pinterface mt)@@ -155,14 +167,22 @@ , 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 Biplate ILexEntry GeniVal where+ biplate (ILE x1 x2 zps zint zfilts zeq x3 zsem x4) =+ plate ILE |- x1 |- x2+ ||* zps+ ||+ zint+ ||+ zfilts+ ||+ zeq |- x3+ ||+ zsem |- x4 +instance DescendGeniVal ILexEntry where+ descendGeniVal s i =+ i { iinterface = descendGeniVal s (iinterface i)+ , iequations = descendGeniVal s (iequations i)+ , isemantics = descendGeniVal s (isemantics i)+ , iparams = descendGeniVal s (iparams i) }+ instance Collectable ILexEntry where collect l = (collect $ iinterface l) . (collect $ iparams l) . (collect $ ifilters l) . (collect $ iequations l) .@@ -185,20 +205,16 @@ \end{code} % -----------------------------------------------------------------------\section{GNode}+\section{TAG nodes (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}+-- | A single node of a TAG tree. data GNode = GN{gnname :: NodeName,- gup :: Flist,- gdown :: Flist,- ganchor :: Bool,- glexeme :: [String],+ gup :: Flist, -- ^ top feature structure+ gdown :: Flist, -- ^ bottom feature structure+ ganchor :: Bool, -- ^ @False@ for na nodes+ glexeme :: [String], -- ^ @[]@ for na nodes gtype :: GType, gaconstr :: Bool, gorigin :: String -- ^ for TAG, this would be the elementary tree@@ -206,6 +222,15 @@ } deriving (Eq, Data, Typeable) +instance Biplate GNode GeniVal where+ biplate (GN x1 zu zd x2 x3 x4 x5 x6) =+ plate GN |- x1+ ||+ zu+ ||+ zd |- x2 |- x3 |- x4 |- x5 |- x6++instance Biplate (Tree GNode) GeniVal where+ biplate (Node zn zkids) = plate Node |+ zn ||+ zkids+ -- Node type used during parsing of the grammar data GType = Subs | Foot | Lex | Other deriving (Show, Eq, Data, Typeable)@@ -235,7 +260,7 @@ -- | Return the value of the "cat" attribute, if available gCategory :: Flist -> Maybe GeniVal gCategory top =- case [ v | (a,v) <- top, a == "cat" ] of+ case [ v | AvPair "cat" v <- top ] of [] -> Nothing [c] -> Just c _ -> geniBug $ "Impossible case: node with more than one category"@@ -281,13 +306,10 @@ 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) }+instance DescendGeniVal GNode where+ descendGeniVal s gn =+ gn { gup = descendGeniVal s (gup gn)+ , gdown = descendGeniVal s (gdown gn) } \end{code} % ----------------------------------------------------------------------@@ -295,14 +317,6 @@ % ---------------------------------------------------------------------- \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 @@ -341,7 +355,7 @@ \subsection{Substitution and Adjunction} -This module handles the strictly syntactic part of the TAG substitution and+This module handles just the tree-cutting aspects of 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.@@ -390,43 +404,12 @@ \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)+data AvPair = AvPair { avAtt :: String+ , avVal :: GeniVal }+ deriving (Ord, Eq, Data, Typeable) --- | (assumes that it's a GVar!)-fromGVar :: GeniVal -> String-fromGVar (GVar x) = x-fromGVar x = error ("fromGVar on " ++ show x)+instance Biplate AvPair GeniVal where+ biplate (AvPair a v) = plate AvPair |- a |* v \end{code} \subsection{Collectable}@@ -461,14 +444,14 @@ collect (GVar v) s = Set.insert v s collect _ s = s -instance Collectable (String,GeniVal) where- collect (_,b) = collect b+instance Collectable AvPair where+ collect (AvPair _ b) = collect b instance Collectable GNode where collect n = (collect $ gdown n) . (collect $ gup n) \end{code} -\subsection{Replacable}+\subsection{DescendGeniVal} \label{sec:replacable} \label{sec:replacements} @@ -477,74 +460,34 @@ 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}+instance DescendGeniVal a => DescendGeniVal (Map.Map k a) where+ descendGeniVal s = {-# SCC "descendGeniVal" #-} Map.map (descendGeniVal s) -Substitution on an attribute/value pairs consists of ignoring-the attribute and performing substitution on the value.+instance DescendGeniVal AvPair where+ descendGeniVal s (AvPair a v) = {-# SCC "descendGeniVal" #-} AvPair a (descendGeniVal s v) -\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 DescendGeniVal a => DescendGeniVal (String, a) where+ descendGeniVal s (n,v) = {-# SCC "descendGeniVal" #-} (n,descendGeniVal 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))+instance DescendGeniVal ([String], Flist) where+ descendGeniVal s (a,v) = {-# SCC "descendGeniVal" #-} (a, descendGeniVal 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}+-- | 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. class Idable a where idOf :: a -> Integer \end{code}@@ -556,22 +499,22 @@ why we want this. \begin{code}-alphaConvertById :: (Collectable a, Replacable a, Idable a) => a -> a+alphaConvertById :: (Collectable a, DescendGeniVal a, Idable a) => a -> a alphaConvertById x = {-# SCC "alphaConvertById" #-} alphaConvert ('-' : (show . idOf $ x)) x -alphaConvert :: (Collectable a, Replacable a) => String -> a -> a+alphaConvert :: (Collectable a, DescendGeniVal 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+ in replace subst x \end{code} \begin{code} -- | Sort an Flist according with its attributes sortFlist :: Flist -> Flist-sortFlist = sortBy (comparing fst)+sortFlist = sortBy (compare `on` avAtt) showFlist :: Flist -> String showFlist f = "[" ++ showPairs f ++ "]"@@ -580,7 +523,10 @@ showPairs = unwords . map showAv showAv :: AvPair -> String-showAv (y,z) = y ++ ":" ++ show z+showAv (AvPair y z) = y ++ ":" ++ show z++instance Show AvPair where+ show = showAv \end{code} % ----------------------------------------------------------------------@@ -594,8 +540,14 @@ type Sem = [Pred] type LitConstr = (Pred, [String]) type SemInput = (Sem,Flist,[LitConstr])-type Subst = Map.Map String GeniVal +instance Biplate Pred GeniVal where+ biplate (g1, g2, g3) = plate (,,) |* g1 |* g2 ||* g3++instance Biplate (Maybe Sem) GeniVal where+ biplate (Just s) = plate Just ||+ s+ biplate Nothing = plate Nothing+ data TestCase = TestCase { tcName :: String , tcSemString :: String -- ^ for gui@@ -612,9 +564,8 @@ 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)+instance DescendGeniVal Pred where+ descendGeniVal s (h, n, lp) = (descendGeniVal s h, descendGeniVal s n, descendGeniVal s lp) \end{code} \begin{code}@@ -716,7 +667,7 @@ \end{code} % ---------------------------------------------------------------------\subsection{Unification}+\subsection{Feature structure unification} \label{sec:fs_unification} % -------------------------------------------------------------------- @@ -765,224 +716,42 @@ \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' performs feature structure unification, under the+-- these assumptions about the input:+--+-- * Features are ordered+--+-- * The Flists do not share variables (renaming has already+-- been done.+--+-- The features are allowed to have different sets of attributes,+-- beacuse we use 'alignFeat' to realign them.+unifyFeat :: Monad m => Flist -> Flist -> m (Flist, Subst) unifyFeat f1 f2 = {-# SCC "unification" #-}- let (att, val1, val2) = alignFeat f1 f2+ let (att, val1, val2) = unzip3 $ 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}+ return (zipWith AvPair att res, subst) -\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 }@;+-- | 'alignFeat' is a pre-procesing step used to ensure that feature structures+-- have the same set of keys. If a key is missing in one, we copy it to the+-- other with an anonymous value. ----- See 'prependToSubst' for a warning!-mergeSubst :: Subst -> Subst -> Subst-mergeSubst sm1 sm2 = Map.foldWithKey (curry prependToSubst) sm2 sm1+-- The two feature structures must be sorted for this to work+alignFeat :: Flist -> Flist -> [(String,GeniVal,GeniVal)]+alignFeat f1 f2 = alignFeatH f1 f2 [] --- | 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+alignFeatH :: Flist -> Flist -> [(String,GeniVal,GeniVal)] -> [(String,GeniVal,GeniVal)]+alignFeatH [] [] acc = reverse acc+alignFeatH [] (AvPair f v :x) acc = alignFeatH [] x ((f,GAnon,v) : acc)+alignFeatH x [] acc = alignFeatH [] x acc+alignFeatH fs1@(AvPair f1 v1:l1) fs2@(AvPair f2 v2:l2) acc =+ case compare f1 f2 of+ EQ -> alignFeatH l1 l2 ((f1, v1, v2) : acc)+ LT -> alignFeatH l1 fs2 ((f1, v1, GAnon) : acc)+ GT -> alignFeatH fs1 l2 ((f2, GAnon, v2) : acc) \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
@@ -48,6 +48,11 @@ 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.AvPair where+ put (AvPair a b) = put a >> put b+ get = get >>= \a -> get >>= \b -> return (AvPair a b)+ 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
@@ -42,36 +42,44 @@ module NLP.GenI.Builder where -import Control.Monad.State+import Control.Applicative ( (<$>), (<*>) )+import Control.Monad.State.Strict import Data.Bits ( (.&.), (.|.), bit, xor )-import Data.List ( (\\), maximum )+import Data.List ( (\\), maximum, delete, sort, nub ) 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 Text.JSON +import Data.Generics.PlateDirect+import Data.Generics ( Data )+import Data.Typeable ( Typeable )+ import NLP.GenI.Automaton (NFA, automatonPaths, automatonPathSets, numStates, numTransitions) import NLP.GenI.Configuration- ( getListFlagP, getFlagP, hasFlagP, Params,+ ( getListFlagP, getFlagP, Params,+ DetectPolaritiesFlg(..), ExtraPolaritiesFlg(..), MetricsFlg(..),- IgnoreSemanticsFlg(..), RootFeatureFlg(..),- polarised )+ RootFeatureFlg(..),+ Optimisation(..), hasOpt,+ ) 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,+ AvPair(..), Flist, showFlist, gtype, GType(Subs, Foot),+ DescendGeniVal(..), Collectable(collect), alphaConvertById, GeniVal(GConst) )+import NLP.GenI.GeniParsers ( geniFeats, runParser, CharParser ) 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 )+import NLP.GenI.Tags ( TagElem(idname,tsemantics,ttree), setTidnums, TagDerivation, DerivationStep(..) ) \end{code} } @@ -103,7 +111,7 @@ , unpack :: st -> [Output] , partial :: st -> [Output] } -type Output = (UninflectedSentence, Derivation)+type Output = (LemmaPlusSentence, Derivation) type Derivation = TagDerivation \end{code} @@ -131,10 +139,18 @@ 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 +type SentenceAut = NFA Int LemmaPlus++data UninflectedDisjunction = UninflectedDisjunction [String] Flist deriving (Show, Data, Typeable)++instance Biplate UninflectedDisjunction GeniVal where+ biplate (UninflectedDisjunction a v) = plate UninflectedDisjunction |- a ||+ v++instance DescendGeniVal UninflectedDisjunction where+ descendGeniVal s (UninflectedDisjunction a v) = {-# SCC "descendGeniVal" #-} UninflectedDisjunction a (descendGeniVal s v)++instance Collectable UninflectedDisjunction where+ collect (UninflectedDisjunction _ b) = collect b \end{code} \section{BuilderState}@@ -168,11 +184,13 @@ seminput = inSemInput input -- extraPol = fromMaybe (Map.empty) $ getFlagP ExtraPolaritiesFlg config+ polsToDetect = fromMaybe (error "there should be a default for --detect-pols")+ $ getFlagP DetectPolaritiesFlg config rootFeat = getListFlagP RootFeatureFlg config -- do any optimisations- isPol = polarised config+ isPol = hasOpt Polarised config -- polarity optimisation (if enabled)- autstuff = buildAutomaton seminput cand rootFeat extraPol+ autstuff = buildAutomaton polsToDetect rootFeat extraPol seminput cand (_, seedAut, aut, sem2) = autstuff autpaths = map concat $ automatonPathSets aut combosPol = if isPol then autpaths else [cand]@@ -192,7 +210,7 @@ -- | Equivalent to 'id' unless the input contains an empty or uninstatiated -- semantics unlessEmptySem :: Input -> Params -> a -> a-unlessEmptySem input config =+unlessEmptySem input _ = 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 ]@@ -200,7 +218,7 @@ 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)+ in if null semanticsErr then id else error semanticsErr \end{code}@@ -209,6 +227,24 @@ \begin{code} -- | Performs surface realisation from an input semantics and a lexical selection.+--+-- Statistics tracked+--+-- * pol_used_bundles - number of bundled paths through the polarity automaton.+-- see 'NLP.GenI.Automaton.automatonPathSets'+--+-- * pol_used_paths - number of paths through the final automaton+--+-- * pol_seed_paths - number of paths through the seed automaton (i.e. with no polarities).+-- This is normally just 1, unless you have multi-literal semantics+--+-- * pol_total_states - combined number of states in the all the polarity automata+--+-- * pol_total_tras - combined number of transitions in all polarity automata+--+-- * pol_max_states - number of states in the polarity automaton with the most states+--+-- * pol_total_tras - number of transitions in the polarity automata with the most transitions run :: Builder st it Params -> Input -> Params -> (st, Statistics) run builder input config = let -- 1 run the setup stuff@@ -290,7 +326,7 @@ 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" ]+getIdx fs = [ v | AvPair "idx" v <- fs ] ts_iafFailure :: [String] -> [Pred] -> String ts_iafFailure is sem = "index accesibility failure -" ++ (unwords is) ++ "- blocked: " ++ showSem sem@@ -378,9 +414,6 @@ \subsection{Statistics} \begin{code}-addCounters :: Statistics -> Statistics -> Statistics-addCounters = mergeMetrics addIntMetrics- modifyStats :: (Metric -> Metric) -> BuilderState st () modifyStats fn = lift $ modify $ updateMetrics fn @@ -400,9 +433,9 @@ \begin{code} initStats :: Params -> Statistics initStats pa =- let identifyMs :: [String] -> [Metric]- identifyMs ["default"] = identifyMs defaultMetricNames- identifyMs ms = map namedMetric ms+ let mdefault ms = if "default" `elem` ms then defaultMetricNames else []+ identifyMs :: [String] -> [Metric]+ identifyMs ms = map namedMetric $ mdefault ms ++ delete "default" ms metrics = identifyMs $ fromMaybe [] $ getFlagP MetricsFlg pa in execState (mapM addMetric metrics) emptyStats @@ -449,6 +482,19 @@ type NullState a = BuilderState () a +-- | Running the null builder allows you to track certain statistics+--+-- * sem_literals - number of literals in the input semantics+--+-- * lex_trees - total number of lexically selected trees++-- * lex_foot_nodes - total number of nodes of any sort in lexically selected trees+--+-- * lex_subst_nodes - total number of sustitution nodes in lexically selected trees+--+-- * lex_foot_nodes - total number of foot nodes in lexically selected trees+--+-- * plex_... - same as the lex_ equivalent, but after polarity filtering initNullBuilder :: Input -> Params -> ((), Statistics) initNullBuilder input config = let countsFor ts = (length ts, length nodes, length sn, length an)@@ -477,4 +523,40 @@ in runState (execStateT countUp ()) (initStats config) \end{code} +% ----------------------------------------------------------------------+% strictly API-ish bits+% ---------------------------------------------------------------------- +\ignore{+\begin{code}+-- | The names of lexically selected chart items used in a derivation+lexicalSelection :: Derivation -> [String]+lexicalSelection = sort . nub . concatMap (\d -> [dsChild d, dsParent d])++-- | A lemma plus its morphological features+data LemmaPlus = LemmaPlus { lpLemma :: String+ , lpFeats :: Flist }+ deriving (Show, Eq, Ord)++-- | A sentence composed of 'LemmaPlus' instead of plain old words+type LemmaPlusSentence = [LemmaPlus]++instance JSON LemmaPlus where+ readJSON j =+ do jo <- fromJSObject `fmap` readJSON j+ let field x = maybe (fail $ "Could not find: " ++ x) readJSON+ $ lookup x jo+ LemmaPlus <$> field "lemma"+ <*> (parsecToJSON "lemma-features" geniFeats =<< field "lemma-features")+ showJSON (LemmaPlus l fs) =+ JSObject . toJSObject $ [ ("lemma", showJSON l)+ , ("lemma-features", showJSON $ showFlist fs)+ ]++parsecToJSON :: Monad m => String -> CharParser () b -> String -> m b+parsecToJSON description p str =+ case runParser p () "" str of+ Left err -> fail $ "Couldn't parse " ++ description ++ " because " ++ show err+ Right res -> return res+\end{code}+}
− src/NLP/GenI/CkyEarley/CkyBuilder.lhs
@@ -1,1244 +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 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}-{-# OPTIONS_GHC -fglasgow-exts #-}--- {-# 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
@@ -1,456 +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 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
@@ -21,26 +21,26 @@ {-# LANGUAGE ExistentialQuantification #-} module NLP.GenI.Configuration ( Params(..), GrammarType(..), BuilderType(..), Instruction, Flag- -- flags+ -- * flags , BatchDirFlg(..)+ , DetectPolaritiesFlg(..) , DisableGuiFlg(..)+ , DumpDerivationFlg(..) , EarlyDeathFlg(..) , ExtraPolaritiesFlg(..) , FromStdinFlg(..) , HelpFlg(..)- , IgnoreSemanticsFlg(..) , InstructionsFileFlg(..) , LexiconFlg(..) , MacrosFlg(..)- , MaxTreesFlg(..) , MetricsFlg(..) , MorphCmdFlg(..) , MorphInfoFlg(..)- , MorphLexiconFlg(..) , NoLoadTestSuiteFlg(..) , OptimisationsFlg(..) , OutputFileFlg(..) , PartialFlg(..)+ , RankingConstraintsFlg(..) , RegressionTestModeFlg(..) , RootFeatureFlg(..) , RunUnitTestFlg(..)@@ -51,16 +51,15 @@ , TimeoutFlg(..) , TracesFlg(..) , VerboseModeFlg(..)+ , VersionFlg(..) , ViewCmdFlg(..) -- , mainBuilderTypes- , getFlagP, getListFlagP, setFlagP, hasFlagP, deleteFlagP, hasOpt, polarised+ , getFlagP, getListFlagP, setFlagP, hasFlagP, deleteFlagP, hasOpt , getFlag, setFlag, hasFlag , Optimisation(..)- , rootcatfiltered, semfiltered- , isIaf , emptyParams, defineParams- , treatArgs, treatStandardArgs, treatArgsWithParams, treatStandardArgsWithParams+ , treatArgs, treatArgsWithParams, usage, basicSections, optionsSections , processInstructions , optionsForStandardGenI , optionsForBasicStuff, optionsForOptimisation, optionsForMorphology, optionsForInputFiles@@ -78,20 +77,22 @@ \ignore{ \begin{code} import qualified Data.Map as Map+import qualified Data.Set as Set 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 System.Environment ( getProgName ) 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.Btypes ( Flist, showFlist, ) import NLP.GenI.General ( geniBug, fst3, snd3, Interval ) import NLP.GenI.GeniParsers ( geniFeats, geniPolarities )+import NLP.GenI.PolarityTypes ( PolarityKey(..), PolarityAttr(..), readPolarityAttrs ) \end{code} } @@ -121,13 +122,6 @@ 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@@ -145,8 +139,11 @@ builderType = SimpleBuilder, grammarType = GeniHand, geniFlags = [ Flag ViewCmdFlg "ViewTAG"- , Flag RootFeatureFlg defaultRootFeat ]+ , Flag DetectPolaritiesFlg (readPolarityAttrs defaultPolarityAttrs)+ , Flag RootFeatureFlg (readRF defaultRootFeat)+ ] }+ where readRF = parseFlagWithParsec "default root feature" geniFeats \end{code} % --------------------------------------------------------------------@@ -158,7 +155,10 @@ short switch is available. For more information, type \texttt{geni --help}. + \begin{code}+type OptSection = (String,[OptDescr Flag],[String])+ -- | Uses the GetOpt library to process the command line arguments. -- Note that we divide them into basic and advanced usage. optionsForStandardGenI :: [OptDescr Flag]@@ -167,7 +167,12 @@ ++ -- FIXME: weird mac stuff [ Option ['p'] [] (reqArg WeirdFlg id "CMD") "" ] -optionsSections :: [(String,[OptDescr Flag],[String])]+basicSections :: [OptSection]+basicSections = map tweakBasic $ take 1 optionsSections+ where+ tweakBasic (x,y,z) = (x,y,z ++ ["See --help for more options"])++optionsSections :: [OptSection] optionsSections = [ ("Core options", optionsForBasicStuff, example) , ("Input", optionsForInputFiles, [])@@ -178,7 +183,6 @@ , ("Morphology", optionsForMorphology, []) , ("User interface", optionsForUserInterface, []) , ("Batch processing", optionsForTesting, [])- , ("Miscellaneous", nubBySwitches $ optionsForIgnoreSem, []) ] where example = [ "Example:"@@ -190,17 +194,7 @@ 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@@ -227,14 +221,12 @@ -- Parsing command line arguments -- ------------------------------------------------------------------- -usage :: Bool -- ^ advanced+-- | Print out a GenI-style usage message with options divided into sections+usage :: [OptSection] -- ^ options+ -> String -- ^ prog name -> 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+usage sections pname =+ let header = "Usage: " ++ pname ++ " [OPTION...]\n" body = unlines $ map usageSection sections in header ++ body @@ -245,40 +237,19 @@ 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+ (os,_,[] )-> return $ defineParams os initParams+ (_,_,errs) -> do p <- getProgName+ ioError (userError $ concat errs ++ usage basicSections p) defineParams :: [Flag] -> Params -> Params defineParams flgs prms = (\p -> foldr setDefault p $ geniFlags prms)- . maybeSetMaxTrees . (mergeFlagsP OptimisationsFlg) . (mergeFlagsP MetricsFlg) $ prms@@ -295,14 +266,14 @@ 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} +At the time of this writing (2009-09-25), it is highly unlikely that all the+options are documented here. See \verb!geni --help! for more details.+ Note that you might see an option described in more than one place because it falls into multiple categories. @@ -310,6 +281,7 @@ \subsection{Basic options} % -------------------------------------------------------------------- + \begin{code} optionsForBasicStuff :: [OptDescr Flag] optionsForBasicStuff =@@ -323,6 +295,24 @@ \subsection{Input files} % -------------------------------------------------------------------- +See Chapter \ref{cha:formats} for details on how to write these files.++\begin{description}+\item[macros]+ The \verb!macros! switch is used to supply GenI with FB-LTAG tree+ schemata.+\item[lexicon]+ The \verb!lexicon! is used for lexical entries that point to the+ macros+\item[suite]+ The \verb!suite! provides test cases on which to run GenI+\item[ranking]+ The \verb!ranking! switch allows you to specify a file containing+ Optimality Theory style constraints which GenI will use to rank+ its output. See Chapter \ref{cha:ranking} for more details on the format+ and use of this file.+\end{description}+ \begin{code} optionsForInputFiles :: [OptDescr Flag] optionsForInputFiles =@@ -333,12 +323,12 @@ , fromStdinOption , morphInfoOption , instructionsOption- , outputOption+ , rankingOption , 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, macrosOption, lexiconOption, tracesOption :: OptDescr Flag instructionsOption = Option [] ["instructions"] (reqArg InstructionsFileFlg id "FILE")@@ -346,7 +336,7 @@ macrosOption = Option ['m'] ["macros"] (reqArg MacrosFlg id "FILE")- "macros file FILE (unanchored trees)"+ "tree schemata file FILE (unanchored trees)" lexiconOption = Option ['l'] ["lexicon"] (reqArg LexiconFlg id "FILE")@@ -356,9 +346,10 @@ 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)"+rankingOption :: OptDescr Flag+rankingOption =+ Option [] ["ranking"] (reqArg RankingConstraintsFlg id "FILE")+ "ranking constraints FILE (using Optimality Theory)" \end{code} % --------------------------------------------------------------------@@ -369,9 +360,19 @@ optionsForOutput :: [OptDescr Flag] optionsForOutput = [ outputOption+ , Option [] ["dump"] (noArg DumpDerivationFlg)+ "print derivation information on stdout (JSON)" , Option [] ["partial"] (noArg PartialFlg) "return partial result(s) if no complete solution is found"+ -- same as rankingOption but with output-centric help text+ , Option [] ["ranking"] (reqArg RankingConstraintsFlg id "FILE")+ "use constraints in FILE to rank output" ]++outputOption :: OptDescr Flag+outputOption =+ Option ['o'] ["output"] (reqArg OutputFileFlg id "FILE")+ "output file FILE (stdout if unset)" \end{code} % --------------------------------------------------------------------@@ -381,7 +382,7 @@ \begin{code} optionsForUserInterface :: [OptDescr Flag] optionsForUserInterface =- [ noguiOption, helpOption+ [ noguiOption, helpOption, versionOption , Option [] ["regression"] (noArg RegressionTestModeFlg) "Run in regression testing mode (needs grammar, etc)" , Option [] ["unit-tests"] (noArg RunUnitTestFlg)@@ -390,11 +391,19 @@ "XMG tree-view command" ] -verboseOption, noguiOption, helpOption :: OptDescr Flag+noguiOption :: OptDescr Flag noguiOption = Option [] ["nogui"] (noArg DisableGuiFlg) "disable graphical user interface"++helpOption :: OptDescr Flag helpOption = Option [] ["help"] (noArg HelpFlg) "show full list of command line switches"++versionOption :: OptDescr Flag+versionOption = Option [] ["version"] (noArg VersionFlg)+ "display the version"++verboseOption :: OptDescr Flag verboseOption = Option ['v'] ["verbose"] (noArg VerboseModeFlg) "verbose mode" \end{code}@@ -422,19 +431,37 @@ polarity-related, and all the adjunction-related optimisations respectively. +\item[detect-pols]+ This tells GenI how to detect polarities in your grammar. You pass+ this in in the form of a space-delimited string, where each word is either+ an attribute or a ``restricted'' attribute. In lieu of an explanation,+ here is an example: the string ``cat idx V.tense D.c'' tells GenI that+ we should detect polarities on the ``cat'' and ``idx'' attribute+ for all nodes and also on the ``tense'' attribute for all nodes+ with the category ``V'' and the ``c'' attribute for all nodes with the+ category ``D''.++ If your grammar comes with its own hand-written polarities, you can+ suppress polarity detection altogether by supplying the empty string.++ Also, if you do not use this switch, the following defaults will be+ used:++\begin{includecodeinmanual}+\begin{code}+defaultPolarityAttrs :: String+defaultPolarityAttrs = "cat"+\end{code}+\end{includecodeinmanual}+ \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 ["-"])- ]+defaultRootFeat :: String+defaultRootFeat = "[cat:s inv:- mode:ind|subj wh:-]" \end{code} \end{includecodeinmanual} @@ -454,6 +481,9 @@ [ Option [] ["opts"] (reqArg OptimisationsFlg readOptimisations "LIST") "optimisations 'LIST' (--help for details)"+ , Option [] ["detect-pols"]+ (reqArg DetectPolaritiesFlg readPolarityAttrs "LIST")+ ("attributes 'LIST' (eg. \"cat idx V.tense\", default:" ++ show defaultPolarityAttrs ++ ")") , Option [] ["rootfeat"] (reqArg RootFeatureFlg readRF "FEATURE") ("root features 'FEATURE' (for polarities, default:"@@ -467,16 +497,20 @@ readRF = parseFlagWithParsec "root feature" geniFeats readPolarities = parseFlagWithParsec "polarity string" geniPolarities -data Optimisation =- PolOpts | AdjOpts | Polarised | NoConstraints |- RootCatFiltered | SemFiltered | Iaf {- one phase only! -}+data Optimisation = PolOpts+ | AdjOpts+ | Polarised+ | NoConstraints+ | SemFiltered+ | Iaf -- ^ one phase only!+ | EarlyNa deriving (Show,Eq,Typeable) coreOptimisationCodes :: [(Optimisation,String,String)] coreOptimisationCodes = [ (Polarised , "p", "polarity filtering")+ , (EarlyNa , "e-na", "detect null adjunction at earliest opportunity") , (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!)") ]@@ -492,7 +526,7 @@ polOpts, adjOpts :: [Optimisation] polOpts = [Polarised]-adjOpts = [RootCatFiltered, SemFiltered]+adjOpts = [EarlyNa, SemFiltered] \end{code} \begin{code}@@ -567,24 +601,20 @@ \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.+ A builder is basically a surface realisation algorithm. \geni has the+ infrastructure to support different realisation algorithms, but some+ broken ones have been removed. \end{description} \begin{code} data BuilderType = NullBuilder |- SimpleBuilder | SimpleOnePhaseBuilder |- CkyBuilder | EarleyBuilder+ SimpleBuilder | SimpleOnePhaseBuilder 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 =@@ -595,13 +625,11 @@ 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@@ -681,43 +709,11 @@ [ 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"- ]+ "morphological features FILE (default: unset)" \end{code} % --------------------------------------------------------------------@@ -735,6 +731,10 @@ \section{Scripting GenI} % ==================================================================== +\begin{description}+\item[instructions] An instructions file can be used to run GenI on+a list of test suites and cases.+ 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@@ -742,36 +742,53 @@ suite. Also, the \verb!%! character and anything after is treated as a comment. +Interaction with \verb!--testsuite! and \verb!--testcase!:+\begin{itemize}+\item If only \verb!--instructions! is set, then the first test suite+ and or test case from the instructions file is used.+\item If only \verb!--testsuite! and \verb!--testcase! are set, we+ pretend that an instructions file was supplied saying that we+ want to run the entirety of the test suite specified in+ \verb!--testsuite!.+\item If both \verb!--instructions! and \verb!--testsuite!/+ \verb!--testcase! are set then the latter are used to+ select from within the instructions.+\end{itemize}+\end{description}++ \begin{code} type Instruction = (FilePath, Maybe [String]) +-- | Update the internal instructions list, test suite and case+-- according to the contents of an instructions file. 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+ do instructions <- case getFlagP InstructionsFileFlg config of+ Nothing -> return fakeInstructions+ 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 =+ let 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+ updateTestCase p =+ if hasFlagP TestCaseFlg p then p+ else case (listToMaybe instructions >>= snd >>= listToMaybe) of+ Just c -> setFlagP TestCaseFlg c p + Nothing -> p+ updateTestSuite p =+ if hasFlagP TestSuiteFlg p then p+ else case (fst `fmap` listToMaybe instructions) of+ Just s -> setFlagP TestSuiteFlg s p+ Nothing -> p+ return . updateInstructions . updateTestSuite . updateTestCase $ config+ where+ fakeInstructions =+ case getFlagP TestSuiteFlg config of+ Just ts -> [ (ts, Nothing) ]+ Nothing -> [] instructionsFile :: String -> [Instruction] instructionsFile = mapMaybe inst . lines@@ -833,23 +850,23 @@ FLAG (BatchDirFlg, FilePath) FLAG (DisableGuiFlg, ())+FLAG (DetectPolaritiesFlg, (Set.Set PolarityAttr))+FLAG (DumpDerivationFlg, ()) FLAG (EarlyDeathFlg, ())-FLAG (ExtraPolaritiesFlg, (Map.Map String Interval))+FLAG (ExtraPolaritiesFlg, (Map.Map PolarityKey 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 (RankingConstraintsFlg, FilePath) FLAG (RegressionTestModeFlg, ()) FLAG (RootFeatureFlg, Flist) FLAG (RunUnitTestFlg, ())@@ -860,6 +877,7 @@ FLAG (TestSuiteFlg, FilePath) FLAG (TimeoutFlg, Integer) FLAG (VerboseModeFlg, ())+FLAG (VersionFlg, ()) FLAG (ViewCmdFlg, String) -- not to be exported (defaults) -- the WeirdFlg exists strictly to please OS X when you launch
src/NLP/GenI/Console.hs view
@@ -26,74 +26,79 @@ 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 System.FilePath ( (</>), takeFileName ) import NLP.GenI.Btypes- ( SemInput, showSem- , TestCase(tcSem, tcName, tcExpected)+ ( SemInput, TestCase(tcSem, tcName) ) import qualified NLP.GenI.Btypes as G import NLP.GenI.General- ( ePutStrLn, withTimeout, exitTimeout- , fst3,+ ( ePutStr, ePutStrLn, withTimeout, exitTimeout ) import NLP.GenI.Geni import NLP.GenI.Configuration ( Params- , BatchDirFlg(..), EarlyDeathFlg(..), FromStdinFlg(..), OutputFileFlg(..)- , MetricsFlg(..), RegressionTestModeFlg(..), RunUnitTestFlg(..), StatsFileFlg(..)- , TestCaseFlg(..), TimeoutFlg(..), VerboseModeFlg(..)- , hasFlagP, getFlagP+ , BatchDirFlg(..), DumpDerivationFlg(..), EarlyDeathFlg(..), FromStdinFlg(..), OutputFileFlg(..)+ , MetricsFlg(..), RankingConstraintsFlg(..), StatsFileFlg(..)+ , TestCaseFlg(..), TestSuiteFlg(..), TestInstructionsFlg(..)+ , TimeoutFlg(..), VerboseModeFlg(..)+ , hasFlagP, getListFlagP, getFlagP, setFlagP , 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)+import NLP.GenI.Statistics ( Statistics ) +import Text.JSON+import Text.JSON.Pretty ( render, pp_value )+ 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+ Nothing -> runInstructions pstRef+ Just t -> withTimeout t (timeoutErr t) $ runInstructions 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+-- | Runs the tests specified in our instructions list.+-- We assume that the grammar and lexicon 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 =+-- directory with one subdirectory per suite and per case within that suite.+runInstructions :: ProgStateRef -> IO ()+runInstructions 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+ let config = pa pst+ case getFlagP BatchDirFlg config of+ Nothing -> runTestCaseOnly pstRef >> return ()+ Just bdir -> runBatch bdir 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 $+ runBatch bdir =+ do config <- pa `fmap` readIORef pstRef+ mapM_ (runSuite bdir) $ getListFlagP TestInstructionsFlg config+ runSuite bdir (file, mtcs) =+ do modifyIORef pstRef $ \p -> p { pa = setFlagP TestSuiteFlg file (pa p) }+ config <- pa `fmap` readIORef pstRef+ -- we assume the that the suites have unique filenames+ let bsubdir = bdir </> takeFileName file+ createDirectoryIfMissing False bsubdir+ fullsuite <- loadTestSuite pstRef+ let suite = case (mtcs, getFlagP TestCaseFlg config) of+ (_, Just c) -> filter (\t -> tcName t == c) fullsuite+ (Nothing,_) -> fullsuite+ (Just cs,_) -> filter (\t -> tcName t `elem` cs) fullsuite+ if any null $ map tcName suite+ then fail $ "Can't do batch processing. The test suite " ++ file ++ " has cases with no name."+ else do ePutStrLn "Batch processing mode"+ mapM_ (runCase bsubdir) suite+ runCase bdir (G.TestCase { tcName = n, tcSem = s }) =+ do config <- pa `fmap` readIORef pstRef+ let verbose = hasFlagP VerboseModeFlg config+ earlyDeath = hasFlagP EarlyDeathFlg config+ when verbose $ ePutStrLn "======================================================" (res , _) <- runOnSemInput pstRef (PartOfSuite n bdir) s ePutStrLn $ " " ++ n ++ " - " ++ (show $ length res) ++ " results"@@ -101,29 +106,6 @@ 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)@@ -151,7 +133,6 @@ 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@@ -161,16 +142,16 @@ -> SemInput -> IO ([GeniResult], Statistics) runOnSemInput pstRef args semInput =- do modifyIORef pstRef (\x -> x{ts = semInput})+ do modifyIORef pstRef (\x -> x{ts = semInput, warnings = []}) pst <- readIORef pstRef let config = pa pst- (results', stats) <- case builderType config of+ dump = hasFlagP DumpDerivationFlg config+ useRanking = hasFlagP RankingConstraintsFlg config+ (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'+ warningsOut <- warnings `fmap` readIORef pstRef -- create directory if need be case args of PartOfSuite n f -> createDirectoryIfMissing False (f </> n)@@ -179,18 +160,35 @@ Standalone "" _ -> putStrLn Standalone f _ -> writeFile f PartOfSuite n f -> writeFile $ f </> n </> "responses"- InRegressionTest -> const $ return ()+ doWrite = case args of+ Standalone _ _ -> const (return ())+ PartOfSuite n f -> writeFile $ f </> n </> "derivations" 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+ --+ if dump+ then oWrite . ppJSON $ results+ else if useRanking+ then oWrite . unlines . map (prettyResult pst) $ results+ else oWrite . unlines . sort . concatMap grRealisations $ results+ doWrite . ppJSON $ results+ -- print any warnings we picked up along the way+ when (not $ null warningsOut) $+ do let ws = reverse warningsOut+ ePutStr $ "Warnings:\n" ++ (unlines $ map (\x -> " - " ++ x) ws)+ case args of+ PartOfSuite n f -> writeFile (f </> n </> "warnings") $ unlines ws+ _ -> return () -- print out statistical data (if available)- when (isJust $ getFlagP MetricsFlg config) $- do soWrite $ "begin stats\n" ++ showFinalStats stats ++ "end"+ when (isJust $ getFlagP MetricsFlg config) $ soWrite (ppJSON stats)+ -- return (results, stats) where+ ppJSON :: JSON a => a -> String+ ppJSON = render . pp_value . showJSON helper builder = do (results, stats, _) <- runGeni pstRef builder return (results, stats)+
src/NLP/GenI/General.hs view
@@ -37,9 +37,7 @@ -- * Triples fst3, snd3, thd3, -- * Lists- equating, comparing, map',- wordsBy, boundsCheck, isEmptyIntersect, groupByFM,@@ -67,7 +65,8 @@ import Control.Monad (liftM) import Data.Bits (shiftR, (.&.)) import Data.Char (isDigit, isSpace, toUpper, toLower)-import Data.List (intersect, groupBy, group, sort)+import Data.Function ( on )+import Data.List (foldl', intersect, groupBy, group, sort) import Data.Tree import System.IO (hPutStrLn, hPutStr, hFlush, stderr) import qualified Data.Map as Map@@ -140,7 +139,7 @@ -- around because the characters 1 < 2. To sort alphanumerically, just -- 'sortBy (comparing toAlphaNum)' toAlphaNum :: String -> [AlphaNum]-toAlphaNum = map readOne . groupBy (equating isDigit)+toAlphaNum = map readOne . groupBy ((==) `on` isDigit) where readOne s | all isDigit s = N (read s)@@ -163,22 +162,11 @@ -- 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@@ -199,7 +187,7 @@ 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+ 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,@@ -207,8 +195,8 @@ 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+ 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]@@ -325,7 +313,7 @@ -- | 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" + "\nPlease file a report on http://trac.haskell.org/GenI/newticket" -- ---------------------------------------------------------------------- -- Intervals
src/NLP/GenI/Geni.lhs view
@@ -25,11 +25,19 @@ involve some messy IO performance tricks. \begin{code}-module NLP.GenI.Geni (ProgState(..), ProgStateRef, emptyProgState,+module NLP.GenI.Geni (+ -- * main interface+ ProgState(..), ProgStateRef, emptyProgState,+ initGeni,+ runGeni, runGeniWithSelector,+ GeniResult(..), ResultType(..),+ -- * helpers+ lemmaSentenceString, prettyResult, showRealisations, groupAndCount,- initGeni, runGeni, runGeniWithSelector, getTraces, GeniResult, Selector,+ getTraces, Selector, loadEverything, loadLexicon, loadGeniMacros, loadTestSuite, loadTargetSemStr,+ loadRanking, readRanking, combine, -- used by auxiliary tools only@@ -40,38 +48,42 @@ \ignore{ \begin{code}-import Control.Arrow (first)+import Control.Applicative ((<$>),(<*>))+import Control.Arrow ((&&&)) import Control.Monad.Error import Control.Monad (unless) import Data.Binary (Binary, decodeFile)+import Data.Function ( on ) import Data.IORef (IORef, readIORef, modifyIORef) import Data.List+import Data.List.Split ( wordsBy ) import qualified Data.Map as Map import Data.Maybe (mapMaybe, fromMaybe, isJust) import Data.Tree (Tree(Node)) import Data.Typeable (Typeable) +import qualified System.IO.UTF8 as UTF8+ import System.IO.Unsafe (unsafePerformIO)-import Text.ParserCombinators.Parsec +import Text.JSON -- import System.Process import NLP.GenI.General(filterTree, repAllNode,- equating, groupAndCount, multiGroupByFM,+ groupAndCount, multiGroupByFM, geniBug, repNodeByNode,- wordsBy, fst3, ePutStr, ePutStrLn, eFlush, ) import NLP.GenI.Btypes (Macros, MTtree, ILexEntry, Lexicon,- Replacable(..),+ replace, replaceList, Sem, SemInput, TestCase(..), sortSem, subsumeSem, params,- GeniVal(GConst), fromGVar,- GNode(ganchor, gnname, gup, gdown, gaconstr, gtype, gorigin), Flist,+ GeniVal(GConst), fromGVar, AvPair(..),+ GNode(ganchor, gnname, gup, gdown, gaconstr, gtype, gorigin), GType(Subs, Other), isemantics, ifamname, iword, iparams, iequations, iinterface, ifilters,@@ -93,9 +105,10 @@ import NLP.GenI.Configuration ( Params, getFlagP, hasFlagP, hasOpt, Optimisation(NoConstraints) , MacrosFlg(..), LexiconFlg(..), TestSuiteFlg(..), TestCaseFlg(..)- , MorphInfoFlg(..), MorphCmdFlg(..), MorphLexiconFlg(..)+ , MorphInfoFlg(..), MorphCmdFlg(..)+ , RankingConstraintsFlg(..) , PartialFlg(..)- , IgnoreSemanticsFlg(..), FromStdinFlg(..), VerboseModeFlg(..)+ , FromStdinFlg(..), VerboseModeFlg(..) , NoLoadTestSuiteFlg(..) , TracesFlg(..) , grammarType@@ -106,9 +119,11 @@ import NLP.GenI.GeniParsers (geniMacros, geniTagElems, geniLexicon, geniTestSuite, geniTestSuiteString, geniSemanticInput,- geniMorphInfo, geniMorphLexicon,+ geniMorphInfo,+ parseFromFile, runParser, Parser, ) import NLP.GenI.Morphology+import NLP.GenI.OptimalityTheory import NLP.GenI.Statistics (Statistics) -- import CkyBuilder @@ -132,14 +147,18 @@ 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],+ -- | OT constraints (optional)+ ranking :: OtRanking, -- | simplified traces (optional)- traces :: [String]+ traces :: [String],+ -- | any warnings accumulated during realisation+ -- (most recent first)+ warnings :: [String] } type ProgStateRef = IORef ProgState@@ -151,12 +170,17 @@ , gr = [] , le = Map.empty , morphinf = const Nothing- , morphlex = Nothing , ts = ([],[],[]) , tcase = [] , tsuite = [] , traces = []+ , ranking = []+ , warnings = [] }++-- | Log another warning in our internal program state+addWarning :: ProgStateRef -> String -> IO ()+addWarning pstRef s = modifyIORef pstRef $ \p -> p { warnings = s : warnings p } \end{code} % --------------------------------------------------------------------@@ -201,11 +225,11 @@ when isNotPreanchored $ loadLexicon pstRef -- in any case, we have to... loadMorphInfo pstRef- when useTestSuite $ loadTestSuite pstRef- -- the morphological lexicon- loadMorphLexicon pstRef+ when useTestSuite $ loadTestSuite pstRef >> return () -- the trace filter file loadTraces pstRef+ -- OT ranking+ loadRanking pstRef \end{code} The file loading functions all work the same way: we load the file,@@ -214,41 +238,53 @@ an IORef. \begin{code}-loadLexicon, loadGeniMacros, loadMorphInfo, loadMorphLexicon, loadTraces :: ProgStateRef -> IO ()-+loadLexicon :: ProgStateRef -> IO () loadLexicon pstRef =- do config <- pa `fmap` readIORef pstRef- let getSem l = if hasFlagP IgnoreSemanticsFlg config- then [] else isemantics l+ do let getSem l = isemantics l sorter l = l { isemantics = (sortSem . getSem) l } cleanup = mapBySemKeys isemantics . map sorter- loadThingOrDie LexiconFlg "lexicon" pstRef+ xs <- loadThingOrDie LexiconFlg "lexicon" pstRef (parseFromFileOrFail geniLexicon)- (\l p -> p { le = cleanup l })+ modifyIORef pstRef (\p -> p { le = cleanup xs }) -- | The macros are stored as a hashing function in the monad.+loadGeniMacros :: ProgStateRef -> IO () loadGeniMacros pstRef =- loadThingOrDie MacrosFlg "trees" pstRef parser updater+ do xs <- loadThingOrDie MacrosFlg "trees" pstRef parser+ modifyIORef pstRef (\p -> p { gr = xs }) where parser = parseFromFileMaybeBinary geniMacros- updater g p = p { gr = g } -- -- | The results are stored as a lookup function in the monad.+loadMorphInfo :: ProgStateRef -> IO () loadMorphInfo pstRef =- loadThingOrIgnore MorphInfoFlg "morphological info" pstRef parser updater+ do xs <- loadThingOrIgnore MorphInfoFlg "morphological info" pstRef parser+ modifyIORef pstRef (\p -> p { morphinf = readMorph xs } ) 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 :: ProgStateRef -> IO () loadTraces pstRef =- loadThingOrIgnore TracesFlg "traces" pstRef- (\f -> lines `fmap` readFile f)- (\t p -> p {traces = t})+ do xs <- loadThingOrIgnore TracesFlg "traces" pstRef+ (\f -> lines `fmap` readFile f)+ modifyIORef pstRef (\p -> p {traces = xs})++loadRanking :: ProgStateRef -> IO ()+loadRanking pstRef =+ do config <- pa `fmap` readIORef pstRef+ let verbose = hasFlagP VerboseModeFlg config+ case getFlagP RankingConstraintsFlg config of+ Nothing -> return ()+ Just f -> do r <- readRanking verbose f+ modifyIORef pstRef (\p -> p { ranking = r })++readRanking :: Bool -- ^ verbose+ -> FilePath -> IO OtRanking+readRanking verbose f =+ do when verbose $ do+ ePutStr $ unwords [ "Loading OT constraints", f ++ "... " ]+ eFlush+ mr <- (resultToEither . decode) `fmap` UTF8.readFile f -- utf-8?+ when verbose $ ePutStr "done"+ either fail return mr \end{code} \subsubsection{Target semantics}@@ -263,22 +299,23 @@ \begin{code} -- | Stores the results in the tcase and tsuite fields-loadTestSuite :: ProgStateRef -> IO ()+loadTestSuite :: ProgStateRef -> IO [TestCase] 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+ let parser f = do+ sem <- parseFromFileOrFail geniTestSuite f+ mStrs <- parseFromFileOrFail geniTestSuiteString f+ return $ zip sem mStrs+ updater s x =+ x { tsuite = s+ , tcase = fromMaybe "" $ getFlagP TestCaseFlg config}+ cleanup (tc,str) =+ tc { tcSem = (sortSem sm, sort sr, lc)+ , tcSemString = str }+ where (sm, sr, lc) = tcSem tc+ xs <- map cleanup `fmap` loadThingOrDie TestSuiteFlg "test suite" pstRef parser+ modifyIORef pstRef (updater xs)+ return xs \end{code} Sometimes, the target semantics does not come from a file, but from@@ -289,8 +326,7 @@ -- | 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+ do parseSem where parseSem = do let sem = runParser geniSemanticInput () "" str@@ -303,38 +339,33 @@ \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 ()+ -> IO [a] -- | Load the file if the relevant option is set, otherwise ignore-loadThingOrIgnore flag description pstRef parser job =+loadThingOrIgnore flag description pstRef parser = do config <- pa `fmap` readIORef pstRef case getFlagP flag config of- Nothing -> return ()- Just f -> loadThing f description pstRef parser job+ Nothing -> return []+ Just f -> loadThing f description pstRef parser -- | Load the file if the relevant option is set, otherwise complain and die-loadThingOrDie flag description pstRef parser job =+loadThingOrDie flag description pstRef parser = 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+ Just f -> loadThing f description pstRef parser -loadThing filename description pstRef parser job =+loadThing :: FilePath -- ^ file to load+ -> String -- ^ description+ -> ProgStateRef+ -> (FilePath -> IO [a]) -- ^ parsing cmd+ -> IO [a]+loadThing filename description pstRef parser = do config <- pa `fmap` readIORef pstRef let verbose = hasFlagP VerboseModeFlg config when verbose $ do@@ -342,7 +373,7 @@ eFlush theTs <- parser filename when verbose $ ePutStr $ (show $ length theTs) ++ " entries\n"- modifyIORef pstRef (job theTs)+ return theTs parseFromFileOrFail :: Parser a -> FilePath -> IO a parseFromFileOrFail p f = parseFromFile p f >>= either (fail.show) (return)@@ -373,8 +404,23 @@ \end{enumerate} \begin{code}-type GeniResult = (String, B.Derivation)+data GeniResult = GeniResult+ { grLemmaSentence :: B.LemmaPlusSentence+ , grRealisations :: [String]+ , grDerivation :: B.Derivation+ , grLexSelection :: [ GeniLexSel ]+ , grRanking :: Int+ , grViolations :: [ OtViolation ]+ , grResultType :: ResultType+ } deriving (Ord, Eq) +data GeniLexSel = GeniLexSel+ { nlTree :: String+ , nlTrace :: [String]+ } deriving (Ord, Eq)++data ResultType = CompleteResult | PartialResult deriving (Ord, Eq)+ -- | 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@@ -384,26 +430,22 @@ runGeniWithSelector :: ProgStateRef -> Selector -> B.Builder st it Params -> IO ([GeniResult], Statistics, st) runGeniWithSelector pstRef selector builder =- do let run = B.run builder+ do pst <- readIORef pstRef+ let config = pa pst+ run = B.run builder unpack = B.unpack builder- getPartial = B.partial builder- -- step 1+ -- step 1: lexical selection 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+ -- step 2: chart generation+ let (finalSt, stats) = run initStuff config+ -- step 3: unpacking+ let uninflected = unpack finalSt+ tryPartial = null uninflected && hasFlagP PartialFlg config+ rawResults = if tryPartial then B.partial builder finalSt else uninflected+ resultTy = if tryPartial then PartialResult else CompleteResult+ -- step 4: post-processing+ results <- finaliseResults pstRef resultTy rawResults+ return (results, stats, finalSt) \end{code} % --------------------------------------------------------------------@@ -429,11 +471,11 @@ then p { ts = (fst3 (ts p),[],[]) } else p) -- lexical selection- pstLex <- readIORef pstRef- (cand, lexonly) <- lexSelector pstLex+ (cand, lexonly) <- lexSelector pstRef+ pst <- readIORef pstRef -- strip morphological predicates- let (tsem,tres,lc) = ts pstLex- tsem2 = stripMorphSem (morphinf pstLex) tsem+ let (tsem,tres,lc) = ts pst+ tsem2 = stripMorphSem (morphinf pst) tsem -- let initStuff = B.Input { B.inSemInput = (tsem2, tres, lc)@@ -441,18 +483,33 @@ , 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+-- | 'finaliseResults' does any post-processing steps that we want to integrate+-- into mainline GenI. So far, this consists of morphological realisation and+-- OT ranking+finaliseResults :: ProgStateRef -> ResultType -> [B.Output] -> IO [GeniResult]+finaliseResults pstRef ty os =+ do pst <- readIORef pstRef+ -- morph TODO: make this a bit safer+ mss <- case getFlagP MorphCmdFlg (pa pst) of+ Nothing -> return $ map sansMorph sentences+ Just cmd -> map snd `fmap` inflectSentencesUsingCmd cmd sentences+ -- OT ranking+ let unranked = zipWith (sansRanking pst) os mss+ rank = rankResults (getTraces pst) grDerivation (ranking pst)+ return . map addRanking . rank $ unranked where- (ss,ds) = unzip os- merge ms d = map (\m -> (m,d)) ms+ sentences = map fst os+ sansRanking pst (l,d) rs =+ GeniResult { grLemmaSentence = l+ , grRealisations = rs+ , grDerivation = d+ , grLexSelection = map (\x -> GeniLexSel x (getTraces pst x)) (B.lexicalSelection d)+ , grRanking = -1+ , grViolations = []+ , grResultType = ty+ }+ addRanking (i,res,vs) = res { grViolations = vs, grRanking = i } \end{code} % --------------------------------------------------------------------@@ -470,9 +527,21 @@ in if null sentences then "(none)" else unlines sentencesGrouped-\end{code} -\begin{code}+-- | No morphology! Pretend the lemma string is a sentence+lemmaSentenceString :: GeniResult -> String+lemmaSentenceString = unwords . map lpLemma . grLemmaSentence++prettyResult :: ProgState -> GeniResult -> String+prettyResult pst nr =+ concat . intersperse "\n" . map showOne . grRealisations $ nr+ where+ showOne str = show theRanking ++ ". " ++ str ++ "\n" ++ violations+ violations = prettyViolations tracesFn verbose (grViolations nr)+ theRanking = grRanking nr+ verbose = hasFlagP VerboseModeFlg (pa pst)+ tracesFn = getTraces pst+ -- | '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@@ -489,7 +558,7 @@ -- | We assume the name was constructed by 'combineName' readPidname :: String -> String readPidname n =- case wordsBy ':' n of+ case wordsBy (== ':') n of (_:_:p:_) -> p _ -> geniBug "readPidname or combineName are broken" \end{code}@@ -507,9 +576,10 @@ succeed in anchoring it. \begin{code}-runLexSelection :: ProgState -> IO ([TagElem], [ILexEntry])-runLexSelection pst =- do -- select lexical items first +runLexSelection :: ProgStateRef -> IO ([TagElem], [ILexEntry])+runLexSelection pstRef =+ do pst <- readIORef pstRef+ -- select lexical items first let (tsem,_,litConstrs) = ts pst lexicon = le pst lexCand = chooseLexCand lexicon tsem@@ -518,8 +588,10 @@ -- then anchor these lexical items to trees let grammar = gr pst combineWithGr l =- do let (_, res) = combineList grammar l+ do let (lexCombineErrors, res) = combineList grammar l familyMembers = [ p | p <- grammar, pfamily p == ifamname l ]+ mapM_ (addWarning pstRef . showErr) $ compressLexCombineErrors+ $ lexCombineErrors -- snippets of error message let lexeme = showLexeme.iword $ l _outOfFamily n = show n ++ "/" ++ (show $ length familyMembers)@@ -529,10 +601,7 @@ [] -> 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) ++ "."+ showWarning xs@(x0:_) = addWarning pstRef $ "Missing co-anchor '" ++ x0 ++ "'" ++ " in " ++ _outOfFamily (length xs) ++ "." -- print out enrichment errors {- unless (null enrichEs) $ do@@ -578,15 +647,20 @@ 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"+ unless (null missedSem) $ addWarning pstRef $ "no lexical selection for " ++ showSem missedSem+ unless (null missedLex) $ forM_ missedLex $ \l -> addWarning pstRef $+ "'" ++ showLex l ++ "' was lexically selected, but not anchored to any trees" return (candFinal, lexCand) where showLex l = (showLexeme $ iword l) ++ "-" ++ (ifamname l)+ showErr (c, e) = show e ++ " (" ++ show c ++ " times)" +compressLexCombineErrors :: [LexCombineError] -> [(Int, LexCombineError)]+compressLexCombineErrors = map (length &&& head) . groupBy h+ where+ h (EnrichError m1 l1 _) (EnrichError m2 l2 _) = pfamily m1 == pfamily m2 &&+ iword l1 == iword l2+ h _ _ = False+ -- | Select and returns the set of entries from the lexicon whose semantics -- subsumes the input semantics. chooseLexCand :: Lexicon -> Sem -> [ILexEntry]@@ -682,10 +756,9 @@ 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")+ show (BoringError s) = s+ show (OtherError t l s) = s ++ " on " ++ 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@@ -775,11 +848,18 @@ return (l,e2) \end{code} -\subsubsection{CGM Enrichement}+\subsubsection{Enrichment} -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.+Enrichment is a process which adds features to either the interface, an+explicitly named node or the co-anchor of a lexically selected tree. The+enrichement information comes from the lexicon in the form of a path equations+which specify+\begin{enumerate}+\item the location+\item top or bottom+\item the attribute+\item what value to associate with it+\end{enumerate} The conventions taken by GenI for path equations are: @@ -832,7 +912,7 @@ -- enrich everything else foldM (enrichBy l) t2 namedE where- toAvPair ((_,_,a),v) = (a,v)+ toAvPair ((_,_,a),v) = AvPair a v enrichInterface tx en = do (i2, isubs) <- unifyFeat [toAvPair en] (pinterface tx) `catchError` (\_ -> throwError $ ifaceEnrichErr en)@@ -851,7 +931,7 @@ 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+ (newfeat, sub) <- unifyFeat [AvPair eqAtt eqVal] tfeat `catchError` (\_ -> throwError enrichErr) let newnode = if eqTop then a {gup = newfeat} else a {gdown = newfeat}@@ -869,7 +949,7 @@ missingCoanchors :: ILexEntry -> MTtree -> [String] missingCoanchors lexEntry t = -- list monad- do eq <- nubBy (equating pathEqName) $ snd $ lexEquations lexEntry+ do eq <- nubBy ((==) `on` pathEqName) $ snd $ lexEquations lexEntry let name = pathEqName eq case seekCoanchor name t of Nothing -> [name]@@ -879,8 +959,14 @@ -- or (co-)anchor modifiers lexEquations :: ILexEntry -> ([PathEqPair], [PathEqPair]) lexEquations =- partition (nameIs "interface") . map (first parsePathEq) . iequations- where nameIs n x = pathEqName x == n+ partition (nameIs "interface") . map parseAv . iequations+ where+ parseAv (AvPair a v) =+ case parsePathEq a of+ Left (err,peq) -> unsafePerformIO $ do putStrLn err+ return (peq,v)+ Right peq -> (peq, v)+ nameIs n x = pathEqName x == n seekCoanchor :: String -> MTtree -> Maybe GNode seekCoanchor eqName t =@@ -896,25 +982,25 @@ matchNodeName n = (== n) . gnname -- | Parse a path equation using the GenI conventions-parsePathEq :: String -> PathEqLhs+-- This always succeeds, but can return @Just warning@+-- if anything anomalous comes up+parsePathEq :: String -> Either (String,PathEqLhs) (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 +++ case wordsBy (== '.') e of+ (n:"top":r) -> Right (n, True, rejoin r)+ (n:"bot":r) -> Right (n, False, rejoin r)+ ("top":r) -> Right ("anchor", True, rejoin r)+ ("bot":r) -> Right ("anchor", False, rejoin r)+ ("anc":r) -> parsePathEq $ rejoin $ "anchor":r+ ("anchor":r) -> Right ("anchor", False, rejoin r)+ ("interface":r) -> Right ("interface", False, rejoin r)+ (n:r) -> Left (err, (n, True, rejoin r))+ where err = "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+ _ -> Left (err, ("", True, e))+ where err = "Warning: could not interpret path equation " ++ e where- rejoin = concat . (intersperse ".")+ rejoin = concat . intersperse "." \end{code} \subsubsection{Lemanchor mechanism}@@ -960,7 +1046,7 @@ Just l -> l lemAnchor :: GNode -> Maybe [String] lemAnchor n =- case [ v | (a,v) <- gdown n, a == _lemanchor ] of+ case [ v | AvPair a v <- gdown n, a == _lemanchor ] of [GConst l] -> Just l _ -> Nothing @@ -990,7 +1076,7 @@ \begin{code} -- | Only used for instances of GenI where the grammar is compiled -- directly into GenI.-type Selector = ProgState -> IO ([TagElem],[ILexEntry])+type Selector = ProgStateRef -> IO ([TagElem],[ILexEntry]) defaultSelector :: Selector defaultSelector = runLexSelection@@ -1010,20 +1096,54 @@ \end{code} % ---------------------------------------------------------------------\section{Morphology} +% Boring utility code % -------------------------------------------------------------------- +\ignore{ \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}+instance JSON GeniResult where+ readJSON j =+ do jo <- fromJSObject `fmap` readJSON j+ let field x = maybe (fail $ "Could not find: " ++ x) readJSON+ $ lookup x jo+ GeniResult <$> field "raw"+ <*> field "realisations"+ <*> field "derivation"+ <*> field "lexical-selection"+ <*> field "ranking"+ <*> field "violations"+ <*> field "result-type"+ showJSON nr =+ JSObject . toJSObject $ [ ("raw", showJSON $ grLemmaSentence nr)+ , ("realisations", showJSONs $ grRealisations nr)+ , ("derivation", showJSONs $ grDerivation nr)+ , ("lexical-selection", showJSONs $ grLexSelection nr)+ , ("ranking", showJSON $ grRanking nr)+ , ("violations", showJSONs $ grViolations nr)+ , ("result-type", showJSON $ grResultType nr)+ ] +instance JSON ResultType where+ readJSON j =+ do js <- fromJSString `fmap` readJSON j+ case js of+ "partial" -> return PartialResult+ "complete" -> return CompleteResult+ ty -> fail $ "unknown result type: " ++ ty+ showJSON CompleteResult = JSString $ toJSString "complete"+ showJSON PartialResult = JSString $ toJSString "partial"++instance JSON GeniLexSel where+ readJSON j =+ do jo <- fromJSObject `fmap` readJSON j+ let field x = maybe (fail $ "Could not find: " ++ x) readJSON+ $ lookup x jo+ GeniLexSel <$> field "lex-item"+ <*> field "trace"+ showJSON x =+ JSObject . toJSObject $ [ ("lex-item", showJSON $ nlTree x)+ , ("trace", showJSONs $ nlTrace x)+ ]+\end{code}+}
src/NLP/GenI/GeniParsers.lhs view
@@ -15,49 +15,76 @@ % 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)}+\chapter{File formats}+\label{cha:formats} \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.+This chapter is a description of the file formats used by \geni. We'll be+using EBNFs to describe the format below. Here are some rules and types of+rules we leave out, and prefer to describe informally: +\begin{verbatim}+<alpha-numeric>+<string-literal> (stuff between quotes)+<opt-whatever> (systematically... "" | <whatever>)+<keyword-whatever> (systematically.. "whatever" ":")+\end{verbatim}+ \ignore{ \begin{code} module NLP.GenI.GeniParsers (- -- test suite stuff+ -- * Test suites 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,+ -- * Trees+ geniMacros, geniTagElems,+ -- * Lexicon and morph+ geniLexicon, geniMorphInfo,+ -- * Basics+ geniFeats, geniPolarities, geniSemantics, geniValue, geniWords,+ -- * Helpers+ geniWord, geniLanguageDef, tillEof,+ --+ parseFromFile, -- UTF-8 version+ module Text.ParserCombinators.Parsec ) 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 NLP.GenI.PolarityTypes+ import Control.Monad (liftM, when) import Data.List (sort)-import qualified Data.Map as Map +import qualified Data.Map as Map import qualified Data.Tree as T-import Text.ParserCombinators.Parsec+import Text.ParserCombinators.Parsec hiding (parseFromFile) import Text.ParserCombinators.Parsec.Language (emptyDef)-import Text.ParserCombinators.Parsec.Token (TokenParser, +import Text.ParserCombinators.Parsec.Token (TokenParser, LanguageDef(..), makeTokenParser) import qualified Text.ParserCombinators.Parsec.Token as P+import qualified System.IO.UTF8 as UTF8 +\end{code}+}++\section{General notes}++\subsection{Comments}++Any \geni format file can include comments. Comments start \verb!%!.+There is also the option of using \verb'/* */' for embedded comments.++\subsection{Reserved words}++The following are reserved words. You should not use them as variable names.+NB: the reserved words are indicated below between quotes; eg. ``semantics''.+You can ignore C pre-processor noise such as \verb!#define SEMANTICS!++\begin{includecodeinmanual}+\begin{code} -- reserved words #define SEMANTICS "semantics" #define SENTENCE "sentence"@@ -75,102 +102,172 @@ #define BEGIN "begin" #define END "end" \end{code}-}+\end{includecodeinmanual} -\section{Test suites}+\subsection{Lexer} -The test suite format consists of arbitrarily many test cases:+For reference, we include the Parsec LanguageDef that we use to implement+the \geni format. +\begin{includecodeinmanual} \begin{code}-geniTestSuite :: Parser [TestCase]-geniTestSuite = - tillEof (many geniTestCase)+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 "_'+-."+\end{code}+\end{includecodeinmanual} --- | Just the String representations of the semantics--- in the test suite-geniTestSuiteString :: Parser [String]-geniTestSuiteString =- tillEof (many geniTestCaseString)+\section{The basics} --- | This is only used by the script genimakesuite-geniDerivations :: Parser [TestCaseOutput]-geniDerivations = tillEof $ many geniOutput+\subsection{Variables and constants}++Below are some examples of \geni variables and constants. Note that we support+atomic disjunction of constants, as in \verb!Foo|bar|baz!, but not variables.++\begin{center}+\begin{tabular}{ll}+anonymous variable & \verb!?_! or \verb!_! \\+variables & \verb!Foo!, \verb!?X! or \verb!?x! \\+constants & \verb!Foo!, \verb!foo!, \verb!X!, \verb!x! or \verb!Foo|bar! \\+\end{tabular}+\end{center}++Here is an EBNF for GenI variables and constants++\begin{SaveVerbatim}{KoweyTmp}+<value> ::= <variable> | <anonymous-variable> | <constant-disj>+<variable> ::= "?" <identifier>+<anonymous> ::= "?_" | "_"+<constant-disj> ::= <constant> (| <constant>)*+<constant> ::= <identifier>+<identifier> ::= <alphanumeric> | "+" | "-" | "_"+\end{SaveVerbatim}+\begin{center}+\fbox{\BUseVerbatim{KoweyTmp}}+\end{center}++\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} -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.+\subsection{Feature structures} +In addition to variables and constants, \geni also makes heavy use of flat+feature structures. They take the form \verb![foo:bar ping:?Pong]!, or more+formally,++\begin{SaveVerbatim}{KoweyTmp}+<feature-structure> ::= "[" <atttribute-value-pair>* "]"+<attribute-value-pair> ::= <identifier> ":" <value>+\end{SaveVerbatim}+\begin{center}+\fbox{\BUseVerbatim{KoweyTmp}}+\end{center}+ \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+geniFeats :: Parser Flist+geniFeats = option [] $ squares $ many geniAttVal --- 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)+geniAttVal :: Parser AvPair+geniAttVal = do+ att <- identifier <?> "an attribute"; colon+ val <- geniValue <?> "a GenI value"+ return $ AvPair att val+\end{code} -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)+\subsection{Semantics}+\label{sec:geni-semantics} -withWhite :: Parser a -> Parser a-withWhite p = p >>= (\a -> whiteSpace >> return a)+A \jargon{semantics} is basically a set of literals. Semantics are used in+to provide \geni input (section \ref{sec:geni-input-semantics}) and in the+definition of lexical entries (section \ref{sec:geni-lexicon}). -geniSentence :: Parser String-geniSentence = optional (keyword SENTENCE) >> squares geniWords+Notice that this is a flat semantic representation! No literals within+literals, please. A literal can take one of two forms:+\begin{verbatim}+ handle:predicate(arguments)+ predicate(arguments)+\end{verbatim} -geniWords :: Parser String-geniWords =- unwords `fmap` (sepEndBy1 geniWord whiteSpace <?> "a sentence")+The arguments are space-delimited. Not providing a handle is+equivalent to providing an anonymous one. -geniWord :: Parser String-geniWord = many1 (noneOf "[]\v\f\t\r\n ")+\begin{SaveVerbatim}{KoweyTmp}+<semantics> ::= <keyword-semantics> "[" <literal>* "]"+<literal> ::= <identifier> "(" <value>* ")"+\end{SaveVerbatim}+\begin{center}+\fbox{\BUseVerbatim{KoweyTmp}}+\end{center} --- | 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+\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} -\section{Semantics}+\section{Semantic inputs and test suites}+\label{sec:geni-input-semantics} -\fnlabel{geniSemanticInput} consists of a semantics, and optionally a-set of index constraints.+\subsection{Semantic input} -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.+The semantic input can either be provided directly in the graphical interface+or as part of a test suite. -Index constraints are represented as feature structures. For more-details about them, see \fnref{detectIdxConstraints}.+The format for semantic inputs is actually a bit richer than the core+definition in section \ref{sec:geni-semantics}, but I have not yet written the+documentation for it. +\textbf{TODO}: 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.+ \begin{code} geniSemanticInput :: Parser (Sem,Flist,[LitConstr]) geniSemanticInput =@@ -181,7 +278,7 @@ let sem2 = createHandles sem semlitC2 = [ (s,c) | (s,c) <- zip sem2 litC, (not.null) c ] return (createHandles sem, idxC, semlitC2)- where + where -- set all anonymous handles to some unique value -- this is to simplify checking if a result is -- semantically complete@@ -189,7 +286,7 @@ createHandles = zipWith setHandle ([1..] :: [Int]) -- setHandle i (h, pred_, par) =- let h2 = if h /= GAnon then h + let h2 = if h /= GAnon then h else GConst ["genihandle" ++ (show i)] in (h2, pred_, par) --@@ -232,42 +329,175 @@ toSemInputString (_,lc,_) s = SemInputString s lc \end{code} +\subsection{Test suite}++\geni accepts an entire test suite of semantic inputs that you can choose from.+The test suite entries can be named. In fact, it is probably a good idea to do+so, because the names are often shorter than the expected output, and easier to+read than the semantics. Note the expected output isn't used by \geni itself,+but external tools that ``test'' \geni.++\begin{SaveVerbatim}{KoweyTmp}+<test-suite> ::= <test-suite-entry>*+<test-suite-entry> ::= <opt-identifier> <semantics> <expected-output>*+<expected-output> ::= <opt-keyword-sentence> "[" <identifier>* "]"+\end{SaveVerbatim}+\begin{center}+\fbox{\BUseVerbatim{KoweyTmp}}+\end{center}++\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++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{Lexicon}+\label{sec:geni-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 :+The lexicon associates semantic entries with lemmas and trees.++\subsection{Lexicon examples}++There are two ways to write the lexicon. We show the old (deprecated)+way first because most of the examples are still written in this style.++\paragraph{Example 1 (deprecated)}++\begin{verbatim}+le clitic (?I)+semantics:[]++le Det (?I)+semantics:[def(?I)]++livre nC (?I)+semantics:[book(?I)]++persuader vArity3 (?E ?X ?Y ?Z)+semantics:[?E:convince(?X ?Y ?Z)]++persuader v vArity3controlObj+semantics:[?E:convince(?X ?Y ?Z)]+\end{verbatim}++\paragraph{Example 2 (preferred)}++\begin{verbatim}+detester n0Vn1+equations:[theta1:agent theta2:patient arg1:?X arg2:?Y evt:?L]+filters:[family:n0Vn1]+semantics:[?E:hate(?L) ?E:agent(?L ?X) ?E:patient(?L ?Y)]+\end{verbatim}++\subsection{Notes about lexicons}+ \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.+\item The semantics associated with a lexicali item may have more than one literal+\begin{verbatim}+cher adj (?E ?X ?Y)+semantics:[?E:cost(?X ?Y) ?E:high(?Y)]+\end{verbatim}++\item A lemma may have more than one distinct semantics+\begin{verbatim}+bank n (?X)+semantics:[bank(?X)]++bank v (?E ?X ?D)+semantics:[?E:lean(?X,?D)]+\end{verbatim}++\item A semantics may be realised by more than one lexical entry (e.g. synonynms)+\begin{verbatim}+livre nC (?I)+semantics:[book(?I)]++bouquin nC (?I)+semantics:[book(?I)]+\end{verbatim} \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} +\subsection{Lexicon EBNF}++\begin{SaveVerbatim}{KoweyTmp}+<lexicon> ::= <lexicon-entry>*+<lexicon-entry> ::= <lexicon-header> <opt-filters> <semantics>+<lexicon-header> ::= <lemma> <family> <parameters>+ | <lemma> <family> <keyword-equations> <feature-structure>+<parameters> ::= "(" <value>* <opt-interface> ")"+<interface> ::= "!" <attribute-value-pairs>*+<filters> ::= <keyword-filter> <feature-structure>+\end{SaveVerbatim}+\begin{center}+\fbox{\BUseVerbatim{KoweyTmp}}+\end{center}+ \begin{code} geniLexicon :: Parser [ILexEntry] geniLexicon = tillEof $ many1 geniLexicalEntry geniLexicalEntry :: Parser ILexEntry-geniLexicalEntry = +geniLexicalEntry = do lemma <- (looseIdentifier <|> stringLiteral) <?> "a lemma" family <- identifier <?> "a tree family" (pars, interface) <- option ([],[]) $ parens paramsParser@@ -279,28 +509,157 @@ (sem,pols) <- squares geniLexSemantics -- return emptyLE { iword = [lemma]- , ifamname = family + , ifamname = family , iparams = pars , iinterface = sortFlist interface , iequations = equations , ifilters = filters , isemantics = sem , isempols = pols }- where + where paramsParser :: Parser ([GeniVal], Flist) paramsParser = do pars <- many geniValue <?> "some parameters" interface <- option [] $ do symbol "!" many geniAttVal return (pars, interface)++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} -\section{Trees}+\section{Tree schemata} -\subsection{Macros}+The tree schemata file (for historical reasons, this is also called the macros+file) contains a set of unlexicalised trees organised into families. Such+``macros'' consist of a -A macro library is basically a list of trees.+\begin{enumerate}+\item a family name and (optionally) a macro name+\item a list of parameters+\item ''initial'' or ''auxiliary''+\item a tree.+\end{enumerate} +\subsection{Trees}++\jargon{Trees} are recursively defined structure of form \verb!node{tree*}!+For example, in the table below, the structure on the left should produce the+tree on the right:++\begin{SaveVerbatim}{KoweyTmp}+n1{+ n2+ n3{+ n4+ n5+ }+ n6+}+\end{SaveVerbatim}+\begin{tabular}{ll}+\BUseVerbatim{KoweyTmp} & \includegraphics[scale=0.50]{images/tree-format-example.png} \\+\end{tabular}++\subsection{Nodes}++\jargon{Nodes} consist of+\begin{enumerate}+\item a name+\item a type (optional)+\item either a lexeme, or top and bottom feature structures. Here are examples of the five possible kinds of nodes:+\end{enumerate}++\noindent+Here are some examples of nodes+\begin{verbatim}+ n1 [cat:n idx:?I]![cat:n idx:?I] % basic+ n3 type:subst [cat:n idx:?Y]![cat:n idx:?Y] % subst+ n4 type:foot [cat:n idx:?Y]![cat:n idx:?Y] % foot+ n5 type:lex "de" % coanchor+ n2 anchor % anchor+ n5 aconstr:noadj % node with a null-adjunction constraint (other than subst or foot)+\end{verbatim}++\subsection{Example}++\begin{verbatim}+adj:post(?I) auxiliary+n0[cat:n idx:?I det:_]![cat:n idx:?I det:minus ]+{+ n1 type:foot [cat:n idx:?I det:minus]![cat:n idx:?I det:minus]+ n2[cat:a]![]+ {+ n3 anchor+ }+}++adj:pre(?I) auxiliary+n0[cat:n idx:?I det:_ qu:_]![cat:n idx:?I det:minus ]+{+ n1[cat:a]![]+ {+ n2 anchor+ }+ n3 type:foot [cat:n idx:?I det:minus]![cat:n idx:?I det:minus]+}++vArity2:n0vn1(?E ?X ?Y) initial+n1[cat:p]![]+{+ n2 type:subst [cat:n idx:?X det:plus]![cat:n idx:?X]+ n3[cat:v idx:?E]![]+ {+ n4 anchor+ }+ n5 type:subst [cat:n idx:?Y det:plus]![cat:n idx:?Y]+}+\end{verbatim}++\subsection{EBNF}++\begin{SaveVerbatim}{KoweyTmp}+<macros> ::= <macro>*+<macro> ::= <family-name> <opt-macro-name> <parameters> <tree-type> <tree>+ <opt-semantics> <opt-trace>++<parameters> ::= "(" <value>* <opt-interface> ")"+<interface> ::= ! <attribute-value-pair>*+<macro-name> ::= <identifier>+<tree-type> ::= "initial" | "auxiliary"+<trace> ::= <keyword-trace> "[" <identifier>* "]"++<tree> ::= <node> | <node> "{" <tree>* "}"+<node> ::= <node-name> <opt-node-type> <node-payload>+<node-name> ::= <identifier>+<node-type> ::= <keyword-type> <core-node-type> | "anchor"+<core-node-type> ::= "foot" | "subst" | "lex"+<node-payload> ::= <string-literal> | <feature-structure> "!" <feature-structure>+\end{SaveVerbatim}+\begin{center}+\fbox{\BUseVerbatim{KoweyTmp}}+\end{center}+ \begin{code} geniMacros :: Parser [MTtree] geniMacros = tillEof $ many geniTreeDef@@ -308,26 +667,13 @@ 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 + (pars,iface) <- geniParams theTtype <- (initType <|> auxType) theTree <- geniTree -- sanity checks?@@ -358,39 +704,9 @@ , 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 = +geniTree = do node <- geniNode kids <- option [] (braces $ many geniTree) <?> "child nodes"@@ -404,14 +720,14 @@ return (T.Node node kids) geniNode :: Parser GNode-geniNode = - do name <- identifier +geniNode =+ do name <- identifier nodeType <- option "" ( (keyword TYPE >> typeParser) <|> reserved ANCHOR) lex_ <- if nodeType == LEX- then (sepBy (stringLiteral<|>identifier) (symbol "|") <?> "some lexemes") - else return [] + then (sepBy (stringLiteral<|>identifier) (symbol "|") <?> "some lexemes")+ else return [] constr <- case nodeType of "" -> adjConstraintParser ANCHOR -> adjConstraintParser@@ -436,27 +752,21 @@ , ganchor = (nodeType == ANCHOR) , gaconstr = constr , gorigin = "" }- where + where typeParser = choice $ map (try.symbol) [ ANCHOR, FOOT, SUBST, LEX ] adjConstraintParser = option False $ reserved ACONSTR_NOADJ >> return True topbotParser =- do top <- geniFeats <?> "top features" + 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}+-- | This makes it possible to read anchored trees, which may be+-- useful for debugging purposes.+--+-- 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. geniTagElems :: Parser [TagElem] geniTagElems = tillEof $ setTidnums `fmap` many geniTagElem @@ -475,43 +785,25 @@ , 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) } +-- | '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.+--+-- TODO: deprecate+geniParams :: Parser ([GeniVal], Flist)+geniParams = parens $ do+ pars <- many geniValue <?> "some parameters"+ interface <- option [] $ do { symbol "!"; many geniAttVal }+ return (pars, interface) \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}.+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}.+(\textbf{TODO}: describe format) \begin{code} geniMorphInfo :: Parser [(String,Flist)]@@ -524,53 +816,51 @@ 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.+% ======================================================================+% everything else+% ====================================================================== \begin{code}-geniMorphLexicon :: Parser [MorphLexEntry]-geniMorphLexicon = tillEof $ many morphLexiconEntry+-- ----------------------------------------------------------------------+-- polarities+-- ----------------------------------------------------------------------+geniPolarities :: Parser (Map.Map PolarityKey Interval)+geniPolarities = tillEof $ toMap `fmap` many pol+ where+ toMap = Map.fromListWith (!+!)+ pol = do p <- geniPolarity+ i <- identifier+ return (PolarityKey i,ival p) -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}+-- | 'geniPolarity' associates a numerical value to a polarity symbol,+-- that is, '+' or '-'.+geniPolarity :: Parser Int+geniPolarity = option 0 (plus <|> minus)+ where+ plus = do { char '+'; return 1 }+ minus = do { char '-'; return (-1) } -\section{Generic GenI stuff}+-- ----------------------------------------------------------------------+-- keyword+-- ---------------------------------------------------------------------- -\subsection{Lexer}+{-# INLINE keyword #-}+keyword :: String -> Parser String+keyword k =+ do let helper = try $ do { reserved k; colon; return k }+ helper <?> k ++ ":" -Some preliminaries about GenI formats in general - comments start with -\verb!%! There is also the option of using \verb'/* */' for embedded-comments. +{-# INLINE keywordSemantics #-}+keywordSemantics :: Parser String+keywordSemantics = keyword SEMANTICS -\begin{code}+-- ----------------------------------------------------------------------+-- language def helpers+-- ----------------------------------------------------------------------+ 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 @@ -598,166 +888,22 @@ 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}+-- ----------------------------------------------------------------------+-- parsec helpers+-- ---------------------------------------------------------------------- -\begin{code} tillEof :: Parser a -> Parser a tillEof p = do whiteSpace r <- p eof return r-\end{code} -+-- stolen from Parsec and adapted to use UTF-8 input+parseFromFile :: Parser a -> SourceName -> IO (Either ParseError a)+parseFromFile p fname+ = do{ input <- UTF8.readFile fname+ ; return (parse p fname input)+ }+\end{code}
src/NLP/GenI/GeniShow.lhs view
@@ -51,7 +51,7 @@ ( TagElem, idname, tsemantics, ttree, tinterface, ttype, ttreename, )-import NLP.GenI.Btypes (GeniVal(GConst), AvPair, Ptype(..),+import NLP.GenI.Btypes (GeniVal(GConst), AvPair(..), Ptype(..), Ttree(params, pidname, pfamily, pinterface, ptype, tree, psemantics, ptrace), GNode(..), GType(..), SemInput, Pred,@@ -70,7 +70,7 @@ geniShow _ = "" instance GeniShow AvPair where- geniShow (a,v) = a ++ ":" ++ geniShow v+ geniShow (AvPair a v) = a ++ ":" ++ geniShow v instance GeniShow GeniVal where geniShow (GConst xs) = concat $ intersperse "|" xs
+ src/NLP/GenI/GeniVal.hs view
@@ -0,0 +1,309 @@+-- GenI surface realiser+-- Copyright (C) 2009 Eric Kow+-- Copyright (C) 2005 Carlos Areces+--+-- 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.++{-# LANGUAGE OverlappingInstances, FlexibleInstances, DeriveDataTypeable #-}+module NLP.GenI.GeniVal where++-- import Debug.Trace -- for test stuff+import Control.Arrow (first, (***))+import Control.Monad (liftM)+import Data.List+import Data.Maybe (fromMaybe, isJust)+import Data.Generics (Data)+import Data.Typeable (Typeable)+import qualified Data.Map as Map++import Test.HUnit+import Test.QuickCheck hiding (collect)+import Test.Framework+import Test.Framework.Providers.HUnit+import Test.Framework.Providers.QuickCheck++import Data.Generics.PlateDirect++import Control.Parallel.Strategies++import NLP.GenI.General (geniBug)++data GeniVal = GConst [String] -- ^ atomic disjunction - constant x | y | z+ | GVar String -- ^ variable+ | GAnon -- ^ anonymous+ deriving (Eq,Ord, Data, Typeable)++instance Uniplate GeniVal where+ uniplate x = (Zero, \Zero -> x)++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)++-- ----------------------------------------------------------------------+-- Helper types+-- ----------------------------------------------------------------------++type Subst = Map.Map String GeniVal++-- ----------------------------------------------------------------------+-- Unification+-- ----------------------------------------------------------------------++-- | 'unify' performs unification on two lists of 'GeniVal'. If+-- unification succeeds, it returns @Just (r,s)@ where \verb!r! is+-- the result of unification and \verb!s! is a list of substitutions that+-- this unification results in.+unify :: Monad m => [GeniVal] -> [GeniVal] -> m ([GeniVal], Subst)+unify l1 l2 = repropagate `liftM` helper l1 l2+ where+ repropagate (xs, sub) = (replace sub xs, sub)+ helper [] l2 = return (l2, Map.empty)+ helper l1 [] = return (l1, Map.empty)+ helper (h1:t1) (h2:t2) =+ case unifyOne h1 h2 of+ Failure -> fail $ "unification failure between " ++ show h1 ++ " and " ++ show h2+ SuccessRep v g -> prepend `liftM` helper t1b t2b+ where+ s = (v,g)+ t1b = replaceOne s t1+ t2b = replaceOne s t2+ prepend = (g:) *** prependToSubst s+ SuccessSans g -> first (g:) `liftM` helper t1 t2++-- | 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 =+ case Map.lookup v sm of+ Just v2 -> geniBug . unlines $+ [ "prependToSubst: GenI just tried to prepend the substitution"+ , " " ++ show (GVar v) ++ " -> " ++ show gr+ , "to one where where "+ , " " ++ show (GVar v) ++ " -> " ++ show v2+ , "is slated to occur afterwards."+ , ""+ , "This could mean that either"+ , " (a) the core unification algorithm is broken"+ , " (b) we failed to propagate a value somewhere or"+ , " (c) we are attempting unification without renaming."+ ]+ Nothing -> Map.insert v gr2 sm+ where gr2 = fromMaybe gr $ Map.lookup r sm+prependToSubst (v, gr) sm = Map.insert v gr sm++-- ----------------------------------------------------------------------+-- Core unification+-- TODO: would continuation passing style make this more efficient?+-- ----------------------------------------------------------------------++data UnificationResult = SuccessSans GeniVal+ | SuccessRep String GeniVal+ | Failure++-- | See source code for details+unifyOne :: GeniVal -> GeniVal -> UnificationResult+unifyOne g GAnon = SuccessSans g+unifyOne GAnon g = SuccessSans g+unifyOne (GVar v) gc@(GConst _) = SuccessRep v gc+unifyOne gc@(GConst _) (GVar v) = SuccessRep v gc+unifyOne (GConst v1) (GConst v2) =+ case v1 `intersect` v2 of+ [] -> Failure+ newV -> SuccessSans (GConst newV)+unifyOne x1@(GVar v1) (GVar v2) =+ if v1 == v2+ then SuccessSans x1+ else SuccessRep v2 x1++-- ----------------------------------------------------------------------+-- Variable substitution+-- ----------------------------------------------------------------------++replace :: DescendGeniVal a => Subst -> a -> a+replace m | Map.null m = id+replace m = descendGeniVal (replaceMapG m)++replaceOne :: DescendGeniVal a => (String, GeniVal) -> a -> a+replaceOne = descendGeniVal . replaceOneG++-- | 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 :: DescendGeniVal a => [(String,GeniVal)] -> a -> a+replaceList = replace . foldl' update Map.empty+ where+ update m (s1,s2) = Map.insert s1 s2 $ Map.map (replaceOne (s1,s2)) m++replaceMapG :: Subst -> GeniVal -> GeniVal+replaceMapG m v@(GVar v_) = {-# SCC "replaceMapG" #-} Map.findWithDefault v v_ m+replaceMapG _ v = {-# SCC "replaceMapG" #-} v++replaceOneG :: (String, GeniVal) -> GeniVal -> GeniVal+replaceOneG (s1, s2) (GVar v_) | v_ == s1 = {-# SCC "replaceOneG" #-} s2+replaceOneG _ v = {-# SCC "replaceOneG" #-} v++-- ----------------------------------------------------------------------+-- Performance+-- ----------------------------------------------------------------------++instance NFData GeniVal+ where rnf (GConst x1) = rnf x1+ rnf (GVar x1) = rnf x1+ rnf (GAnon) = ()++-- ----------------------------------------------------------------------+-- Genericity+-- ----------------------------------------------------------------------++class DescendGeniVal a where+ descendGeniVal :: (GeniVal -> GeniVal) -> a -> a++instance DescendGeniVal GeniVal where+ descendGeniVal f = f++instance (Functor f, DescendGeniVal a) => DescendGeniVal (f a) where+ descendGeniVal = fmap . descendGeniVal++-- ----------------------------------------------------------------------+-- Testing+-- ----------------------------------------------------------------------++testSuite = testGroup "unification"+ [ testProperty "self" prop_unify_sym+ , testProperty "anonymous variables" prop_unify_anon+ , testProperty "symmetry" prop_unify_sym+ , testBackPropagation+ ]++-- | Unifying something with itself should always succeed+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++-- | Unifying something with only anonymous variables should succeed and return+-- the same result.+prop_unify_anon :: [GeniVal] -> Bool+prop_unify_anon x =+ case unify x y of+ Nothing -> False+ Just unf -> fst unf == x+ where --+ y = replicate (length x) GAnon++-- | 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.+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++testBackPropagation =+ testGroup "back propagation"+ [ testCase "unify left/right" $ assertEqual "" expected $ unify left right+ , testCase "unify right/left" $ assertEqual "" expected $ unify right left+ ]+ where+ n = 3+ cx = GConst ["X"]+ leftStrs = map show [1..n]+ left = map GVar leftStrs+ right = drop 1 left ++ [cx]+ expected = Just (expectedResult, expectedSubst)+ expectedResult = replicate n cx+ expectedSubst = Map.fromList $ zip leftStrs (repeat cx)++-- ----------------------------------------------------------------------+-- Testing+-- ----------------------------------------------------------------------++-- 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,+ fmap (GVar . fromGTestString2) arbitrary,+ fmap (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
src/NLP/GenI/GraphvizShow.lhs view
@@ -29,17 +29,19 @@ \ignore{ \begin{code} import Data.List(intersperse,nub)+import Data.List.Split (wordsBy)+import Data.Maybe(listToMaybe) import NLP.GenI.Tags ( TagElem, TagDerivation, idname, tsemantics, ttree,+ DerivationStep(..), )-import NLP.GenI.Btypes (GeniVal(GConst), AvPair,+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)@@ -128,7 +130,7 @@ in stub ++ maybeShow_ " " extra instance GraphvizShowString () AvPair where- graphvizShow () (a,v) = a ++ ":" ++ (graphvizShow_ v)+ graphvizShow () (AvPair a v) = a ++ ":" ++ graphvizShow_ v instance GraphvizShowString () GeniVal where graphvizShow () (GConst x) = concat $ intersperse " ! " x@@ -160,9 +162,7 @@ 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)+ listToMaybe [ v | AvPair a v <- getFeat gn, a == attr ] -- | Apply fn to s if s is not null maybeShow :: ([a] -> String) -> [a] -> String@@ -188,15 +188,15 @@ ++ (concatMap showHistNode histNodes) ++ (concatMap graphvizShowDerivation' deriv) where showHistNode n = gvNode (gvDerivationLab n) (label n) []- label n = case wordsBy ':' n of+ 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+ histNodes = reverse $ nub $ concatMap (\ (DerivationStep _ c p _) -> [c,p]) deriv \end{code} \begin{code}-graphvizShowDerivation' :: (Char, String, (String, String)) -> String-graphvizShowDerivation' (substadj, child, (parent,_)) =+graphvizShowDerivation' :: DerivationStep -> String+graphvizShowDerivation' (DerivationStep substadj child parent _) = gvEdge (gvDerivationLab parent) (gvDerivationLab child) "" p where p = if substadj == 'a' then [("style","dashed")] else [] \end{code}
src/NLP/GenI/Gui.lhs view
@@ -30,8 +30,8 @@ import qualified Data.Map as Map import Data.IORef-import Data.List (isPrefixOf, nub, delete, (\\), find)-import Data.Maybe (isJust)+import Data.List (isPrefixOf, nub, delete, findIndex)+import Data.Maybe ( fromMaybe ) import System.Directory import System.Exit (exitWith, ExitCode(ExitSuccess)) @@ -39,10 +39,12 @@ import qualified NLP.GenI.BuilderGui as BG import NLP.GenI.Geni ( ProgState(..), ProgStateRef, combine, initGeni- , loadEverything, loadTestSuite, loadTargetSemStr)+ , lemmaSentenceString, GeniResult(..), prettyResult+ , loadEverything, loadTestSuite, loadTargetSemStr+ , showRealisations ) 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.Btypes (TestCase(..), showFlist,)+import NLP.GenI.Tags (idname, tpolarities, TagElem) import NLP.GenI.GeniShow (geniShow) import NLP.GenI.Configuration ( Params(..), Instruction, hasOpt@@ -50,29 +52,25 @@ , parseFlagWithParsec -- , ExtraPolaritiesFlg(..)- , IgnoreSemanticsFlg(..) , LexiconFlg(..) , MacrosFlg(..)- , MaxTreesFlg(..) , MorphCmdFlg(..) , MorphInfoFlg(..) , OptimisationsFlg(..)+ , RankingConstraintsFlg(..) , RootFeatureFlg(..) , TestSuiteFlg(..)- , TestCaseFlg(..) , TestInstructionsFlg(..) , ViewCmdFlg(..) -- , Optimisation(..) , BuilderType(..), mainBuilderTypes )-import NLP.GenI.GeniParsers+import NLP.GenI.GeniParsers hiding ( choice, label, tab ) import NLP.GenI.GuiHelper import NLP.GenI.Polarity import NLP.GenI.Simple.SimpleGui-import NLP.GenI.CkyEarley.CkyGui--+import NLP.GenI.Statistics (Statistics, showFinalStats) \end{code} } @@ -84,12 +82,15 @@ \end{code} When you first start GenI, you will see this screen:-[[FIXME:screenshot wanted]] +\begin{center}+\includegraphics[width=0.47\textwidth]{hcar/GenI-main-screenshot.jpg}+\end{center}+ 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}.+automatically loaded up), select some optimisations and run the realiser. You+can also opt to run the debugger instead of the realiser (section+\ref{sec:gui:debugger}). \begin{code} mainGui :: ProgStateRef -> IO ()@@ -125,14 +126,12 @@ -- ----------------------------------------------------------------- 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 "" ]+ , text := "" ] testCaseChoice <- choice f [ selection := 0 , enabled := hasSem ] -- Box and Frame for files loaded @@ -153,8 +152,6 @@ [ 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@@ -165,6 +162,10 @@ [ text := "Sem constraints" , tooltip := "Use any sem constraints the user provides" ]+ earlyNaChk <- optCheckBox EarlyNa pstRef f+ [ text := "NA detection"+ , tooltip := "Early detection of nodes that cannot be adjoined to"+ ] iafChk <- optCheckBox Iaf pstRef f [ text := "Idx acc filter" , tooltip := "Only available in CKY/Earley for now"@@ -173,10 +174,6 @@ [ 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" @@ -209,7 +206,7 @@ [ dynamic $ row 5 [ label "Extra: ", widget extrapolText ] ] ] , dynamic $ widget useSemConstraintsChk , dynamic $ widget semfilterChk - , dynamic $ widget rootfilterChk+ , dynamic $ widget earlyNaChk , dynamic $ widget iafChk ] set f [layout := column 5 [ gramsemBox@@ -225,20 +222,14 @@ , 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}+-- 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. toggleAlgo :: (Selection a, Items a String) => ProgStateRef -> a -> IO () toggleAlgo pstRef box = do asel <- get box selection@@ -282,17 +273,14 @@ 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+-- --------------------------------------------------------------------+-- Loading files+-- -------------------------------------------------------------------- -\begin{code}+-- | 'readConfig' is used to update the graphical interface after+-- you run the 'configGui'.+-- It is also called when you first launch the GUI 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 =@@ -326,21 +314,15 @@ 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))+ do (loadTestSuite pstRef >> return ())+ `catch` \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+ suiteCases = case filter (\c -> tcName c `elem` cs) suite of+ [] -> suite+ res -> res+ where cs = fromMaybe [] mcs suiteCaseNames = map tcName suiteCases -- we number the cases for easy identification, putting -- a star to highlight the selected test case (if available)@@ -349,11 +331,12 @@ ++ (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+ caseSel <- if null theCase+ then return 0+ else case findIndex (== theCase) suiteCaseNames of+ Nothing -> do errorDialog f "" ("No such test case: " ++ theCase)+ return 0+ Just i -> return i ---------------------------------------------------- -- handler for selecting a test case ----------------------------------------------------@@ -361,11 +344,11 @@ geniShow $ toSemInputString si str let onTestCaseChoice = do csel <- get caseChoice selection- if (boundsCheck csel suite)+ 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" + show csel ++ " of " ++ show tcaseLabels ++ "\n" ---------------------------------------------------- set caseChoice [ items := tcaseLabels , selection := caseSel@@ -377,18 +360,19 @@ \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 configuration GUI aims to a provide a graphical substitute for the command+line switches. Note you cannot yet select optimisations and test cases from+this window; use the main window instead. Note also that changes to GenI tend+to start from the command line switches and only percolate to the GUI when time+permits. For full control of GenI, see \verb!geni --help!. -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{center}+\emph{TODO: screenshot wanted}+\end{center} \begin{code}+-- | 'configGui' @pstRef loadFn@ provides the configuration GUI. The continuation+-- @loadFn@ tells us what to do when the user closes this window. configGui :: ProgStateRef -> IO () -> IO () configGui pstRef loadFn = do pst <- readIORef pstRef@@ -404,11 +388,9 @@ ++ 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}+ -- -----------------------------------------------------------------+ -- basic options tab+ -- ----------------------------------------------------------------- pbas <- panel nb [] -- files loaded (labels) macrosFileLabel <- staticText pbas [ text := getListFlagP MacrosFlg config ]@@ -440,12 +422,9 @@ -- 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}+ -- -----------------------------------------------------------------+ -- advanced options tab+ -- ----------------------------------------------------------------- padv <- panel nb [] -- XMG tools viewCmdTxt <- entry padv @@ -472,30 +451,16 @@ , 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}+ $ [ layXMG, layPolarities, layMorph]+ -- -----------------------------------------------------------------+ -- browse button action+ --+ -- 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.+ -- ----------------------------------------------------------------- -- helper functions curDir <- getCurrentDirectory let curDir2 = curDir ++ "/"@@ -520,12 +485,9 @@ 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}+ -- -----------------------------------------------------------------+ -- config GUI layout+ -- ----------------------------------------------------------------- let parsePol = parseFlagWithParsec "polarities" geniPolarities parseRF = parseFlagWithParsec "root features" geniFeats onLoad @@ -541,19 +503,14 @@ 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)+ . (maybeSetStr TestInstructionsFlg [(tsVal,Nothing)]) . (maybeSet RootFeatureFlg parseRF rootCatVal) . (maybeSet ExtraPolaritiesFlg parsePol extraPolVal) . (maybeSetStr ViewCmdFlg viewVal)@@ -579,13 +536,12 @@ \end{code} % ---------------------------------------------------------------------\section{Running the generator}+\section{Generation} % -------------------------------------------------------------------- -\paragraph{doGenerate} parses the target semantics, then calls the-generator and displays the result in a results gui (below).- \begin{code}+-- | 'doGenerate' parses the target semantics, then calls the generator and+-- displays the result in a results gui (below). doGenerate :: Textual b => Window a -> ProgStateRef -> b -> Bool -> Bool -> IO () doGenerate f pstRef sembox useDebugger pauseOnLex = do loadEverything pstRef@@ -599,8 +555,6 @@ 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@@ -613,10 +567,14 @@ 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.+When surface realisation is complete, we display a results window with various+tabs for intermediary results in lexical selection, derived trees, derivation+trees and generation statistics. +\begin{center}+\emph{TODO: screenshot wanted}+\end{center}+ \begin{code} resultsGui :: BG.BuilderGui -> ProgStateRef -> IO () resultsGui builderGui pstRef =@@ -630,28 +588,58 @@ 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+ -- summary tab+ let sentences = concatMap grRealisations results+ summTab <- statsGui nb sentences stats+ -- ranking tab+ pst <- readIORef pstRef+ let useRanking = hasFlagP RankingConstraintsFlg (pa pst)+ rankTab <- messageGui nb . unlines . map (prettyResult pst) $ results+ -- tabs+ let myTabs = [ tab "summary" summTab+ , tab "realisations" resTab+ ] +++ (if useRanking then [ tab "ranking" rankTab ] else []) -- 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 ] ]+ set f [ layout := container p $ column 0 [ tabs nb myTabs ] , clientSize := sz 700 600 ] return ()++-- | 'statsGui' displays the generation statistics and provides a+-- handy button for saving results to a text file.+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} -\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}.+\label{sec:gui:debugger}+Instead of going directly to the results window, you could instead use the+interactive debugger which GenI provides. The debugger shows a separate tab+for each phase in surfuce realisation (lexical selection, filtering, building).+The building phase has a parameterisable GUI, which means that if you wanted to+develop a new surface realisation algorithm for GenI, you could also extend the+debugger GUI to go with it. +\begin{center}+\includegraphics[width=0.47\textwidth]{hcar/GenI-debugger-screenshot.jpg}+\end{center}+ \begin{code}+-- | We provide here a universal debugging interface, which makes use of some+-- parameterisable bits as defined in the BuilderGui module. debugGui :: BG.BuilderGui -> ProgStateRef -> Bool -> IO () debugGui builderGui pstRef pauseOnLex = do pst <- readIORef pstRef@@ -665,9 +653,7 @@ nb <- notebook p [] -- generation step 1 initStuff <- initGeni pstRef- let (tsem,_,_) = B.inSemInput initStuff- (cand,_) = unzip $ B.inCands initStuff- lexonly = B.inLex initStuff+ let (cand,_) = unzip $ B.inCands initStuff -- continuation for candidate selection tab let step2 newCands = do -- generation step 2.A (run polarity stuff)@@ -689,15 +675,9 @@ , 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+ then pauseOnLexGui pst nb cand step2+ else candidateGui pst nb cand -- basic tabs let basicTabs = [ tab "lexical selection" canPnl ] --@@ -706,21 +686,19 @@ -- 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.+The GenI tree browser displays all the TAG trees in the grammar grouped+according to the semantics with which they are associated. \begin{code}+-- 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. treeBrowserGui :: ProgStateRef -> IO () treeBrowserGui pstRef = do pst <- readIORef pstRef
+ src/NLP/GenI/GuiHelper.hs view
@@ -0,0 +1,750 @@+-- 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.++{-# LANGUAGE FlexibleContexts #-}+module NLP.GenI.GuiHelper where++import Graphics.UI.WX+-- import Graphics.UI.WXCore++import Control.Arrow ( (&&&), (***) )+import qualified Control.Monad as Monad +import Control.Monad.State.Strict ( execStateT, runState )+import qualified Data.Map as Map++import Data.IORef+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.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(..) )+import NLP.GenI.GeniParsers ( geniTagElems )+import NLP.GenI.General+ (geniBug, boundsCheck, dropTillIncluding, ePutStrLn)+import NLP.GenI.Btypes+ ( showAv, showPred, showLexeme, )+import NLP.GenI.PolarityTypes ( PolarityKey(..) )+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 ()++-- ----------------------------------------------------------------------+-- Types+-- ----------------------------------------------------------------------++data GraphvizStatus = GvError String+ | GvNoSuchItem Int+ | GvCached+ | GvCreated FilePath+ deriving Show++-- ----------------------------------------------------------------------+-- Lexically selected items+-- ----------------------------------------------------------------------++-- | 'candidateGui' displays the lexically selected items, grouped by the+-- semantics they subsume.+candidateGui :: ProgState+ -> Window a+ -> [TagElem]+ -> GvIO () Bool (Maybe TagElem)+candidateGui pst f xs = do+ p <- panel f [] + (tb,gvRef,updater) <- tagViewerGui pst p "lexically selected item" "candidates"+ $ sectionsBySem xs+ let polFeats = "Polarity attributes detected: " ++ (unwords.detectPolFeatures) xs+ warning = unlines $ filter (not.null) (polFeats : warnings pst)+ -- 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++-- ----------------------------------------------------------------------+-- Polarity Automata+-- ----------------------------------------------------------------------++-- | A browser to see the automata constructed during the polarity optimisation+-- step.+polarityGui :: (Window a) -> [(PolarityKey,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 (PolarityKey fv,a1,a2) = [ fv ++ stats a1, fv ++ " pruned" ++ stats a2]+ finalAutLab = ( final, "final" ++ stats final )+ autslabs = concatBoth (map (aut2 &&& autLabel) xs) ++ [ finalAutLab ]+ gvRef <- newGvRef () () "automata"+ setGvDrawables gvRef autslabs+ graphvizGui f "polarity" gvRef++concatBoth :: [ ([a],[b]) ] -> [ (a,b) ]+concatBoth = uncurry zip . (concat *** concat) . unzip -- is there a simpler way?++-- ----------------------------------------------------------------------+-- Helpers+-- ----------------------------------------------------------------------+ +-- | 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]++-- | '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.+toSentence :: TagElem -> String+toSentence = unwords . map squishLeaf . tagLeaves++squishLeaf :: (a,([String], b)) -> String+squishLeaf = showLexeme.fst.snd++-- ----------------------------------------------------------------------+-- TAG viewer+-- ----------------------------------------------------------------------++-- | Variant of 'graphvizGui' with a toggle to view feature structures+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 [] + gvRef <- newGvRef () False tip+ setGvDrawables gvRef itNlab+ (lay,ref,onUpdate) <- 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+ onUpdate+ 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,onUpdate)++-- ----------------------------------------------------------------------+-- XMG Metagrammar stuff.+-- See <http://sourcesup.cru.fr/xmg/>+-- ----------------------------------------------------------------------++-- | Calls Yannick Parmentier's handy visualisation tool ViewTAG.+viewTagWidgets :: (GraphvizShow Bool t, TagItem t, XMGDerivation t)+ => Window a -> GraphvizGuiRef st (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 ()++-- --------------------------------------------------------------------+-- Graphical debugger (helper functions)+-- --------------------------------------------------------------------++-- | 'pauseOnLexGui' allows the user to see lexical selection only and either+-- dump it to file or read replace it by the contents of some other file+pauseOnLexGui :: ProgState -> (Window a) -> [TagElem]+ -> ([TagElem] -> IO ()) -- ^ continuation+ -> GvIO () Bool (Maybe TagElem)+pauseOnLexGui pst f xs job = do+ p <- panel f []+ candV <- varCreate xs+ (tb, ref, updater) <- candidateGui pst p xs+ -- 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+ setGvDrawables 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)++type DebuggerItemBar st flg itm+ = Panel () -- ^ parent panel+ -> GraphvizGuiRef st (Maybe itm) flg -- ^ gv ref to use+ -> GvUpdater -- ^ onUpdate+ -> IO (Layout, GvUpdater)++-- | A generic graphical debugger widget for GenI, including+--+-- * item viewer which allows the user to select one of the items in the+-- builder state.+--+-- * 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.+--+-- * A dashboard which lets the user do things like ``go ahead 6 steps''.+-- +-- 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 st 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+ p <- panel f [] + -- ---------------------------------------------------------+ -- item viewer: select and display an item+ -- ---------------------------------------------------------+ gvRef <- newGvRef initS gvInitial "debugger session"+ setGvDrawables gvRef (stateToGv initS)+ (layItemViewer,_,onUpdateMain) <- graphvizGui p cachedir gvRef+ -- ----------------------------------------------------------+ -- item bar: controls for how an individual item is displayed+ -- ----------------------------------------------------------+ (layItemBar,onUpdateItemBar) <- itemBar p gvRef onUpdateMain+ -- ------------------------------------------- + -- dashboard: controls for the debugger itself + -- ------------------------------------------- + let onUpdate = onUpdateMain >> onUpdateItemBar+ 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 = unwords [ "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]+ modifyIORef gvRef $ \g -> g { gvcore = s2 }+ setGvDrawables gvRef (stateToGv s2)+ setGvSel gvRef 1+ onUpdate+ updateStatsTxt stats2+ set nextBt [ on command :~ (\_ -> showNext (s2,stats2) ) ]+ let showLast = + do -- redo generation from scratch+ let (s2, stats2) = runState (execStateT allSteps initS) initStats + setGvDrawables gvRef (stateToGv s2)+ onUpdate+ updateStatsTxt stats2+ let showReset = + do set nextBt [ on command := showNext (initS, initStats) ]+ updateStatsTxt initStats + setGvDrawables gvRef (stateToGv initS)+ setGvSel gvRef 1+ onUpdate+ -- 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 ] ++-- --------------------------------------------------------------------+-- Graphviz GUI+-- --------------------------------------------------------------------+++data GraphvizOrder = GvoParams | GvoItems | GvoSel + deriving Eq++data GraphvizGuiSt st a b =+ GvSt { gvcore :: st,+ 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 st a b -> IO ()),+ gvsel :: Int,+ gvorders :: [GraphvizOrder] }++-- | This provides a mechanism for communicating with the GUI. The basic idea:+--+-- 1. you create a GvRef with newGvRef+--+-- 2. you call 'graphvizGui' and get back an updater function+--+-- 3. whenever you want to modify something, you use setGvWhatever and call+-- the updater function+--+-- 4. if you want to react to the selection being changed, you should set+-- gvhandler+type GraphvizGuiRef st a b = IORef (GraphvizGuiSt st a b)++newGvRef :: st -> b -> String -> IO (GraphvizGuiRef st a b)+newGvRef initSt p t =+ let st = GvSt { gvcore = initSt,+ gvparams = p,+ gvitems = Map.empty,+ gvlabels = [], + gvhandler = Nothing,+ gvtip = t,+ gvsel = 0,+ gvorders = [] }+ in newIORef st++setGvSel :: GraphvizGuiRef st a b -> Int -> IO ()+setGvSel gvref s =+ do let fn x = x { gvsel = s,+ gvorders = GvoSel : (gvorders x) }+ modifyIORef gvref fn + +setGvParams :: GraphvizGuiRef st a b -> b -> IO ()+setGvParams gvref c =+ do let fn x = x { gvparams = c,+ gvorders = GvoParams : (gvorders x) }+ modifyIORef gvref fn ++modifyGvParams :: GraphvizGuiRef st a b -> (b -> b) -> IO ()+modifyGvParams gvref fn =+ do gvSt <- readIORef gvref+ setGvParams gvref (fn $ gvparams gvSt)++setGvDrawables :: GraphvizGuiRef st a b -> [(a,String)] -> IO ()+setGvDrawables gvref itlb =+ do let (it,lb) = unzip itlb+ fn x = x { gvitems = Map.fromList $ zip [0..] it+ , gvlabels = lb+ , gvorders = GvoItems : (gvorders x)+ }+ modifyIORef gvref fn ++-- | 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 st (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 :: GraphvizGuiRef st a b -> Maybe (GraphvizGuiSt st 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 :: GraphvizGuiRef st a b -> (GraphvizGuiSt st 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++type GvIO st f d = IO (Layout, GraphvizGuiRef st d f, GvUpdater)+type GvUpdater = IO ()++-- |'graphvizGui' @f glab cachedir gvRef@ is 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.+--+-- This returns a layout (wxhaskell container) and a function that you're+-- expected to call whever something changes that would require the GUI to+-- refresh itself (for example, you create a new chart 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.+-- * @glab@ - (gui labels) a tuple of strings (tooltip, next button text)+-- * @cachedir@ - the cache subdirectory. We intialise this by creating a cache+-- directory for images which will be generated from the results+-- * @gvRef@ - see above+graphvizGui :: (GraphvizShow f d) => (Window a) -> String -> GraphvizGuiRef st d f -> GvIO st f d+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 ]+ ------------------------------------------------+ -- create an updater function+ ------------------------------------------------+ let withoutSelector job =+ bracket ( swap rchoice (on select) (return ()) )+ ( \fn -> set rchoice [ on select := fn ] )+ ( const job )+ -- the selector calls onUpdate which calls the selector+ -- indirectly by setting the selection+ let onUpdate = withoutSelector $ do+ gvSt <- readIORef gvRef+ let orders = gvorders gvSt+ initCacheDir cachedir+ Monad.when (GvoItems `elem` orders) $+ set rchoice [ items := gvlabels gvSt ]+ Monad.when (GvoSel `elem` orders) $+ set rchoice [ selection := gvsel gvSt ]+ modifyIORef gvRef (\x -> x { gvorders = []})+ createAndOpenImage cachedir p gvRef openFn+ ------------------------------------------------+ -- 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+ onUpdate+ ------------------------------------------------+ set rchoice [ on select := selectAndShow ]+ -- call the updater function for the first time+ setGvSel gvRef 1+ onUpdate + -- 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, onUpdate)++-- ---------------------------------------------------------------------- +-- Bitmap stuff+-- ---------------------------------------------------------------------- ++-- | Bitmap with a scrollbar+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)++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 []++-- | '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.+createAndOpenImage :: (GraphvizShow f b) => + FilePath -> Window a -> GraphvizGuiRef st b f -> OpenImageFn -> IO ()+createAndOpenImage cachedir f gvref openFn = do + let errormsg g = "The file " ++ g ++ " was not created!\n"+ ++ "Is graphviz installed?"+ gvStatus <- createImage cachedir f gvref+ case gvStatus of+ GvCreated graphic ->+ do exists <- doesFileExist graphic+ if exists+ then openFn graphic+ else errorDialog f "" (errormsg graphic)+ GvError err -> errorDialog f "" err+ GvNoSuchItem _ -> return ()+ GvCached -> return ()++-- | Creates a graphical visualisation for anything which can be displayed+-- by graphviz.+createImage :: (GraphvizShow f b)+ => FilePath -- ^ cache directory+ -> Window a -- ^ parent window+ -> GraphvizGuiRef st b f -- ^ stuff to display+ -> IO GraphvizStatus+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 . GvCreated $ graphicFile+ handler err = do errorDialog f "Error calling graphviz" (show err) + return . GvError . show $ err+ exists <- doesFileExist graphicFile+ -- we only call graphviz if the image is not in the cache+ if exists+ then return (GvCreated graphicFile)+ else case Map.lookup sel drawables of+ Nothing -> return . GvNoSuchItem $ sel+ Just it -> create it `catch` handler++-- | Directory to dump image files in so that we can avoid regenerating them.+-- If the directory already exists, we can just delete all the files in it.+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++-- ----------------------------------------------------------------------+-- Miscellaneous+-- ----------------------------------------------------------------------++-- | 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 ]) ++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"
− src/NLP/GenI/GuiHelper.lhs
@@ -1,860 +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.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
@@ -19,26 +19,39 @@ \label{cha:Morphology} This module handles mostly everything to do with morphology in Geni.-There are two basic tasks: morphological input and output. +There are two basic tasks: morphological input and output. GenI farms out morphology to whatever third party program you-specify in the configuration file.+specify on the command line. Note that a simple and stupid+``sillymorph'' realiser is provided either in the GenI repository+or on hackage. \begin{code}-module NLP.GenI.Morphology where+module NLP.GenI.Morphology+ (+ MorphFn+ -- re-export+ , LemmaPlus(..), LemmaPlusSentence+ -- * Morphological predicates+ , readMorph, stripMorphSem, attachMorph, setMorphAnchor+ -- * Morphological realisation+ , inflectSentencesUsingCmd, sansMorph+ ) where \end{code} \ignore{ \begin{code}-import Data.Maybe (isNothing, isJust)-import Data.List (intersperse)+import Data.Maybe (isNothing) import Data.Tree import qualified Data.Map as Map import System.IO import System.Process+import Text.JSON+import Text.JSON.Pretty import NLP.GenI.Btypes import NLP.GenI.General import NLP.GenI.Tags+import NLP.GenI.Builder \end{code} } @@ -46,7 +59,7 @@ type MorphFn = Pred -> Maybe Flist \end{code} -\section{Input}+\section{Morphological input} Morphological input means attaching morphological features on trees. The user specifies morphological input through the input semantics. Our job@@ -125,93 +138,95 @@ in (head.fst) $ listRepNode fn filt [t] \end{code} -\section{Output}+\section{Morphological realisation} -Output (\jargon{morphological generation}) refers to the actual process+\jargon{Morphological realisation} 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:+The morphological software must accept a JSON list of \jargon{lemma sentences}+where each lemma sentence is itself a list of objects containing a lemma and+a feature structure. \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]+[+ [{"lemma": "le", "lemma-features": "[num:sg gen:f]"},+ {"lemma": "fille", "lemma-features": "[num:sg]"},+ {"lemma": "detester", "lemma-features": "[num:sg tense:past]"},+ {"lemma": "le", "lemma-features": "[num:pl gen:m]"},+ {"lemma": "garcon", "lemma-features": "[num:pl]"}+ ],++ [{"lemma": "ce", "lemma-features": "[]"},+ {"lemma": "etre", "lemma-features": "[]"},+ {"lemma": "le", "lemma-features": "[]"},+ {"lemma": "garcon", "lemma-features": "[]"},+ {"lemma": "que", "lemma-features": "[]"},+ {"lemma": "le", "lemma-features": "[num:sg gen:f]"},+ {"lemma": "fille", "lemma-features": "[num:sg]"},+ {"lemma": "detester", "lemma-features": "[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:+NB: I recommend using a JSON library instead of parsing and writing this by+hand. +The morphological realiser may return more than one output per sentence.+Indeed, we expect a JSON-formatted list (a) of lists (b), where each (b)+provides a number of candidate morphological realisations for a sentence in+(a). The list (a) must have the same length as the input because each item in+(a) is expected to correspond to a sentence from the 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+[+ ["la fille detestait les garcons"],++ ["c'est le garcon que la fille detestait"+ ,"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.+If your morphological software does not do this, you could wrap it with a+simple 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 ]+sansMorph :: LemmaPlusSentence -> [String]+sansMorph = singleton . unwords . map lem+ where+ lem (LemmaPlus l _) = l -- | 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 :: String -> [LemmaPlusSentence] -> IO [(LemmaPlusSentence,[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+ do -- run the inflector+ (toP, fromP, _, _) <- runInteractiveCommand morphcmd+ hPutStrLn toP . render . pp_value . showJSON $ sentences 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+ mResults <- (resultToEither . decode) `fmap` hGetContents fromP+ case mResults of+ Left err -> fallback $ "Could not parse morphological generator output: " ++ err+ Right res -> do let lenResults = length res+ lenSentences = length sentences+ if lenResults == lenSentences+ then return $ zip sentences res+ else fallback $ "Morphological generator returned "+ ++ show lenResults ++ " results for "+ ++ show lenSentences ++ " inputs"+ `catch` \e -> fallback $ "Error calling morphological generator:\n" ++ show e+ where+ fallback err =+ do ePutStrLn err+ return $ map (\x -> (x, sansMorph x)) sentences singleton :: a -> [a] singleton x = [x]
+ src/NLP/GenI/OptimalityTheory.lhs view
@@ -0,0 +1,390 @@+% 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.++\chapter{Ranking output}+\label{cha:ranking}++\begin{code}+module NLP.GenI.OptimalityTheory+ ( -- * Input+ OtConstraint(..), OtRanking,+ -- * Output+ GetTraces, OtResult, OtViolation, RankedOtConstraint(..),+ rankResults, otWarnings,+ -- * Display+ prettyViolations,prettyRank+ )+ where++import Control.Applicative ( (<$>), (<*>) )+import Control.Arrow ( first )+import Data.Function (on)+import Data.Char ( isSpace )+import Data.List (nub, partition, sort, sortBy, groupBy, intersperse, (\\), unfoldr )+import Text.JSON++import NLP.GenI.Btypes ( Macros, ptrace )+import qualified NLP.GenI.Builder as B+\end{code}++If your tree schemata are annotated with traces (TODO link to traces and+metagrammars), you can re-use them as a basis for ranking the output produced+by GenI. The basic idea is to supply a list of either positive, negative or+negative conjunction constraints.++For users familiar with Haskell, the constraints are described with the+following type:+\begin{includecodeinmanual}+\begin{code}+data OtConstraint = PositiveC String -- ^ the trace must appear+ | NegativeC String -- ^ the trace must NOT appear+ | NegativeConjC [String] -- ^ these traces must not appear AT THE SAME TIME+ deriving (Show, Eq)+\end{code}+\end{includecodeinmanual}++Roughly speaking the more highly ranked the constraint, the greater the impact+of a violation of that constraint will be. See section+\ref{sec:ranking-procedure} for more details on the ranking procedure.++\begin{code}+data RankedOtConstraint = RankedOtConstraint Int OtConstraint+ deriving (Show, Eq)++instance Ord RankedOtConstraint where+ compare (RankedOtConstraint r1 _) (RankedOtConstraint r2 _) = compare r1 r2++-- | Same as 'RankedOtConstraint' with the sorting inverted+newtype RankedOtConstraint2 = RankedOtConstraint2 RankedOtConstraint deriving Eq++instance Ord RankedOtConstraint2 where+ compare (RankedOtConstraint2 x) (RankedOtConstraint2 y) = compare y x+++type OtRanking = [[OtConstraint]]++data OtViolation = OtViolation { otLexName :: String -- ^ empty for global+ , otConstraintViolated :: RankedOtConstraint }+ deriving (Show, Eq, Ord)++data LexItem = LexItem+ { lLexname :: String+ , lTraces :: [String]+ } deriving (Ord, Eq, Show)++type GetTraces = String -> [String]+type OtResult x = (Int,x,[OtViolation])+\end{code}++\section{Input format}++Constraints are expressed in JSON as a list of \jargon{ranking levels}. A+ranking level is a list of constraints that should be assigned the same rank.+In lieu of a formal description, we provide an example below:+\small{NB: Either the JSON format or the JSON parser used by GenI is strict+enough to refuse initial whitespace in this file.}++\begin{verbatim}+[+ [{"neg-constraint": "dian0Vn1dePassive"},+ {"pos-constraint": "CanonicalSubject"}],++ [{"neg-conj-constraint": ["InvertedNominalSubject",+ "CanonicalSententialObjectFinite"]}],++ [{"neg-conj-constraint": ["InvertedNominalSubject",+ "UnboundedCleft"]},+ {"neg-constraint": "CleftSubject"}]+]+\end{verbatim}++This example constraints file has three ranking levels. These levels contain+following constraints:++\begin{enumerate}+\item A negative constraint saying that \verb!dian0Vn1dePassive! should+ not appear, and a positive one saying that \verb!CanonicalSubject!+ \emph{should} appear. These constraints appear together only because+ the author of the example thinks they should have the same rank,+ not because there is neccesarily any inherent relationship between+ them.+\item A single negative conjunction constraint saying that+ \verb!InvertedNominalSubject! and+ \verb!CanonicalSententialObjectFinite!+ should not appear together.+\item A negative conjunction constraint saying tat+ \verb!InvertedNominalSubject! and \verb!UnboundedCleft! should not+ appear together; and also a negative constraints saying that+ \verb!CleftSubject! should not appear. As with the first ranking+ level, there is no relationship between these two constraints. We+ just put them on the same level to give them the same rank+\end{enumerate}++\begin{code}+instance JSON OtConstraint where+ readJSON j =+ do jv <- fromJSObject `fmap` readJSON j+ case lookup "pos-constraint" jv of+ Just v -> PositiveC `fmap` readJSON v+ Nothing -> case lookup "neg-constraint" jv of+ Just v -> NegativeC `fmap` readJSON v+ Nothing -> case lookup "neg-conj-constraint" jv of+ Just v -> NegativeConjC `fmap` readJSONs v+ Nothing -> fail $ "Could not read OtConstraint"+ showJSON (PositiveC c) =+ JSObject . toJSObject $ [ ("pos-constraint", showJSON c ) ]+ showJSON (NegativeC c) =+ JSObject . toJSObject $ [ ("neg-constraint", showJSON c ) ]+ showJSON (NegativeConjC cs) =+ JSObject . toJSObject $ [ ("neg-conj-constraint", showJSONs cs ) ]+\end{code}++\begin{code}+-- ---------------------------------------------------------------------+-- top level stuff+-- ---------------------------------------------------------------------+otWarnings :: Macros -> OtRanking -> [OtViolation] -> [String]+otWarnings gram ranking blocks =+ addWarning neTraces neTracesW+ . addWarning nvConstraints nvConstraintsW+ $ []+ where+ addWarning xs w = if null xs then id else (w xs :)+ neTracesW xs = "these traces never appear in the grammar: " ++ unwords xs+ neTraces = nonExistentTraces gram ranking+ nvConstraintsW xs = "these constraints are never violated: " ++ unwords (map prettyConstraint xs)+ nvConstraints = neverViolated blocks ranking++rankResults :: GetTraces -> (a -> B.Derivation) -> OtRanking -> [a] -> [OtResult a]+rankResults getTraces getDerivation r = squish . sortResults . map addViolations+ where+ addViolations x = (x, getViolations x)+ getViolations = violations (concatRank r) . lexTraces getTraces . getDerivation+ squish = concat . zipWith applyRank [1..]+ applyRank i = map (\(x,vs) -> (i,x,vs))+\end{code}+++\begin{code}+-- ---------------------------------------------------------------------+-- detecting violations+-- ---------------------------------------------------------------------++violations :: [RankedOtConstraint] -> [LexItem] -> [OtViolation]+violations cs ls = posVs ls ++ negVs ls+ where+ negVs = concatMap (\l -> negViolations cs (lLexname l) (lTraces l))+ posVs = posViolations cs . concatMap lTraces++-- | A positive constraint is violated when a trace is NOT present+posViolations :: [RankedOtConstraint] -> [String] -> [OtViolation]+posViolations cs ss =+ [ OtViolation "" c | c@(RankedOtConstraint _ (PositiveC s)) <- cs, not (s `elem` ss) ]++-- | A negative constraint is violated when a trace is present+--+-- Note that we will not notice if a constraint is violated more+-- than once. If you want to count multiple violations, you'll+-- either need to partition the input strings and map this function+-- on each sublist or rewrite this code.+negViolations :: [RankedOtConstraint]+ -> String -- ^ lex name+ -> [String] -- ^ traces+ -> [OtViolation]+negViolations cs l ss =+ [ OtViolation l c | c@(RankedOtConstraint _ (NegativeC s)) <- cs, s `elem` ss ] +++ [ OtViolation l c | c@(RankedOtConstraint _ (NegativeConjC xs)) <- cs, all (`elem` ss) xs ]+\end{code}++\section{Ranking procedure}+\label{sec:ranking-procedure}++Generation results are sorted according to their highest-ranking constraint+violation (moving on to the next-highest ranking violation and so forth in case+of a tie). The best result appears first.++\begin{code}+-- | Violations sorted so that the highest ranking constraint+-- (smallest number) goes first+sortedViolations :: (a, [OtViolation]) -> [RankedOtConstraint2]+sortedViolations = map (RankedOtConstraint2 . otConstraintViolated) . sort . snd++-- | Sort the sentences so that the ones with the *lowest*+-- ranking violations (biggest number) go first.+-- Note that we return in groups for the sake of ties.+sortResults :: [(a, [OtViolation])] -> [[(a, [OtViolation])]]+sortResults = sortAndGroupByDecoration compare sortedViolations++lexTraces :: GetTraces -> B.Derivation -> [LexItem]+lexTraces getTraces = map (toLexItem getTraces) . B.lexicalSelection++toLexItem :: GetTraces -> String -> LexItem+toLexItem getTraces t =+ LexItem { lLexname = t+ , lTraces = getTraces t }+\end{code}++\section{Output format}++Constraint violations can be outputted as JSON objects as the following example+shows++\begin{verbatim}+ { "lex-item": "discuter:n0Vn1pn2:Tn0Vn1pn2-5830:22",+ , "rank": 6,+ , "violation": {"neg-constraint": "passiveVerbMorphology"}+ }+\end{verbatim}++Positive constraint violations are not associated with any lexical items+so the lex-item field is omitted for them.++\begin{code}+-- ---------------------------------------------------------------------+-- output+-- ---------------------------------------------------------------------+instance JSON RankedOtConstraint where+ readJSON j =+ do jo <- fromJSObject `fmap` readJSON j+ let field x = maybe (fail $ "Could not find: " ++ x) readJSON+ $ lookup x jo+ RankedOtConstraint <$> field "rank"+ <*> field "violation"+ showJSON = JSObject . toJSObject . rankedOtConstraintToPairs++rankedOtConstraintToPairs :: RankedOtConstraint -> [ (String, JSValue) ]+rankedOtConstraintToPairs (RankedOtConstraint r c) =+ [ ("rank", showJSON r), ("violation", showJSON c) ]++instance JSON OtViolation where+ readJSON j =+ do jo <- fromJSObject `fmap` readJSON j+ case lookup "lex-item" jo of+ Nothing -> OtViolation "" <$> readJSON j+ Just l -> OtViolation <$> readJSON l+ <*> readJSON j++ showJSON ov = JSObject . toJSObject $ pairs+ where+ pairs = case otLexName ov of+ "" -> basicPairs+ l -> ("lex-item", showJSON l) : basicPairs+ basicPairs = rankedOtConstraintToPairs (otConstraintViolated ov)++-- ---------------------------------------------------------------------+-- pretty printing+-- ---------------------------------------------------------------------++-- TODO: Return as a pretty Doc+prettyViolations :: GetTraces -> Bool -> [OtViolation] -> String+prettyViolations getTraces noisy vs =+ unlines $ (if null posVs then [] else [ indented 1 75 . showPosVs $ posVs ])+ ++ map showLexVs negBuckets+ where+ (posVs, negVs) = partition (null . otLexName) vs+ negBuckets = buckets otLexName negVs+ --+ showPosVs = unwords . map (prettyRankedConstraint . otConstraintViolated)+ showLexVs (l,lvs) =+ let itmName = "(" ++ l ++ ")"+ constraints = map otConstraintViolated lvs+ allTraces = indented 4 75 . unwords . getTraces $ l+ in (indented 2 75 . unwords $ itmName : map prettyRankedConstraint constraints)+ ++ (if noisy then "\n" ++ allTraces else "")++prettyRankedConstraint :: RankedOtConstraint -> String+prettyRankedConstraint (RankedOtConstraint r c) = prettyConstraint c ++ " " ++ prettyRank r++prettyConstraint :: OtConstraint -> String+prettyConstraint (PositiveC str) = '+' : str+prettyConstraint (NegativeC str) = '*' : str+prettyConstraint (NegativeConjC strs) = "*(" ++ (concat $ intersperse " & " strs) ++ ")"++prettyRank :: Int -> String+prettyRank r = "(r" ++ show r ++ ")"++-- ---------------------------------------------------------------------+-- detecting impossible constraints or other potential errors+-- ---------------------------------------------------------------------++neverViolated :: [OtViolation] -> [[OtConstraint]] -> [OtConstraint]+neverViolated vs ranking = concat ranking \\ cs_used+ where+ cs_used = nub . map (noRank . otConstraintViolated) $ vs++nonExistentTraces :: Macros -> [[OtConstraint]] -> [String]+nonExistentTraces ms vs = r_traces \\ m_traces+ where+ m_traces = nub $ concatMap ptrace ms+ r_traces = nub $ concatMap cTraces $ concat vs++cTraces :: OtConstraint -> [String]+cTraces (PositiveC c) = [c]+cTraces (NegativeConjC cs) = cs+cTraces (NegativeC c) = [c]++-- ----------------------------------------------------------------------+-- helpers+-- ----------------------------------------------------------------------++concatRank :: [[OtConstraint]] -> [RankedOtConstraint]+concatRank = concat . zipWith rank [1..]+ where+ rank x ys = map (RankedOtConstraint x) ys++noRank :: RankedOtConstraint -> OtConstraint+noRank (RankedOtConstraint _ c) = c++-- ----------------------------------------------------------------------+-- odds and ends+-- ----------------------------------------------------------------------++buckets :: Ord b => (a -> b) -> [a] -> [ (b,[a]) ]+buckets f = map (first head . unzip)+ . groupBy ((==) `on` fst)+ . sortBy (compare `on` fst)+ . map (\x -> (f x, x))++-- | Results are grouped so that ties can be noticed+sortAndGroupByDecoration :: Eq b => (b -> b -> Ordering) -> (a -> b) -> [a] -> [[a]]+sortAndGroupByDecoration cmp f = map (map snd)+ . groupBy ((==) `on` fst)+ . sortBy (cmp `on` fst)+ . map (\x -> (f x, x))++indented :: Int -> Int -> String -> String+indented x len = concat . intersperse "\n" . map (\s -> spaces x ++ s) . unfoldr f+ where+ f "" = Nothing+ f str = Just $ splitAtBefore len str++spaces :: Int -> String+spaces n = replicate n ' '++splitAtBefore :: Int -> String -> (String, String)+splitAtBefore len xs+ | length xs < len = (xs, "")+ | any isSpace xs = (begin, trim $ drop (length begin) xs)+ | otherwise = (xs, "")+ where+ begin+ | length upToSpace > len = upToSpace+ | otherwise = reverse . trim . dropWhile isNotSpace . reverse . take len $ xs+ upToSpace = takeWhile isNotSpace xs+ isNotSpace = not . isSpace+ trim = drop 1+\end{code}
src/NLP/GenI/Polarity.lhs view
@@ -1,5 +1,5 @@ % GenI surface realiser-% Copyright (C) 2005 Carlos Areces and Eric Kow+% 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@@ -84,23 +84,25 @@ \begin{code} import Data.Bits+import qualified Data.Set as Set import qualified Data.Map as Map import Data.List-import Data.Maybe (isNothing)+import Data.Maybe (isNothing, isJust) 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(..),+import NLP.GenI.Btypes(Pred, SemInput, Sem, Flist, AvPair(..), showAv,+ GeniVal(..), fromGConst, isConst,+ replace, emptyPred, Ptype(Initial), showFlist, showSem, sortSem,- root, gup, gdown, gtype, GType(Subs),- SemPols, unifyFeat, rootUpd)+ GNode, root, gup, gdown, gtype, GType(Subs),+ SemPols, unify, unifyFeat, rootUpd) import NLP.GenI.General( BitVector, isEmptyIntersect, thd3, Interval, ival, (!+!), showInterval)+import NLP.GenI.PolarityTypes import NLP.GenI.Tags(TagElem(..), TagItem(..), setTidnums) \end{code} @@ -109,28 +111,36 @@ \begin{code} -- | intermediate auts, seed aut, final aut, potentially modified sem type PolResult = ([AutDebug], PolAut, PolAut, Sem)-type AutDebug = (String, PolAut, PolAut)+type AutDebug = (PolarityKey, 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 =+-- | Constructs a polarity automaton. For debugging purposes, it returns+-- all the intermediate automata produced by the construction algorithm.+buildAutomaton :: Set.Set PolarityAttr -- ^ polarities to detect+ -> Flist -- ^ root features to compensate for+ -> PolMap -- ^ explicit extra polarities+ -> SemInput -- ^ input semantics+ -> [TagElem] -- ^ lexical selection+ -> PolResult+buildAutomaton polarityAttrs rootFeat extrapol (tsem,tres,_) candRaw = let -- root categories, index constraints, and external polarities- rcatPol :: Map.Map String Interval- rcatPol = Map.fromList $ polarise (-1) $ getval __cat__ rootFeat+ rcatPol :: Map.Map PolarityKey Interval+ rcatPol = Map.fromList . pdJusts+ $ map (\v -> detectPolarityForAttr (-1) (pAttr v) rootFeat)+ $ Set.toList polarityAttrs+ pAttr p@(SimplePolarityAttr _) = spkAtt p+ pAttr p@(RestrictedPolarityAttr _ _) = rpkAtt p+ -- 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+ r = detect . tinterface $ t candRest = map constrain candRaw inputRest = declareIdxConstraints tres -- polarity detection - cand = detectPols candRest+ cand = detectPols polarityAttrs candRest -- building the automaton in makePolAut cand tsem allExtraPols \end{code}@@ -171,7 +181,7 @@ makePolAut :: [TagElem] -> Sem -> PolMap -> PolResult makePolAut candsRaw tsemRaw extraPol = let -- polarity items- ksCands = concatMap ((Map.keys).tpolarities) cands+ ksCands = concatMap (Map.keys . tpolarities) cands ksExtra = Map.keys extraPol ks = sortBy (flip compare) $ nub $ ksCands ++ ksExtra -- perform index counting@@ -186,7 +196,7 @@ 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+ res = foldr build [(PolarityKey "(seed)",seed,prune seed)] ks in (reverse res, seed, thd3 $ head res, tsem) \end{code} @@ -289,7 +299,7 @@ -- 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)+ next = nub newStates : states aut -- recursive step to the next literal in buildSeedAut' cands ls (i+1) (newAut { states = next }) @@ -340,7 +350,7 @@ automaton that you can use when there is no ``previous automaton''. \begin{code}-buildPolAut :: String -> Interval -> PolAut -> PolAut +buildPolAut :: PolarityKey -> Interval -> PolAut -> PolAut buildPolAut k initK skelAut = let concatPol p (PolSt pr b pol) = PolSt pr b (p:pol) newStart = concatPol initK $ startSt skelAut@@ -364,7 +374,7 @@ automaton. \begin{code}-buildPolAut' :: String -> PolTransFn -> PolAut -> PolAut+buildPolAut' :: PolarityKey -> PolTransFn -> PolAut -> PolAut -- for each literal... (this is implicit in the automaton state grouping) buildPolAut' fk skeleton aut = let -- previously created candidates @@ -372,14 +382,14 @@ -- 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)+ 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 :: PolarityKey -> 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@@ -398,7 +408,7 @@ newSt t skel2 = PolSt pr2 ex2 (po2:skelPo2) where PolSt pr2 ex2 skelPo2 = skel2 - po2 = po1 !+! (Map.findWithDefault (ival 0) fk pol)+ po2 = po1 !+! Map.findWithDefault (ival 0) fk pol pol = case t of Nothing -> Map.empty Just t2 -> tpolarities t2 in result @@ -680,20 +690,20 @@ 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+ where fn (i,c) = replicate (negate 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)+ 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 []+ 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+ extra = replicate (min (negate maxNeeded) (ct - cm)) i comparePron :: (PredLite,SemPols) -> [GeniVal] comparePron (lit,c1) = concat $ zipWith3 comparefn idxs c1 c2 where idxs = snd lit@@ -740,7 +750,7 @@ \begin{code} assignIndex :: GeniVal -> TagElem -> TagElem assignIndex i te =- let idxfs = [ (__idx__, i) ]+ let idxfs = [ AvPair __idx__ i ] oldt = ttree te oldr = root oldt tfup = gup oldr@@ -785,16 +795,16 @@ detectIdxConstraints :: Flist -> Flist -> PolMap detectIdxConstraints cs interface = let matches = intersect cs interface- matchStr = map showIdxConstraint matches+ matchStr = map idxConstraintKey matches in Map.fromList $ zip matchStr ((repeat.ival) 1) declareIdxConstraints :: Flist -> PolMap declareIdxConstraints = Map.fromList . (map declare) where- declare c = (showIdxConstraint c, minusone)+ declare c = (idxConstraintKey c, minusone) minusone = ival (-1) -showIdxConstraint :: AvPair -> String-showIdxConstraint = ('.' :) . showAv+idxConstraintKey :: AvPair -> PolarityKey+idxConstraintKey = PolarityKey . ('.' :) . showAv \end{code} \subsection{Automatic detection}@@ -817,7 +827,7 @@ sfeats = [ concat s | s <- map substTops tes, (not.null) s ] -- attrs :: Flist -> [String]- attrs avs = [ a | (a,v) <- avs, isConst v ]+ attrs avs = [ a | AvPair a v <- avs, isConst v ] theAttributes = map attrs $ rfeats ++ sfeats in if null tes then [] else foldr1 intersect theAttributes @@ -829,9 +839,9 @@ detectSansIdx :: [TagElem] -> [TagElem] detectSansIdx = let rfeats t = (gdown.root.ttree) t- feats t | ttype t == Initial = concat $ (rfeats t) : (substTops t)+ feats t | ttype t == Initial = concat $ rfeats t : substTops t feats t = concat $ substTops t- attrs avs = [ a | (a,v) <- avs, isConst v ]+ attrs avs = [ a | AvPair a v <- avs, isConst v ] hasIdx t = __idx__ `elem` (attrs.feats $ t) || (ttype t /= Initial && (null $ substTops t)) in filter (not.hasIdx) \end{code}@@ -859,32 +869,38 @@ time. It would be nice to have some kind of mutual exclusion working. \begin{code}-detectPols :: [TagElem] -> [TagElem]-detectPols = map detectPols'+detectPols :: Set.Set PolarityAttr -> [TagElem] -> [TagElem]+detectPols attrs = map (detectPolsH attrs) -detectPols' :: TagElem -> TagElem-detectPols' te =- let otherFeats = [] --, __idx__ ]- feats = __cat__ : otherFeats+detectPolsH :: Set.Set PolarityAttr -> TagElem -> TagElem+detectPolsH polarityAttrs te =+ let detectOrBust x1 x2 x3 x4 =+ case detectPolarity x1 x2 x3 x4 of+ PD_UserError e -> error $ e ++ " in " ++ tgIdName te -- ideally we'd propagate this+ PD_Nothing -> []+ PD_Just p -> p --- 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+ rup = gup . root .ttree $ te+ rdown = gdown . root . ttree $ te --- -- substs nodes only- commonPols :: [ (String,Interval) ]- commonPols = polarise (-1) substuff+ catAttr = SimplePolarityAttr "cat"+ rstuffLite = concatMap (\v -> detectOrBust 1 v rup rdown)+ $ Set.toList $ Set.delete catAttr polarityAttrs+ rstuff :: [(PolarityKey,Interval)]+ rstuff = if Set.member catAttr polarityAttrs+ then -- cat is considered global to the whole tree to be+ -- robust, we grab it from the top feature+ detectOrBust 1 catAttr rup rup ++ rstuffLite+ else rstuffLite+ substuff :: [(PolarityKey,Interval)]+ substuff = let tops = substTops te+ detect :: PolarityAttr -> [(PolarityKey,Interval)]+ detect v = concat $ zipWith (detectOrBust (-1) v) tops tops+ in concatMap detect $ Set.toList polarityAttrs -- substs and roots- pols :: [ (String,Interval) ] pols = case ttype te of- Initial -> commonPols ++ polarise 1 rstuff- _ -> commonPols+ Initial -> substuff ++ rstuff+ _ -> substuff -- oldfm = tpolarities te in te { tpolarities = foldr addPol oldfm pols }@@ -893,14 +909,45 @@ __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." +data PolarityDetectionResult = PD_UserError String+ | PD_Nothing+ | PD_Just [ (PolarityKey, Interval) ]++-- | Careful, this completely ignores any user errors+pdJusts :: [PolarityDetectionResult] -> [(PolarityKey,Interval)]+pdJusts = concatMap helper+ where helper (PD_Just x) = x+ helper _ = []++detectPolarity :: Int -- ^ polarity to assign+ -> PolarityAttr -- ^ attribute to look for+ -> Flist -- ^ feature structure to filter on+ -> Flist -- ^ feature structure to get value from+ -> PolarityDetectionResult+detectPolarity i (RestrictedPolarityAttr cat att) filterFl fl =+ case [ v | AvPair a v <- filterFl, a == __cat__ ] of+ [] -> PD_UserError $ "[polarities] No category " ++ cat ++ " in:" ++ showFlist filterFl+ [v] -> if isJust (unify [GConst [cat]] [v])+ then detectPolarityForAttr i att fl+ else PD_Nothing+ _ -> PD_UserError $ "[polarities] More than one category " ++ " in:" ++ showFlist filterFl+detectPolarity i (SimplePolarityAttr att) _ fl = detectPolarityForAttr i att fl++detectPolarityForAttr :: Int -- ^ polarity to assign+ -> String+ -> Flist+ -> PolarityDetectionResult+detectPolarityForAttr i att fl =+ case [ v | AvPair a v <- fl, a == att ] of+ [] -> PD_UserError $ "[polarities] No value for attribute: " ++ att ++ " in:" ++ showFlist fl+ [v] -> if isConst v+ then PD_Just $ case prefixWith att (fromGConst v) of+ [x] -> [ (PolarityKey x, ival i) ] -- singleton+ xs -> map (\x -> (PolarityKey x, toZero i)) xs -- interval if ambiguous+ else PD_UserError $ "[polarities] Non-constant value for attribute: " ++ att ++ " in:" ++ showFlist fl+ _ -> PD_UserError $ "[polarities] More than one value for attribute: " ++ att ++ " in:" ++ showFlist fl+ toZero :: Int -> Interval toZero x | x < 0 = (x, 0) | otherwise = (0, x)@@ -908,19 +955,11 @@ 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+substNodes :: TagElem -> [GNode]+substNodes t = [ gn | gn <- (flatten.ttree) t, gtype gn == Subs ] substTops :: TagElem -> [Flist]-substTops t = [ gup gn | gn <- (flatten.ttree) t, gtype gn == Subs ]+substTops = map gup . substNodes \end{code} \subsection{Chart sharing}@@ -1031,11 +1070,11 @@ \begin{code} type SemMap = Map.Map Pred [TagElem]-type PolMap = Map.Map String Interval +type PolMap = Map.Map PolarityKey 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 :: (PolarityKey,Interval) -> PolMap -> PolMap addPol (p,c) m = Map.insertWith (!+!) p c m -- | Ensures that all states and transitions in the polarity automaton@@ -1162,6 +1201,6 @@ -- The advantage is that it displays fewer quotation marks. showLitePm :: PolMap -> String showLitePm pm = - let showPair (f, pol) = showInterval pol ++ f + let showPair (f, pol) = showInterval pol ++ fromPolarityKey f in concat $ intersperse " " $ map showPair $ Map.toList pm \end{code}
+ src/NLP/GenI/PolarityTypes.hs view
@@ -0,0 +1,44 @@+-- 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.++module NLP.GenI.PolarityTypes where++import Data.List ( break )+import qualified Data.Set as Set+import Data.Generics ( Data )+import Data.Typeable ( Typeable )++newtype PolarityKey = PolarityKey { fromPolarityKey :: String } deriving (Show, Eq, Ord, Data, Typeable)++-- | 'PolarityAttr' is something you want to perform detect polarities on.+data PolarityAttr = SimplePolarityAttr { spkAtt :: String }+ -- | 'RestrictedPolarityKey' @c att@ is a polarity key in which we only pay+ -- attention to nodes that have the category @c@. This makes it possible+ -- to have polarities for a just a small subset of nodes+ | RestrictedPolarityAttr { _rpkCat :: String, rpkAtt :: String }+ deriving (Eq, Ord, Typeable)++readPolarityAttrs :: String -> Set.Set PolarityAttr+readPolarityAttrs = Set.fromList . map helper . words+ where+ helper s = case break (== '.') s of+ (a,"") -> SimplePolarityAttr a+ (c,a) -> RestrictedPolarityAttr c (drop 1 a)++instance Show PolarityAttr where+ show (SimplePolarityAttr a) = a+ show (RestrictedPolarityAttr c a) = c ++ "." ++ a
+ src/NLP/GenI/Regression.hs view
@@ -0,0 +1,83 @@+-- 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.++-- | Just regression testing of suites+-- This can be seen as regression testing of GenI+-- and also of grammars using GenI++module NLP.GenI.Regression (regressionGeni) where++import Control.Monad+import Data.IORef(readIORef, modifyIORef)+import Data.List(sort)+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+ ( fst3,+ )+import NLP.GenI.Geni+import NLP.GenI.Configuration+ ( Params+ , builderType , BuilderType(..)+ )+import qualified NLP.GenI.Builder as B+import NLP.GenI.Simple.SimpleBuilder++regressionGeni :: ProgStateRef -> IO ()+regressionGeni pstRef = do+ do pst <- readIORef pstRef+ loadEverything pstRef+ tests <- (mapM toTest) . tsuite $ pst+ runTestTT . (H.TestList) . concat $ tests+ return ()+ 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 (tcSem tc)+ let sentences = map lemmaSentenceString 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)++-- | 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 -> SemInput -> IO [GeniResult]+runOnSemInput pstRef semInput =+ do modifyIORef pstRef (\x -> x{ts = semInput})+ pst <- readIORef pstRef+ let config = pa pst+ go = case builderType config of+ NullBuilder -> helper B.nullBuilder+ SimpleBuilder -> helper simpleBuilder_2p+ SimpleOnePhaseBuilder -> helper simpleBuilder_1p+ sort `fmap` go+ where+ helper builder = fst3 `fmap` runGeni pstRef builder
src/NLP/GenI/Simple/SimpleBuilder.lhs view
@@ -24,8 +24,8 @@ item is a derived tree. \begin{code}-{-# OPTIONS_GHC -fglasgow-exts #-}--- {-# LANGUAGE LiberalTypeSynonyms #-}+{-# OPTIONS_GHC -fglasgow-exts -fno-warn-orphans #-}+{-# LANGUAGE LiberalTypeSynonyms, TemplateHaskell #-} module NLP.GenI.Simple.SimpleBuilder ( -- Types Agenda, AuxAgenda, Chart, SimpleStatus, SimpleState,@@ -33,15 +33,14 @@ -- From SimpleStatus simpleBuilder_1p, simpleBuilder_2p, simpleBuilder,- theAgenda, theAuxAgenda, theChart, theResults,+ theAgenda, theHoldingPen, theChart, theResults, initSimpleBuilder, addToAgenda, addToChart, genconfig,--#ifndef DISABLE_GUI SimpleGuiItem(..),- theTrash, unpackResult,-#endif+ theTrash, step,++ unpackResult, ) where \end{code}@@ -49,24 +48,28 @@ \ignore{ \begin{code}-import Control.Monad (when, liftM2)-import Control.Monad.State+import Control.Arrow ( second )+import Control.Monad (when, unless, liftM2)+import Control.Monad.State.Strict (get, put, modify, gets, runState, execStateT) -import Data.List- (partition, delete, foldl', unfoldr, sortBy)-import Data.Maybe (isJust, isNothing)+import Data.List (partition, delete, foldl')+import Data.Maybe (isJust, isNothing, mapMaybe) import Data.Ord (comparing) import Data.Bits import qualified Data.Map as Map import Data.Tree +import Data.Generics ( Data )+import Data.Generics.PlateDirect+ import NLP.GenI.Statistics (Statistics) import NLP.GenI.Automaton ( automatonPaths, NFA(..), addTrans ) import NLP.GenI.Btypes- ( Ptype(Initial,Auxiliar)- , Replacable(..), replaceOneAsMap+ ( Ptype(Initial)+ , GeniVal+ , replace, DescendGeniVal(..) , GNode(..), NodeName , root, foot , plugTree, spliceTree@@ -78,27 +81,28 @@ DispatchFilter, (>-->), condFilter, nullFilter, semToIafMap, IafAble(..), IafMap, fromUniConst, getIdx, recalculateAccesibility, iafBadSem, ts_iafFailure,+ LemmaPlus(..), ) import qualified NLP.GenI.Builder as B -import NLP.GenI.Tags (TagElem, TagSite(TagSite),+import NLP.GenI.Tags (TagElem, TagSite(..), tagLeaves, tidnum, ttree, ttype, tsemantics, detectSites,- TagDerivation,+ TagDerivation, DerivationStep(..), 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++import Data.List ( sortBy, unfoldr ) \end{code} } @@ -119,7 +123,7 @@ { 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.finished = \s -> (null.theAgenda) s && (not twophase || isAdjunctionPhase (step s)) , B.unpack = unpackResults.theResults , B.partial = unpackResults.partialResults }@@ -133,9 +137,15 @@ type Agenda = [SimpleItem] type AuxAgenda = [SimpleItem] type Chart = [SimpleItem]-#ifndef DISABLE_GUI type Trash = [SimpleItem]-#endif++data GenerationPhase = SubstitutionPhase+ | AdjunctionPhase+ deriving (Show)++isAdjunctionPhase :: GenerationPhase -> Bool+isAdjunctionPhase AdjunctionPhase = True+isAdjunctionPhase _ = False \end{code} \subsection{SimpleState and SimpleStatus}@@ -160,15 +170,13 @@ data SimpleStatus = S { theAgenda :: Agenda- , theAuxAgenda :: AuxAgenda+ , theHoldingPen :: AuxAgenda , theChart :: Chart-#ifndef DISABLE_GUI , theTrash :: Trash-#endif , theResults :: [SimpleItem] , theIafMap :: IafMap -- for index accessibility filtering , tsem :: BitVector- , step :: Ptype+ , step :: GenerationPhase , gencounter :: Integer , genconfig :: Params -- we keep a SemBitMap strictly to help display the semantics@@ -181,37 +189,38 @@ \begin{code} addToAgenda :: SimpleItem -> SimpleState ()-addToAgenda te = do- modify $ \s -> s{theAgenda = te:(theAgenda s) }+addToAgenda te =+ modify $ \s -> s{theAgenda = te : theAgenda s } updateAgenda :: Agenda -> SimpleState ()-updateAgenda a = do+updateAgenda a = 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+ let counter = gencounter s + 1 te2 = te { siId = counter } put s{gencounter = counter,- theAuxAgenda = te2:(theAuxAgenda s) }+ theHoldingPen = te2 : theHoldingPen s } addToChart :: SimpleItem -> SimpleState () addToChart te = do- modify $ \s -> s { theChart = te:(theChart s) }+ 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+ disableGui <- gets (hasFlagP DisableGuiFlg . genconfig)+ unless disableGui $+ modify $ \s -> s { theTrash = te2 : theTrash s }+ where+ te2 = modifyGuiStuff (\g -> g { siDiagnostic = err:siDiagnostic g }) te addToResults :: SimpleItem -> SimpleState ()-addToResults te = do- modify $ \s -> s { theResults = te : (theResults s) }+addToResults te =+ modify $ \s -> s { theResults = te : theResults s } \end{code} \subsection{SimpleItem}@@ -220,11 +229,11 @@ data SimpleItem = SimpleItem { siId :: ChartId --- , siSubstnodes :: ![TagSite]- , siAdjnodes :: ![TagSite]+ , siSubstnodes :: [TagSite]+ , siAdjnodes :: [TagSite] --- , siSemantics :: !BitVector- , siPolpaths :: !BitVector+ , siSemantics :: BitVector+ , siPolpaths :: BitVector -- for generation sans semantics -- , siAdjlist :: [(String,Integer)] -- (node name, auxiliary tree id) -- for index accesibility filtering (one-phase only)@@ -240,13 +249,24 @@ , 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+ } deriving (Show) -#ifndef DISABLE_GUI+instance Biplate SimpleItem GeniVal where+ biplate (SimpleItem x1 zss zas x2 x3 x4 x5 zls x6 zr zf zp x7 zg) =+ plate SimpleItem |- x1+ ||+ zss ||+ zas |- x2 |- x3 |- x4 |- x5+ ||+ zls |- x6+ |+ zr |+ zf ||+ zp |- x7+ |+ zg++instance Biplate (String, B.UninflectedDisjunction) GeniVal where+ biplate (s,d) = plate (,) |- s |+ d++instance DescendGeniVal (String, B.UninflectedDisjunction) where+ descendGeniVal m (s,d) = (s, descendGeniVal m d)+ -- | Things whose only use is within the graphical debugger data SimpleGuiItem = SimpleGuiItem { siHighlight :: [String] -- ^ nodes to highlight@@ -255,33 +275,35 @@ , siDiagnostic :: [String] , siFullSem :: Sem , siIdname :: String- } deriving Show+ } deriving (Show, Data, Typeable) +instance Biplate SimpleGuiItem GeniVal where+ biplate (SimpleGuiItem x1 zns x2 zsem x3) =+ plate SimpleGuiItem |- x1+ ||+ zns |- x2+ ||+ zsem |- x3++emptySimpleGuiItem :: SimpleGuiItem+emptySimpleGuiItem = SimpleGuiItem [] [] [] [] ""+ 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+instance DescendGeniVal SimpleItem where+ descendGeniVal s i = s `seq` i `seq`+ i { siSubstnodes = descendGeniVal s (siSubstnodes i)+ , siAdjnodes = descendGeniVal s (siAdjnodes i)+ , siLeaves = descendGeniVal s (siLeaves i)+ , siRoot = descendGeniVal s (siRoot i)+ , siFoot = descendGeniVal s (siFoot i)+ , siPendingTb = descendGeniVal s (siPendingTb i)+ , siGuiStuff = descendGeniVal s (siGuiStuff i) }- replaceOne = replaceOneAsMap -#ifndef DISABLE_GUI-instance Replacable SimpleGuiItem where- replaceMap s i = i { siNodes = replaceMap s (siNodes i) }- replaceOne = replaceOneAsMap-#endif+instance DescendGeniVal SimpleGuiItem where+ descendGeniVal s i = i { siNodes = descendGeniVal s (siNodes i) } \end{code} \begin{code}@@ -297,7 +319,7 @@ -- | True if both 'closed' and 'aux' are True closedAux :: SimpleItem -> Bool-closedAux x = (aux x) && (closed x)+closedAux x = aux x && closed x adjdone :: SimpleItem -> Bool adjdone = null.siAdjnodes@@ -314,7 +336,8 @@ -- | Creates an initial SimpleStatus. initSimpleBuilder :: Bool -> B.Input -> Params -> (SimpleStatus, Statistics) initSimpleBuilder twophase input config =- let cands = map (initSimpleItem bmap) $ B.inCands input+ let disableGui = hasFlagP DisableGuiFlg config+ cands = map (initSimpleItem disableGui bmap) $ B.inCands input (sem,_,_) = B.inSemInput input bmap = defineSemanticBits sem -- FIXME: I don't know if this matters for one-phase@@ -322,20 +345,18 @@ -- 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)+ else simpleDispatch_1p (hasOpt Iaf config) initialDp = dpTbFailure >--> simpleDp -- initS = S{ theAgenda = []- , theAuxAgenda = []+ , theHoldingPen = [] , theChart = []-#ifndef DISABLE_GUI , theTrash = []-#endif , theResults = [] , semBitMap = bmap , tsem = semToBitVector bmap sem , theIafMap = semToIafMap sem- , step = Initial+ , step = SubstitutionPhase , gencounter = toInteger $ length cands , genconfig = config } --@@ -343,8 +364,9 @@ runState (execStateT (mapM initialDp cands) initS) (B.initStats config) -initSimpleItem :: SemBitMap -> (TagElem, BitVector) -> SimpleItem-initSimpleItem bmap (teRaw,pp) =+initSimpleItem :: Bool -- ^ disable gui+ -> SemBitMap -> (TagElem, BitVector) -> SimpleItem+initSimpleItem disableGui bmap (teRaw,pp) = let (te,tlite) = renameNodesWithTidnum teRaw in case detectSites (ttree te) of (snodes,anodes,nullAdjNodes) -> setIaf $ SimpleItem@@ -358,7 +380,7 @@ , siInaccessible = [] -- for generation sans semantics -- , siAdjlist = []- , siLeaves = tagLeaves te+ , siLeaves = map (second (uncurry B.UninflectedDisjunction)) $ tagLeaves te , siDerived = tlite , siRoot = ncopy.root $ theTree , siFoot = if ttype te == Initial then Nothing@@ -367,14 +389,11 @@ -- note: see comment in initSimpleBuilder re: tb unification , siPendingTb = nullAdjNodes ---#ifndef DISABLE_GUI- , siGuiStuff = initSimpleGuiItem te-#endif+ , siGuiStuff = if disableGui then emptySimpleGuiItem else initSimpleGuiItem te } where setIaf i = i { siAccesible = iafNewAcc i } theTree = ttree te -#ifndef DISABLE_GUI initSimpleGuiItem :: TagElem -> SimpleGuiItem initSimpleGuiItem te = SimpleGuiItem { siHighlight = []@@ -382,7 +401,6 @@ , siDiagnostic = [] , siFullSem = tsemantics te , siIdname = idname te }-#endif renameNodesWithTidnum :: TagElem -> (TagElem, Tree NodeName) renameNodesWithTidnum te =@@ -408,7 +426,7 @@ generateStep_1p :: SimpleState () generateStep_1p = do isDone <- gets (null.theAgenda)- iaf <- gets (isIaf.genconfig)+ iaf <- gets (hasOpt Iaf .genconfig) let dispatch = mapM (simpleDispatch_1p iaf) if isDone then return ()@@ -447,46 +465,38 @@ 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'+ case curStep of+ SubstitutionPhase -> if nir then switchToAux else generateStep_2p_sub+ AdjunctionPhase -> if nir then return () else generateStep_2p_adj -generateStep_2p' :: SimpleState ()-generateStep_2p' =- do -- choose an item from the agenda+generateStep_2p_sub :: SimpleState ()+generateStep_2p_sub =+ do incrCounter num_iterations 1+ -- 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+ res <- applySubstitution given+ mapM_ simpleDispatch_2p res -- put the given into the chart untouched- if (curStep == Initial)- then addToChart given- else when (adjdone given) $ trashIt given+ addToChart given++generateStep_2p_adj :: SimpleState ()+generateStep_2p_adj =+ do incrCounter num_iterations 1+ -- choose an item from the agenda+ given <- selectGiven+ res <- liftM2 (++) (applyAdjunction2p given) (sansAdjunction2p given)+ mapM_ simpleDispatch_2p_adjphase res+ 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+ do disableGui <- gets (hasFlagP DisableGuiFlg . genconfig)+ unless disableGui $ do+ s <- get let bmap = semBitMap s itemSem = siSemantics item inputSem = tsem s@@ -494,7 +504,6 @@ 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.@@ -502,53 +511,50 @@ selectGiven = do agenda <- gets theAgenda case agenda of- [] -> geniBug "null agenda in selectGiven"+ [] -> 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+After the substitution and na-constraint phases are complete, we switch to the+final adjunction phase. We do this by deleting junk from the agenda+(particularly, trees with open substitution sites remaining), transfering trees+from the holding pen to the chart and setting the phase to AdjunctionPhase \begin{code} switchToAux :: SimpleState () switchToAux = do st <- get- let chart = theChart st+ let oldAuxTrees = theHoldingPen 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+ initialT = filter siInitial (theChart st)+ (compT1, incompT1) = partition (null.siSubstnodes) initialT --- auxAgenda = theAuxAgenda st- (compT2, incompT2) =- if semfiltered config- then semfilter (tsem st) auxAgenda compT1- else res1+ (auxTrees, compT2) =+ if hasOpt EarlyNa config+ then ( mapMaybe (detectNa oldAuxTrees) oldAuxTrees+ , mapMaybe (detectNa auxTrees) compT1 )+ else ( oldAuxTrees, compT1 ) --- compT = compT2- put st{ theAgenda = []- , theAuxAgenda = []- , theChart = auxAgenda- , step = Auxiliar}+ (compT3, incompT3) =+ if hasOpt SemFiltered config+ then semfilter (tsem st) auxTrees compT2+ else (compT2, [])+ --+ compT = compT3 -- the root cat filter by Claire- let switchFilter =- if rootcatfiltered config- then dpRootFeatFailure2 >--> dpToAgenda- else dpToAgenda- mapM switchFilter compT+ put st{ theAgenda = []+ , theHoldingPen = []+ , theChart = auxTrees+ , step = AdjunctionPhase }+ mapM_ simpleDispatch_2p_adjphase 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 ()+ mapM_ (\t -> addToTrash t ts_synIncomplete) incompT1+ mapM_ (\t -> addToTrash t "sem-filtered") incompT3 \end{code} \subsubsection{SemFilter Optimisation}@@ -645,17 +651,13 @@ 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))+ else nr : (siPendingTb item1 ++ siPendingTb item2) return $! replace subst $ combineSimpleItems [rn] item1g $ item2 { siSubstnodes = stail ++ (siSubstnodes item1) , siAdjnodes = adj2 ++ adj1@@ -737,20 +739,14 @@ -- 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}@@ -783,26 +779,15 @@ 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)+ (pSite : pTail) -> do -- 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+ (anr, anf, subst12) <- canAdjoin aItem pSite+ let r = siRoot aItem+ f <- siFoot aItem+ let an_name = tsName pSite -- the new adjunction nodes auxlite = delete r $ siAdjnodes aItem- newadjnodes = anr : (atail ++ auxlite)- ---#ifdef DISABLE_GUI- aItem2 = aItem-#else+ newadjnodes = anr : (pTail ++ auxlite) -- 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@@ -813,41 +798,68 @@ 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+ combineSimpleItems [tsName r, 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)+ , siDerived = spliceTree (tsName f) (siDerived aItem) an_name (siDerived pItem)+ , siDerivation = addToDerivation 'a' (aItem,tsOrigin r) (pItem,tsOrigin pSite,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)+ else anf : (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+ tbRes <- unifyFeat (tsUp anf) (tsDown anf) 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++-- Note that we do not propagate variable substitutions in the nodes we return+canAdjoin :: SimpleItem -> TagSite -> Maybe (TagSite, TagSite, Subst)+canAdjoin aItem pSite = do+ -- let's go!+ let r = siRoot aItem -- auxiliary tree, eh?+ f <- siFoot aItem -- should really be an error if fails+ (anr_up', subst1) <- unifyFeat (tsUp r) (tsUp pSite)+ (anf_down, subst2) <- unifyFeat (replace subst1 $ tsDown f) (replace subst1 $ tsDown pSite)+ let -- combined substitution list and success condition+ subst12 = mergeSubst subst1 subst2+ anr = replace subst12 $ r { tsUp = anr_up' }+ anf = replace subst12 $ pSite { tsDown = anf_down }+ return (anr, anf, subst12) \end{code} +\begin{code}+detectNa :: [SimpleItem] -- ^ aux trees+ -> SimpleItem -- ^ me+ -> Maybe SimpleItem+detectNa rawAux i = helper (siAdjnodes i) Map.empty []+ where+ compatAux = filterCompatible i rawAux+ helper [] s acc = Just $ replace s $ i { siAdjnodes = acc }+ helper (t:ts) s acc =+ let hasAdj = any isJust $ map (\a -> canAdjoin a t) compatAux+ in case (snd `fmap` unifyFeat (tsUp t) (tsDown t)) of+ Just s2 -> if hasAdj+ then helper ts s (t : acc)+ else helper (replace s2 ts) (mergeSubst s s2) acc+ Nothing -> if hasAdj+ then helper ts s (t : acc)+ else Nothing+\end{code}+ % -------------------------------------------------------------------- \subsection{Helper functions for operations} % --------------------------------------------------------------------@@ -866,35 +878,35 @@ -- * 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- ]+lookupChart given = gets (filterCompatible given . theChart) +filterCompatible :: SimpleItem -> [SimpleItem] -> [SimpleItem]+filterCompatible given chart =+ [ 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+ ]+ where+ gpaths = siPolpaths given+ gsem = siSemantics given+ -- | 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+ item2 { siSemantics = siSemantics item1 .|. siSemantics item2+ , siPolpaths = siPolpaths item1 .&. siPolpaths item2 , 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)+ item2 { siFullSem = sortSem $ siFullSem item1 ++ siFullSem item2+ , siNodes = siNodes item1 ++ siNodes item2+ , siDiagnostic = siDiagnostic item1 ++ siDiagnostic item2 , siHighlight = hi } @@ -902,7 +914,6 @@ 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}@@ -919,7 +930,7 @@ addToDerivation op (tc,tcOrigin) (tp,tpOrigin,tpSite) = let hp = siDerivation tp hc = siDerivation tc- newnode = (op, tcOrigin, (tpOrigin, tpSite))+ newnode = DerivationStep op tcOrigin tpOrigin tpSite in newnode:hp++hc \end{code} @@ -942,6 +953,11 @@ simpleDispatch (dpRootFeatFailure >--> dpToResults) (dpAux >--> dpToAgenda) +simpleDispatch_2p_adjphase :: SimpleDispatchFilter+simpleDispatch_2p_adjphase =+ simpleDispatch (dpRootFeatFailure >--> dpToResults)+ dpToAgenda+ simpleDispatch_1p :: Bool -> SimpleDispatchFilter simpleDispatch_1p iaf = simpleDispatch (dpRootFeatFailure >--> dpTbFailure >--> dpToResults)@@ -957,16 +973,12 @@ condFilter isResult resFilter nonResFilter item dpAux, dpToAgenda :: SimpleDispatchFilter-dpTbFailure, dpRootFeatFailure, dpRootFeatFailure2, dpToResults :: SimpleDispatchFilter+dpTbFailure, 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@@ -994,18 +1006,14 @@ -- | 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 =+dpRootFeatFailure :: SimpleDispatchFilter+dpRootFeatFailure 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+ dpToTrash (ts_rootFeatureMismatch rootFeat) item Just (_, s) -> return . Just $ replace s item \end{code}@@ -1027,7 +1035,7 @@ type TbEither = Either String Subst tbUnifyTree :: SimpleItem -> Bool tbUnifyTree item = {-# SCC "tbUnifyTree" #-}- case foldl tbUnifyNode (Right Map.empty) (siPendingTb item) of+ case foldl' tbUnifyNode (Right Map.empty) (siPendingTb item) of Left _ -> False Right _ -> True \end{code}@@ -1146,7 +1154,7 @@ listToSentenceAut :: [ B.UninflectedDisjunction ] -> B.SentenceAut listToSentenceAut nodes = let theStart = 0- theEnd = (length nodes) - 1+ theEnd = length nodes - 1 theStates = [theStart..theEnd] -- emptyAut = NFA@@ -1158,10 +1166,10 @@ -- 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 =+ helper (current, B.UninflectedDisjunction lemmas features) aut = foldl' addT aut lemmas where- addT a t = addTrans a current (Just (t, features)) next+ addT a t = addTrans a current (Just (LemmaPlus t features)) next next = current + 1 -- in foldr helper emptyAut (zip theStates nodes)@@ -1180,7 +1188,6 @@ \begin{code} partialResults :: SimpleStatus -> [SimpleItem]-#ifndef DISABLE_GUI partialResults st = unfoldr getNext 0 where inputsem = tsem st@@ -1199,8 +1206,20 @@ 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}++% --------------------------------------------------------------------+% Performance+% --------------------------------------------------------------------++\begin{code}+{-+instance NFData SimpleItem where+ rnf (SimpleItem x1 x2 x3 x4 x5 x6 x7 x8 x9 x10 x11 x12 x13+ ) = rnf x1 `seq` rnf x2 `seq` rnf x3 `seq` rnf x4 `seq` rnf x5 `seq` rnf x6+ `seq` rnf x7 `seq` rnf x8 `seq` rnf x9 `seq` rnf x10 `seq` rnf x11+ `seq` rnf x11 `seq` rnf x12 `seq` rnf x13+ `seq` rnf x14+-} \end{code}
src/NLP/GenI/Simple/SimpleGui.lhs view
@@ -26,31 +26,32 @@ \begin{code} import Graphics.UI.WX +import Control.Arrow ( (&&&) ) 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.Btypes (GNode(gnname, gup), AvPair(..), 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,+ debuggerPanel, DebuggerItemBar, setGvParams, GvIO, newGvRef, GraphvizGuiSt(..), viewTagWidgets, XMGDerivation(getSourceTrees), )-import NLP.GenI.Tags (tsemantics, TagElem(idname, ttree), TagItem(..), emptyTE)+import NLP.GenI.Tags (tsemantics, DerivationStep(dsChild), TagElem(idname, ttree), TagItem(..), emptyTE) import NLP.GenI.GraphvizShow ( graphvizShowDerivation ) import qualified NLP.GenI.Builder as B+import NLP.GenI.Builder (LemmaPlus(..)) 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)+ , step, theResults, theAgenda, theHoldingPen, theChart, theTrash) \end{code} } @@ -87,14 +88,14 @@ \begin{code} realisationsGui :: ProgStateRef -> (Window a) -> [SimpleItem]- -> GvIO Bool (Maybe SimpleItem)+ -> GvIO () Bool (Maybe SimpleItem) realisationsGui _ f [] = do m <- messageGui f "No results found"- g <- newGvRef False [] ""+ g <- newGvRef () False "" return (m, g, return ()) realisationsGui pstRef f resultsRaw = do let tip = "result"- itNlabl = map (\t -> (Just t, siToSentence t)) resultsRaw+ itNlabl = map (Just &&& siToSentence) resultsRaw -- pst <- readIORef pstRef -- FIXME: have to show the semantics again@@ -116,7 +117,7 @@ stToGraphviz :: SimpleStatus -> [(Maybe SimpleItem, String)] stToGraphviz st = let agenda = section "AGENDA" $ theAgenda st- auxAgenda = section "AUXILIARY" $ theAuxAgenda st+ auxAgenda = section "HOLDING" $ theHoldingPen st trash = section "TRASH" $ theTrash st chart = section "CHART" $ theChart st results = section "RESULTS" $ theResults st@@ -127,9 +128,10 @@ showPaths t = " (" ++ showPolPaths t ++ ")" in concat [ agenda, auxAgenda, chart, trash, results ] -simpleItemBar :: Params -> DebuggerItemBar Bool SimpleItem+simpleItemBar :: Params -> DebuggerItemBar SimpleStatus Bool SimpleItem simpleItemBar pa f gvRef updaterFn = do ib <- panel f []+ phaseTxt <- staticText ib [ text := "" ] detailsChk <- checkBox ib [ text := "Show features" , checked := False ] viewTagLay <- viewTagWidgets ib gvRef pa@@ -140,12 +142,18 @@ updaterFn set detailsChk [ on command := onDetailsChk ] --- return . hfloatCentre . (container ib) . row 5 $+ let lay = hfloatCentre . container ib . row 5 $ [ hspace 5+ , widget phaseTxt+ , hglue , widget detailsChk , hglue , viewTagLay , hspace 5 ]+ let onUpdate =+ do status <- gvcore `fmap` readIORef gvRef+ set phaseTxt [ text := show (step status) ]+ return (lay, onUpdate) \end{code} % --------------------------------------------------------------------@@ -160,7 +168,7 @@ instance XMGDerivation SimpleItem where -- Note: this is XMG-related stuff- getSourceTrees it = tgIdName it : (map snd3 . siDerivation $ it)+ getSourceTrees it = tgIdName it : (map dsChild . siDerivation $ it) \end{code} \begin{code}@@ -188,7 +196,7 @@ 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]) ] }+ Nothing -> emptyGNode { gup = [ AvPair "cat" (GConst ["error looking up " ++ k]) ] } Just x -> x \end{code} @@ -196,5 +204,5 @@ siToSentence :: SimpleItem -> String siToSentence si = case unpackResult si of [] -> siIdname.siGuiStuff $ si- (h:_) -> unwords . map fst . fst $ h+ (h:_) -> unwords . map lpLemma . fst $ h \end{code}
src/NLP/GenI/Statistics.hs view
@@ -30,67 +30,38 @@ USA. -} -module NLP.GenI.Statistics(Statistics, StatisticsState, StatisticsStateIO,+module NLP.GenI.Statistics(Statistics, StatisticsState, emptyStats, - printOutAllMetrics, printOutAllMetrics', printOutInspectionMetrics, showFinalStats, initialStatisticsStateFor,- addMetric, addInspectionMetric, setPrintOutInterval,- mergeMetrics,+ addMetric, Metric(IntMetric), queryMetrics, updateMetrics,- incrIntMetric, queryIntMetric, addIntMetrics,+ incrIntMetric, queryIntMetric, ) where import Control.Monad.State import Data.Maybe (mapMaybe)-import Data.List (intersperse)+import Text.JSON ------------------------------------------- -- 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}+newtype Statistics = Stat{ metrics::[Metric] } 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)}+updateMetrics f stat = stat{metrics = map f (metrics 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+queryMetrics f = mapMaybe f . metrics emptyStats :: Statistics-emptyStats = Stat{metrics=[],- inspectionMetrics=[],- count=0,- step=Nothing}+emptyStats = Stat [] --------------------------- Monadic Statistics functions follow ------------------------------ @@ -98,53 +69,13 @@ 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 -}+-- | Adds a metric at the beginning of the list+-- (note we reverse the order whene we want to print the metrics) 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"+addMetric newMetric = modify (\stat -> stat{metrics = newMetric : metrics stat } ) showFinalStats :: Statistics -> String-showFinalStats stats = unlines $ map show $ metrics stats+showFinalStats = unlines . map show . reverse . metrics -------------------------------------------- -- Metrics@@ -152,7 +83,7 @@ data Metric = IntMetric String Int instance Show Metric where- show (IntMetric s x) = s ++ " : " ++ (show x)+ show (IntMetric s x) = s ++ " : " ++ show x incrIntMetric :: String -> Int -> Metric -> Metric incrIntMetric key i (IntMetric s c) | s == key = IntMetric s (c+i)@@ -162,10 +93,13 @@ 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+--------------------------- JSON Output ------------------------------ --- ratio :: Int -> Int -> Float--- ratio x y = (fromIntegral x) / (fromIntegral y)+instance JSON Statistics where+ readJSON _j =+ error "can't read GenI statistics from JSON yet; sorry"+ showJSON = JSObject . toJSObject . map metricToJSON . metrics +-- not quite showJSON here+metricToJSON :: Metric -> (String, JSValue)+metricToJSON (IntMetric s i) = (s, showJSON i)
+ src/NLP/GenI/SysGeni.hs view
@@ -0,0 +1,77 @@+-- 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.++{-# LANGUAGE ForeignFunctionInterface #-}++-- | 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 @../Resources/bin@+-- to the path for the miscellaneous resources that we ship with with GenI.+module NLP.GenI.SysGeni+where++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++-- * Running a process++waitForProcess :: S.ProcessHandle -> IO ExitCode+waitForProcess = S.waitForProcess++-- | 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 @../Resources/bin@.+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 ++-- * Process helpers++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
− src/NLP/GenI/SysGeni.lhs
@@ -1,93 +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--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
@@ -24,10 +24,12 @@ and \ref{sec:adjunction} instead. \begin{code}+{-# LANGUAGE TemplateHaskell #-}+ module NLP.GenI.Tags( -- Main Datatypes Tags, TagElem(..), TagItem(..), TagSite(..),- TagDerivation, emptyTE,+ TagDerivation, DerivationStep(..), emptyTE, ts_synIncomplete, ts_semIncomplete, ts_tbUnificationFailure, ts_rootFeatureMismatch, @@ -45,22 +47,29 @@ \ignore{ \begin{code}+import Control.Applicative ( (<$>), (<*>) ) import qualified Data.Map as Map import Data.Maybe (fromMaybe, listToMaybe, mapMaybe) import Data.List (intersperse) import Data.Tree +import Data.Generics (Data)+import Data.Generics.PlateDirect+import Data.Typeable (Typeable)+import Text.JSON+ import NLP.GenI.Btypes (Ptype(Initial, Auxiliar), SemPols,- GeniVal(GConst),+ GeniVal(GConst), AvPair(..), GNode(gup, glexeme, gnname, gaconstr, gdown, gtype, gorigin), GType(Subs), Flist,- Replacable(..), replaceOneAsMap,+ DescendGeniVal(..), Collectable(..), Idable(..), Sem, Pred, emptyPred, emptyGNode, showFlist, showPairs, showSem, lexemeAttributes, ) import NLP.GenI.General (groupByFM, preTerminals)+import NLP.GenI.PolarityTypes (PolarityKey(..)) \end{code} } @@ -89,28 +98,42 @@ combining macros with lexicon (see section \ref{sec:combine_macros}). \begin{code}-data TagSite = TagSite { tsName :: !String- , tsUp :: !Flist- , tsDown :: !Flist- , tsOrigin :: !String+data TagSite = TagSite { tsName :: String+ , tsUp :: Flist+ , tsDown :: Flist+ , tsOrigin :: String }- deriving (Show, Eq, Ord)+ deriving (Show, Eq, Ord, Data, Typeable) +instance Biplate TagSite GeniVal where+ biplate (TagSite x1 zu zd x2) = plate TagSite |- x1 ||+ zu ||+ zd |- x2++instance Biplate (Maybe TagSite) GeniVal where+ biplate (Just x1) = plate Just |+ x1+ biplate Nothing = plate Nothing+ data TagElem = TE { idname :: String, ttreename :: String, tidnum :: Integer,- ttype :: !Ptype,+ ttype :: Ptype, ttree :: Tree GNode, tsemantics :: Sem, -- optimisation stuff -- (polarity key to charge interval)- tpolarities :: Map.Map String (Int,Int), + tpolarities :: Map.Map PolarityKey (Int,Int), tinterface :: Flist, -- for idxconstraints (pol) ttrace :: [String], tsempols :: [SemPols] }- deriving (Show, Eq)+ deriving (Show, Eq, Data, Typeable)++instance Biplate TagElem GeniVal where+ biplate (TE x1 x2 x3 x4 zt zsem x5 zint x6 x7) =+ plate TE |- x1 |- x2 |- x3 |- x4+ |+ zt+ ||+ zsem |- x5+ ||+ zint |- x6 |- x7 \end{code} A TAG derivation history consists of a list of 3-tuples representing the@@ -118,7 +141,30 @@ the name of the parent tree and the node affected. \begin{code}-type TagDerivation = [ (Char, String, (String, String)) ]+type TagDerivation = [ DerivationStep ]++data DerivationStep = DerivationStep+ { dsOp :: Char+ , dsChild :: String+ , dsParent :: String+ , dsParentSite :: String+ } deriving (Show, Ord, Eq)++instance JSON DerivationStep where+ readJSON j =+ do jo <- fromJSObject `fmap` readJSON j+ let field x = maybe (fail $ "Could not find: " ++ x) readJSON+ $ lookup x jo+ DerivationStep <$> field "op"+ <*> field "child"+ <*> field "parent"+ <*> field "parent-node"+ showJSON x =+ JSObject . toJSObject $ [ ("op", showJSON $ dsOp x)+ , ("child", showJSON $ dsChild x)+ , ("parent", showJSON $ dsParent x)+ , ("parent-node", showJSON $ dsParentSite x)+ ] \end{code} \begin{code}@@ -132,16 +178,14 @@ _ -> 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 DescendGeniVal TagElem where+ descendGeniVal s te =+ te { tinterface = descendGeniVal s (tinterface te)+ , ttree = descendGeniVal s (ttree te)+ , tsemantics = descendGeniVal s (tsemantics te) } -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 DescendGeniVal TagSite where+ descendGeniVal s (TagSite n fu fd o) = TagSite n (descendGeniVal s fu) (descendGeniVal s fd) o instance Collectable TagElem where collect t = (collect $ tinterface t) . (collect $ ttree t) @@ -288,7 +332,7 @@ lexs -> lexs where grab la =- let match (a, (GConst v)) | a == la = Just v+ let match (AvPair a (GConst v)) | a == la = Just v match _ = Nothing in firstMaybe match guppy guppy = gup node@@ -330,3 +374,7 @@ ts_semIncomplete :: [Pred] -> String ts_semIncomplete sem = "semantically incomplete - missing: " ++ showSem sem \end{code}++% ----------------------------------------------------------------------+% Performance+% ----------------------------------------------------------------------
src/NLP/GenI/Test.hs view
@@ -17,20 +17,15 @@ -- 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+import System.Environment ( getArgs )+import Test.Framework+import NLP.GenI.GeniVal ( testSuite ) 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]+ do args <- filter (/= "--unit-tests") `fmap` getArgs+ flip defaultMainWithArgs args+ [ NLP.GenI.GeniVal.testSuite+ ]
− src/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}