packages feed

dawgdic-0.1.0: src/Data/DAWG/Internal/DAWG.hs

{-|
Module: Data.DAWG.Internal.DAWG
Description: Exports dawg as well as its internal API.
Copyright: (c) Andrey Prokopenko, 2025
License: BSD-3-Clause
Stability: experimental
-}
module Data.DAWG.Internal.DAWG where

import Control.DeepSeq (NFData)
import Control.Monad (forM_)
import Data.Bit (Bit (..))
import Data.Char
import Data.Maybe (fromMaybe)
import GHC.Generics (Generic)
import GHC.Stack (HasCallStack)

import Data.DAWG.Internal.BaseType
import Data.DAWG.Internal.BaseUnit (BaseUnit (..))

import qualified Data.Vector.Unboxed as UV

import qualified Data.DAWG.Internal.BaseUnit as BU


-- ** DAWG

-- | Represents directed acycclic word graph (DAWG), the core data type of this package.
-- Could be built from the input lexicon (list of words).
-- Mostly it is used to generate separate 'Data.DAWG.Internal.Dictionary.Dictionary'
-- (for querying words and searching associated values)
-- and 'Data.DAWG.Internal.Guide' (for faster completions).
--
data DAWG = DAWG
  { dawgBasePool :: BasePool -- ^ Pool of base units.
  , dawgLabelPool :: LabelPool -- ^ Pool of characters.
  , dawgFlagPool :: FlagPool -- ^ Pool of bits.
  , dawgNumOfStates :: SizeType -- ^ Number of states.
  , dawgNumOfMergedTransitions :: SizeType -- ^ Number of merged transitions.
  , dawgNumOfMergedStates :: SizeType -- ^ Number of merged states.
  , dawgNumOfMergingStates :: SizeType -- ^ Number of merging states.
  }
  deriving (Generic, NFData)


-- | Wrapper around a vector of base units.
newtype BasePool = BasePool { unBasePool :: UV.Vector BaseUnit }
  deriving newtype NFData

-- | Wrapper around a vector of chars.
newtype LabelPool = LabelPool { unLabelPool :: UV.Vector UCharType }
  deriving newtype NFData

-- | Wrapper around 'BitPool'.
newtype FlagPool = FlagPool { unFlagPool :: BitPool }
  deriving newtype NFData

-- | Wrapper around a vector of bits.
newtype BitPool = BitPool { unBitPool :: UV.Vector Bit }
  deriving newtype NFData

-- | Root dawg index. Equivalent to @0@.
root :: BaseType
root = 0
{-# INLINE root #-}

-- | Constructs an empty dawg.
empty :: DAWG
empty = DAWG
  { dawgBasePool = BasePool UV.empty
  , dawgLabelPool = LabelPool UV.empty
  , dawgFlagPool = FlagPool (BitPool UV.empty)
  , dawgNumOfStates = 1
  , dawgNumOfMergedTransitions = 0
  , dawgNumOfMergedStates = 0
  , dawgNumOfMergingStates = 0
  }
{-# INLINE empty #-}

-- | Gets a child by given dawg index.
child :: HasCallStack => BaseType -> DAWG -> BaseType
child !ix = BU.child . (UV.! fromIntegral ix) . unBasePool . dawgBasePool
{-# INLINE child #-}

-- | Gets a sibling by given dawg index.
sibling :: HasCallStack => BaseType -> DAWG -> BaseType
sibling !ix !dawg =
  let hasSibling' = BU.hasSibling . (UV.! fromIntegral ix) . unBasePool . dawgBasePool
      itHasSibling = hasSibling' dawg
  in if itHasSibling then ix + 1 else root
{-# INLINE sibling #-}

-- | Gets a value by given dawg index.
value :: HasCallStack => BaseType -> DAWG -> ValueType
value !ix = BU.value . (UV.! fromIntegral ix) . unBasePool . dawgBasePool
{-# INLINE value #-}

-- | Checks whether the given dawg index is leaf or not.
isLeaf :: HasCallStack => BaseType -> DAWG -> Bool
isLeaf !ix = (== '\0') . chr . fromIntegral . label ix
{-# INLINE isLeaf #-}

-- | Gets a label by given dawg index.
label :: HasCallStack => BaseType -> DAWG -> UCharType
label !ix = (UV.! fromIntegral ix) . unLabelPool . dawgLabelPool
{-# INLINE label #-}

-- | Checks whether given dawg index is merging or not.
isMerging :: HasCallStack => BaseType -> DAWG -> Bool
isMerging !ix = unBit . (UV.! fromIntegral ix) . unBitPool . unFlagPool . dawgFlagPool
{-# INLINE isMerging #-}

-- | Gets a size of a dawg.
size :: DAWG -> SizeType
size = fromIntegral . UV.length . unBasePool . dawgBasePool
{-# INLINE size #-}

-- ** Helpers

-- | Dump dawg state to stdout.
dump :: DAWG -> IO ()
dump DAWG{..} = do
  putStrLn "base_pool"
  let BasePool bpool = dawgBasePool
      LabelPool lpool = dawgLabelPool
      !bs = UV.length bpool
      !ls = UV.length lpool
      !ms = max bs ls

  putStrLn $ concat [ "b(" <> show bs <> ")\tl(" <> show ls <> ")" ]
  
  forM_ [0 .. ms - 1] \ix -> do
    let !b = fromMaybe (BaseUnit 0) (bpool UV.!? ix)
        !l = fromMaybe 0 (lpool UV.!? ix)
    putStrLn $ concat
      [ show b, "\t", show (chr $ fromIntegral l), " (", show l, ")" ]

  putStrLn $ "num_of_states : " <> show dawgNumOfStates
  putStrLn $ "num_of_merged_transitions : " <> show dawgNumOfMergedTransitions
  putStrLn $ "num_of_merged_states : " <> show dawgNumOfMergedStates
  putStrLn $ "num_of_merging_states : " <> show dawgNumOfMergingStates