packages feed

regexp-tries-0.1.0: src/Data/ListTrie/RegExp/Weighted.hs

{-# LANGUAGE
        FlexibleContexts,
        FlexibleInstances,
        FunctionalDependencies,
        MultiParamTypeClasses,
        OverlappingInstances,
        UndecidableInstances
  #-}
{-# OPTIONS_GHC
        -fno-warn-orphans
  #-}

{- | This module provides 'delete' and 'lookup' functions for 'StringTrie'
     using regular expressions. The algorithm used is the one from the package
    'weighted-regexp': <http://sebfisch.github.com/haskell-regexp/>.
-}

module Data.ListTrie.RegExp.Weighted
    (
    -- * Trie types
      ListTrie
    , StringTrie

    -- * Construction of regular expressions
    , RegExp

    , eps
    , char
    , sym
    , psym

    , anySym
    , noMatch

    , alt
    , seq_

    , rep
    , rep1
    , opt
    , brep

    , perm

    -- * Expression Type for Combinators
    , Expr
    , ExprClass

    -- * Regular Expression Combinators
    , complement
    , intersection
    , union

    -- * Trie operations
    , lookup
    , delete

    , lookupW
    )
where

import Prelude hiding
    ( lookup
    , mod
    , null
    , pred
    )
import Data.ListTrie.ListTrie hiding
    ( delete
    , empty
    , intersection
    , lookup
    , union
    )
import Data.ListTrie.RegExp.Common
import Data.Maybe
import Data.Semiring
import Text.RegExp
import Text.RegExp.Internal

-- Storing weights within the Trie ---------------------------------------------

class StoreWeights v w v' | v v' -> w where
    store :: w -> v -> v'
    select :: m v -> m v' -> m v'

instance StoreWeights v Bool v where
    store _ v = v
    select m _ = m

instance StoreWeights v w (v, w) where
    store w v = (v, w)
    select _ m = m

-- Regular Expression lookup ---------------------------------------------------

instance WrapClass map emap sym
      => LookupRegExpClass (RegExp sym) map emap sym where
    lookupRegExp     = lookupReW 0 True id  empty
    lookupComplement = lookupReW 0 True not empty

lookupReW
    :: (WrapClass map emap sym, StoreWeights v w v', Weight sym sym w)
    => Int
    -> w
    -> (w -> w)
    -> (RegW w sym -> w)
    -> RegExp sym
    -> map v
    -> map v'
lookupReW n mark mod pred (RegExp re) t =
    wrap $ lookupRe n mark mod pred re $ unwrap t

lookupRe
    :: (KeyMap sym map, StoreWeights v w v', Weight sym sym w)
    => Int
    -> w
    -> (w -> w)
    -> (RegW w sym -> w)
    -> RegW w sym
    -> ListTrie map v
    -> ListTrie map v'
lookupRe _ _    _   _    _  NoListTrie       = NoListTrie
lookupRe n mark mod pred re t@(ListTrie tn tc)
    | n > 0 && not (active re) =
        if mod one' /= zero
            then NoListTrie
            else select t $ error "This should never haqppen!"
                        -- (KM.map (store one') t)
    | otherwise =
        if isJust tn' || not (null tc')
            then ListTrie tn' tc'
            else NoListTrie
    where
        one' = one
        tn'  = if w /= zero then fmap (store w) tn else Nothing
        w    = mod $ pred re
        tc'  = mapMaybeWithKey go tc

        go c ts =
            cleanup $ lookupRe (n + 1) zero mod final (shift mark (reg re) c) ts

        cleanup ts
            | null ts   = Nothing
            | otherwise = Just ts

-- Expression instances --------------------------------------------------------

instance ExprClass (RegExp sym) (RegExp sym) where
    toExpr = REExpr

instance ExprClass e (RegExp Char) => Show e where
    showsPrec _ e =
        case toExpr e of
            REExpr re ->
                showString "(RegExp<" . shows re . showString ">)"
            Complement e' ->
                showString "(Complement " . shows e' . showString ")"
            Intersection e1 e2 ->
                showString "(Intersection " . shows e1 . showChar ' '
                                            . shows e2 . showString ")"
            Union e1 e2 ->
                showString "(Union" . shows e1 . showChar ' '
                                    . shows e2 . showString ")"

-- Trie operations -------------------------------------------------------------

{- | Lookup the given regular expression in the Trie and return a Trie of
     results, containing also the calculated weights.

     Note: As weights don't make much sense with delete (would all be 'zero'),
     there is no 'deleteW'.

     Note: Since there doesn't seem to be a way to efficiently allow combining
     of weighted regular expressions, there are no combinators for this for now.
-}
lookupW
    :: (WrapClass map emap sym, StoreWeights v w (v, w), Weight sym sym w)
    => RegExp sym
    -> map v
    -> map (v, w)
lookupW (RegExp re) t = wrap $ lookupRe 0 one id empty (weighted re) $ unwrap t

-- Optimzation of combined Regular Expressions ---------------------------------

instance OptimizeClass (RegExp sym) where
    optimizeExpr e@(REExpr _) =
        e

    optimizeExpr (Complement e) =
        case optimizeExpr e of
            (Complement e') ->
                e'

            (Intersection (Complement e1') (Complement e2')) ->
                Union e1' e2'

            e' ->
                Complement e'

    optimizeExpr (Intersection e1 e2) =
        case (optimizeExpr e1, optimizeExpr e2) of
            (e1', e2') ->
                Intersection (optimizeExpr e1') (optimizeExpr e2')

    optimizeExpr (Union e1 e2) =
        case (optimizeExpr e1, optimizeExpr e2) of
            (REExpr r1, REExpr r2) ->
                REExpr $ r1 `alt` r2

            -- Complement of regular expressions is cheap.
            (e1'@(Complement (REExpr _)), e2') ->
                Union e1' e2'
            (e1', e2'@(Complement (REExpr _))) ->
                Union e1' e2'

            -- Evaluating Complement once is cheaper than doing it twice.
            (Complement e1', Complement e2') ->
                Complement $ Intersection e1' e2'

            (e1', e2') ->
                Union e1' e2'